summaryrefslogtreecommitdiff
path: root/Runtime/Graphics
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Graphics')
-rw-r--r--Runtime/Graphics/CubemapProcessor.cpp515
-rw-r--r--Runtime/Graphics/CubemapProcessor.h35
-rw-r--r--Runtime/Graphics/CubemapTexture.cpp186
-rw-r--r--Runtime/Graphics/CubemapTexture.h30
-rw-r--r--Runtime/Graphics/DXTCompression.cpp595
-rw-r--r--Runtime/Graphics/DXTCompression.h13
-rw-r--r--Runtime/Graphics/DisplayManager.h54
-rw-r--r--Runtime/Graphics/DrawSplashScreenAndWatermarks.cpp449
-rw-r--r--Runtime/Graphics/DrawSplashScreenAndWatermarks.h18
-rw-r--r--Runtime/Graphics/DrawUtil.cpp173
-rw-r--r--Runtime/Graphics/DrawUtil.h51
-rw-r--r--Runtime/Graphics/ETC2Decompression.cpp487
-rw-r--r--Runtime/Graphics/ETC2Decompression.h39
-rw-r--r--Runtime/Graphics/FlashATFDecompression.h7
-rw-r--r--Runtime/Graphics/GeneratedTextures.cpp258
-rw-r--r--Runtime/Graphics/GeneratedTextures.h28
-rw-r--r--Runtime/Graphics/GraphicsHelper.cpp103
-rw-r--r--Runtime/Graphics/GraphicsHelper.h98
-rw-r--r--Runtime/Graphics/Image.cpp1657
-rw-r--r--Runtime/Graphics/Image.h178
-rw-r--r--Runtime/Graphics/ImageConversion.cpp621
-rw-r--r--Runtime/Graphics/ImageConversion.h28
-rw-r--r--Runtime/Graphics/LightProbeGroup.cpp50
-rw-r--r--Runtime/Graphics/LightProbeGroup.h41
-rw-r--r--Runtime/Graphics/LightmapSettings.cpp153
-rw-r--r--Runtime/Graphics/LightmapSettings.h101
-rw-r--r--Runtime/Graphics/LowerResBlitTexture.h61
-rw-r--r--Runtime/Graphics/MatrixStack.cpp72
-rw-r--r--Runtime/Graphics/MatrixStack.h32
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp102
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h29
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp603
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h62
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp60
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp51
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorModule.h25
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp124
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h34
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp118
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h25
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp164
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ForceModule.h36
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp193
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/InitialModule.h66
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp141
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h238
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp51
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp83
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp650
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h80
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp50
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp41
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp148
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SubModule.h38
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp105
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/UVModule.h36
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp127
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h36
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp122
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h42
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystem.cpp2110
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystem.h298
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h48
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp196
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h187
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp27
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h87
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp323
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h116
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp1241
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h134
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp29
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp113
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h48
-rw-r--r--Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp405
-rw-r--r--Runtime/Graphics/ParticleSystem/PolynomialCurve.h229
-rw-r--r--Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp259
-rw-r--r--Runtime/Graphics/Polygon2D.cpp145
-rw-r--r--Runtime/Graphics/Polygon2D.h66
-rw-r--r--Runtime/Graphics/ProceduralCache.cpp154
-rw-r--r--Runtime/Graphics/ProceduralLinker.cpp521
-rw-r--r--Runtime/Graphics/ProceduralMaterial.cpp1339
-rw-r--r--Runtime/Graphics/ProceduralMaterial.h289
-rw-r--r--Runtime/Graphics/ProceduralPreset.cpp143
-rw-r--r--Runtime/Graphics/ProceduralTexture.cpp372
-rw-r--r--Runtime/Graphics/ProceduralTexture.h190
-rw-r--r--Runtime/Graphics/RenderBufferManager.cpp251
-rw-r--r--Runtime/Graphics/RenderBufferManager.h58
-rw-r--r--Runtime/Graphics/RenderSurface.h40
-rw-r--r--Runtime/Graphics/RenderTexture.cpp889
-rw-r--r--Runtime/Graphics/RenderTexture.h210
-rw-r--r--Runtime/Graphics/S3Decompression.cpp1882
-rw-r--r--Runtime/Graphics/S3Decompression.h18
-rw-r--r--Runtime/Graphics/ScreenManager.cpp201
-rw-r--r--Runtime/Graphics/ScreenManager.h257
-rw-r--r--Runtime/Graphics/SpriteFrame.cpp272
-rw-r--r--Runtime/Graphics/SpriteFrame.h190
-rw-r--r--Runtime/Graphics/SpriteUtility.cpp155
-rw-r--r--Runtime/Graphics/SpriteUtility.h22
-rw-r--r--Runtime/Graphics/SubstanceArchive.cpp121
-rw-r--r--Runtime/Graphics/SubstanceArchive.h75
-rw-r--r--Runtime/Graphics/SubstanceInput.h243
-rw-r--r--Runtime/Graphics/SubstanceSystem.cpp876
-rw-r--r--Runtime/Graphics/SubstanceSystem.h160
-rw-r--r--Runtime/Graphics/Texture.cpp332
-rw-r--r--Runtime/Graphics/Texture.h165
-rw-r--r--Runtime/Graphics/Texture2D.cpp1422
-rw-r--r--Runtime/Graphics/Texture2D.h208
-rw-r--r--Runtime/Graphics/Texture3D.cpp322
-rw-r--r--Runtime/Graphics/Texture3D.h67
-rw-r--r--Runtime/Graphics/TextureFormat.cpp248
-rw-r--r--Runtime/Graphics/TextureFormat.h96
-rw-r--r--Runtime/Graphics/TextureGenerator.h114
-rw-r--r--Runtime/Graphics/TextureSettings.cpp64
-rw-r--r--Runtime/Graphics/TextureSettings.h42
-rw-r--r--Runtime/Graphics/Transform.cpp1695
-rw-r--r--Runtime/Graphics/Transform.h327
-rw-r--r--Runtime/Graphics/TransformTests.cpp102
-rw-r--r--Runtime/Graphics/TriStripper.cpp112
-rw-r--r--Runtime/Graphics/TriStripper.h22
125 files changed, 30275 insertions, 0 deletions
diff --git a/Runtime/Graphics/CubemapProcessor.cpp b/Runtime/Graphics/CubemapProcessor.cpp
new file mode 100644
index 0000000..2cf535e
--- /dev/null
+++ b/Runtime/Graphics/CubemapProcessor.cpp
@@ -0,0 +1,515 @@
+#include "UnityPrefix.h"
+#include "CubemapProcessor.h"
+
+//--------------------------------------------------------------------------------------
+//Based on CCubeMapProcessor from CubeMapGen v1.4
+// Class for filtering and processing cubemaps
+//
+//
+//--------------------------------------------------------------------------------------
+// (C) 2005 ATI Research, Inc., All rights reserved.
+//--------------------------------------------------------------------------------------
+
+//used to index cube faces
+#define CP_FACE_X_POS 0
+#define CP_FACE_X_NEG 1
+#define CP_FACE_Y_POS 2
+#define CP_FACE_Y_NEG 3
+#define CP_FACE_Z_POS 4
+#define CP_FACE_Z_NEG 5
+
+//used to index image edges
+// NOTE.. the actual number corresponding to the edge is important
+// do not change these, or the code will break
+//
+// CP_EDGE_LEFT is u = 0
+// CP_EDGE_RIGHT is u = width-1
+// CP_EDGE_TOP is v = 0
+// CP_EDGE_BOTTOM is v = height-1
+#define CP_EDGE_LEFT 0
+#define CP_EDGE_RIGHT 1
+#define CP_EDGE_TOP 2
+#define CP_EDGE_BOTTOM 3
+
+//corners of CUBE map (P or N specifys if it corresponds to the
+// positive or negative direction each of X, Y, and Z
+#define CP_CORNER_NNN 0
+#define CP_CORNER_NNP 1
+#define CP_CORNER_NPN 2
+#define CP_CORNER_NPP 3
+#define CP_CORNER_PNN 4
+#define CP_CORNER_PNP 5
+#define CP_CORNER_PPN 6
+#define CP_CORNER_PPP 7
+
+
+//information about cube maps neighboring face after traversing
+// across an edge
+struct CPCubeMapNeighbor
+{
+ UInt8 m_Face; //index of neighboring face
+ UInt8 m_Edge; //edge in neighboring face that abuts this face
+};
+
+//------------------------------------------------------------------------------
+// D3D cube map face specification
+// mapping from 3D x,y,z cube map lookup coordinates
+// to 2D within face u,v coordinates
+//
+// --------------------> U direction
+// | (within-face texture space)
+// | _____
+// | | |
+// | | +Y |
+// | _____|_____|_____ _____
+// | | | | | |
+// | | -X | +Z | +X | -Z |
+// | |_____|_____|_____|_____|
+// | | |
+// | | -Y |
+// | |_____|
+// |
+// v V direction
+// (within-face texture space)
+//------------------------------------------------------------------------------
+
+//Information about neighbors and how texture coorrdinates change across faces
+// in ORDER of left, right, top, bottom (e.g. edges corresponding to u=0,
+// u=1, v=0, v=1 in the 2D coordinate system of the particular face.
+//Note this currently assumes the D3D cube face ordering and orientation
+CPCubeMapNeighbor sg_CubeNgh[6][4] =
+{
+ //XPOS face
+ {{CP_FACE_Z_POS, CP_EDGE_RIGHT },
+ {CP_FACE_Z_NEG, CP_EDGE_LEFT },
+ {CP_FACE_Y_POS, CP_EDGE_RIGHT },
+ {CP_FACE_Y_NEG, CP_EDGE_RIGHT }},
+ //XNEG face
+ {{CP_FACE_Z_NEG, CP_EDGE_RIGHT },
+ {CP_FACE_Z_POS, CP_EDGE_LEFT },
+ {CP_FACE_Y_POS, CP_EDGE_LEFT },
+ {CP_FACE_Y_NEG, CP_EDGE_LEFT }},
+ //YPOS face
+ {{CP_FACE_X_NEG, CP_EDGE_TOP },
+ {CP_FACE_X_POS, CP_EDGE_TOP },
+ {CP_FACE_Z_NEG, CP_EDGE_TOP },
+ {CP_FACE_Z_POS, CP_EDGE_TOP }},
+ //YNEG face
+ {{CP_FACE_X_NEG, CP_EDGE_BOTTOM},
+ {CP_FACE_X_POS, CP_EDGE_BOTTOM},
+ {CP_FACE_Z_POS, CP_EDGE_BOTTOM},
+ {CP_FACE_Z_NEG, CP_EDGE_BOTTOM}},
+ //ZPOS face
+ {{CP_FACE_X_NEG, CP_EDGE_RIGHT },
+ {CP_FACE_X_POS, CP_EDGE_LEFT },
+ {CP_FACE_Y_POS, CP_EDGE_BOTTOM },
+ {CP_FACE_Y_NEG, CP_EDGE_TOP }},
+ //ZNEG face
+ {{CP_FACE_X_POS, CP_EDGE_RIGHT },
+ {CP_FACE_X_NEG, CP_EDGE_LEFT },
+ {CP_FACE_Y_POS, CP_EDGE_TOP },
+ {CP_FACE_Y_NEG, CP_EDGE_BOTTOM }}
+};
+
+
+//The 12 edges of the cubemap, (entries are used to index into the neighbor table)
+// this table is used to average over the edges.
+int sg_CubeEdgeList[12][2] = {
+ {CP_FACE_X_POS, CP_EDGE_LEFT},
+ {CP_FACE_X_POS, CP_EDGE_RIGHT},
+ {CP_FACE_X_POS, CP_EDGE_TOP},
+ {CP_FACE_X_POS, CP_EDGE_BOTTOM},
+
+ {CP_FACE_X_NEG, CP_EDGE_LEFT},
+ {CP_FACE_X_NEG, CP_EDGE_RIGHT},
+ {CP_FACE_X_NEG, CP_EDGE_TOP},
+ {CP_FACE_X_NEG, CP_EDGE_BOTTOM},
+
+ {CP_FACE_Z_POS, CP_EDGE_TOP},
+ {CP_FACE_Z_POS, CP_EDGE_BOTTOM},
+ {CP_FACE_Z_NEG, CP_EDGE_TOP},
+ {CP_FACE_Z_NEG, CP_EDGE_BOTTOM}
+};
+
+
+//Information about which of the 8 cube corners are correspond to the
+// the 4 corners in each cube face
+// the order is upper left, upper right, lower left, lower right
+int sg_CubeCornerList[6][4] = {
+ { CP_CORNER_PPP, CP_CORNER_PPN, CP_CORNER_PNP, CP_CORNER_PNN }, // XPOS face
+ { CP_CORNER_NPN, CP_CORNER_NPP, CP_CORNER_NNN, CP_CORNER_NNP }, // XNEG face
+ { CP_CORNER_NPN, CP_CORNER_PPN, CP_CORNER_NPP, CP_CORNER_PPP }, // YPOS face
+ { CP_CORNER_NNP, CP_CORNER_PNP, CP_CORNER_NNN, CP_CORNER_PNN }, // YNEG face
+ { CP_CORNER_NPP, CP_CORNER_PPP, CP_CORNER_NNP, CP_CORNER_PNP }, // ZPOS face
+ { CP_CORNER_PPN, CP_CORNER_NPN, CP_CORNER_PNN, CP_CORNER_NNN } // ZNEG face
+};
+
+
+
+//==========================================================================================================
+//void FixupCubeEdges(CImageSurface *a_CubeMap, int a_FixupType, int a_FixupWidth);
+//
+//Apply edge fixup to a cubemap mip level.
+//
+//a_CubeMap [in/out] Array of 6 images comprising cubemap miplevel to apply edge fixup to.
+//a_FixupType [in] Specifies the technique used for edge fixup. Choose one of the following,
+// CP_FIXUP_NONE, CP_FIXUP_PULL_LINEAR, CP_FIXUP_PULL_HERMITE, CP_FIXUP_AVERAGE_LINEAR,
+// CP_FIXUP_AVERAGE_HERMITE
+//a_FixupWidth [in] Fixup width in texels
+//
+//==========================================================================================================
+
+
+//--------------------------------------------------------------------------------------
+// Fixup cube edges
+//
+// average texels on cube map faces across the edges
+//--------------------------------------------------------------------------------------
+
+void FixupCubeEdges(CImageSurface *a_CubeMap, int a_FixupType, int a_FixupWidth)
+{
+ int i, j, k;
+ int face;
+ int edge;
+ int neighborFace;
+ int neighborEdge;
+
+ int nChannels = a_CubeMap[0].m_NumChannels;
+ int size = a_CubeMap[0].m_Width;
+
+ CPCubeMapNeighbor neighborInfo;
+
+ CP_ITYPE* edgeStartPtr;
+ CP_ITYPE* neighborEdgeStartPtr;
+
+ int edgeWalk;
+ int neighborEdgeWalk;
+
+ //pointer walk to walk one texel away from edge in perpendicular direction
+ int edgePerpWalk;
+ int neighborEdgePerpWalk;
+
+ //number of texels inward towards cubeface center to apply fixup to
+ int fixupDist;
+ int iFixup;
+
+ // note that if functionality to filter across the three texels for each corner, then
+ CP_ITYPE *cornerPtr[8][3]; //indexed by corner and face idx
+ CP_ITYPE *faceCornerPtrs[4]; //corner pointers for face
+ int cornerNumPtrs[8]; //indexed by corner and face idx
+ int iCorner; //corner iterator
+ int iFace; //iterator for faces
+ int corner;
+
+ //if there is no fixup, or fixup width = 0, do nothing
+ if((a_FixupType == CP_FIXUP_NONE) ||
+ (a_FixupWidth == 0) )
+ {
+ return;
+ }
+
+ //special case 1x1 cubemap, average face colors
+ if( a_CubeMap[0].m_Width == 1 )
+ {
+ //iterate over channels
+ for(k=0; k<nChannels; k++)
+ {
+ CP_ITYPE accum = 0.0f;
+
+ //iterate over faces to accumulate face colors
+ for(iFace=0; iFace<6; iFace++)
+ {
+ accum += *(a_CubeMap[iFace].m_ImgData + k);
+ }
+
+ //compute average over 6 face colors
+ accum /= 6.0f;
+
+ //iterate over faces to distribute face colors
+ for(iFace=0; iFace<6; iFace++)
+ {
+ *(a_CubeMap[iFace].m_ImgData + k) = accum;
+ }
+ }
+
+ return;
+ }
+
+
+ //iterate over corners
+ for(iCorner = 0; iCorner < 8; iCorner++ )
+ {
+ cornerNumPtrs[iCorner] = 0;
+ }
+
+ //iterate over faces to collect list of corner texel pointers
+ for(iFace=0; iFace<6; iFace++ )
+ {
+ //the 4 corner pointers for this face
+ faceCornerPtrs[0] = a_CubeMap[iFace].m_ImgData;
+ faceCornerPtrs[1] = a_CubeMap[iFace].m_ImgData + ( (size - 1) * nChannels );
+ faceCornerPtrs[2] = a_CubeMap[iFace].m_ImgData + ( (size) * (size - 1) * nChannels );
+ faceCornerPtrs[3] = a_CubeMap[iFace].m_ImgData + ( (((size) * (size - 1)) + (size - 1)) * nChannels );
+
+ //iterate over face corners to collect cube corner pointers
+ for(i=0; i<4; i++ )
+ {
+ corner = sg_CubeCornerList[iFace][i];
+ cornerPtr[corner][ cornerNumPtrs[corner] ] = faceCornerPtrs[i];
+ cornerNumPtrs[corner]++;
+ }
+ }
+
+
+ //iterate over corners to average across corner tap values
+ for(iCorner = 0; iCorner < 8; iCorner++ )
+ {
+ for(k=0; k<nChannels; k++)
+ {
+ CP_ITYPE cornerTapAccum;
+
+ cornerTapAccum = 0.0f;
+
+ //iterate over corner texels and average results
+ for(i=0; i<3; i++ )
+ {
+ cornerTapAccum += *(cornerPtr[iCorner][i] + k);
+ }
+
+ //divide by 3 to compute average of corner tap values
+ cornerTapAccum *= (1.0f / 3.0f);
+
+ //iterate over corner texels and average results
+ for(i=0; i<3; i++ )
+ {
+ *(cornerPtr[iCorner][i] + k) = cornerTapAccum;
+ }
+ }
+ }
+
+
+ //maximum width of fixup region is one half of the cube face size
+ fixupDist = std::min( a_FixupWidth, size / 2);
+
+ //iterate over the twelve edges of the cube to average across edges
+ for(i=0; i<12; i++)
+ {
+ face = sg_CubeEdgeList[i][0];
+ edge = sg_CubeEdgeList[i][1];
+
+ neighborInfo = sg_CubeNgh[face][edge];
+ neighborFace = neighborInfo.m_Face;
+ neighborEdge = neighborInfo.m_Edge;
+
+ edgeStartPtr = a_CubeMap[face].m_ImgData;
+ neighborEdgeStartPtr = a_CubeMap[neighborFace].m_ImgData;
+ edgeWalk = 0;
+ neighborEdgeWalk = 0;
+
+ //amount to pointer to sample taps away from cube face
+ edgePerpWalk = 0;
+ neighborEdgePerpWalk = 0;
+
+ //Determine walking pointers based on edge type
+ // e.g. CP_EDGE_LEFT, CP_EDGE_RIGHT, CP_EDGE_TOP, CP_EDGE_BOTTOM
+ switch(edge)
+ {
+ case CP_EDGE_LEFT:
+ // no change to faceEdgeStartPtr
+ edgeWalk = nChannels * size;
+ edgePerpWalk = nChannels;
+ break;
+ case CP_EDGE_RIGHT:
+ edgeStartPtr += (size - 1) * nChannels;
+ edgeWalk = nChannels * size;
+ edgePerpWalk = -nChannels;
+ break;
+ case CP_EDGE_TOP:
+ // no change to faceEdgeStartPtr
+ edgeWalk = nChannels;
+ edgePerpWalk = nChannels * size;
+ break;
+ case CP_EDGE_BOTTOM:
+ edgeStartPtr += (size) * (size - 1) * nChannels;
+ edgeWalk = nChannels;
+ edgePerpWalk = -(nChannels * size);
+ break;
+ }
+
+ //For certain types of edge abutments, the neighbor edge walk needs to
+ // be flipped: the cases are
+ // if a left edge mates with a left or bottom edge on the neighbor
+ // if a top edge mates with a top or right edge on the neighbor
+ // if a right edge mates with a right or top edge on the neighbor
+ // if a bottom edge mates with a bottom or left edge on the neighbor
+ //Seeing as the edges are enumerated as follows
+ // left =0
+ // right =1
+ // top =2
+ // bottom =3
+ //
+ //If the edge enums are the same, or the sum of the enums == 3,
+ // the neighbor edge walk needs to be flipped
+ if( (edge == neighborEdge) || ((edge + neighborEdge) == 3) )
+ { //swapped direction neighbor edge walk
+ switch(neighborEdge)
+ {
+ case CP_EDGE_LEFT: //start at lower left and walk up
+ neighborEdgeStartPtr += (size - 1) * (size) * nChannels;
+ neighborEdgeWalk = -(nChannels * size);
+ neighborEdgePerpWalk = nChannels;
+ break;
+ case CP_EDGE_RIGHT: //start at lower right and walk up
+ neighborEdgeStartPtr += ((size - 1)*(size) + (size - 1)) * nChannels;
+ neighborEdgeWalk = -(nChannels * size);
+ neighborEdgePerpWalk = -nChannels;
+ break;
+ case CP_EDGE_TOP: //start at upper right and walk left
+ neighborEdgeStartPtr += (size - 1) * nChannels;
+ neighborEdgeWalk = -nChannels;
+ neighborEdgePerpWalk = (nChannels * size);
+ break;
+ case CP_EDGE_BOTTOM: //start at lower right and walk left
+ neighborEdgeStartPtr += ((size - 1)*(size) + (size - 1)) * nChannels;
+ neighborEdgeWalk = -nChannels;
+ neighborEdgePerpWalk = -(nChannels * size);
+ break;
+ }
+ }
+ else
+ { //swapped direction neighbor edge walk
+ switch(neighborEdge)
+ {
+ case CP_EDGE_LEFT: //start at upper left and walk down
+ //no change to neighborEdgeStartPtr for this case since it points
+ // to the upper left corner already
+ neighborEdgeWalk = nChannels * size;
+ neighborEdgePerpWalk = nChannels;
+ break;
+ case CP_EDGE_RIGHT: //start at upper right and walk down
+ neighborEdgeStartPtr += (size - 1) * nChannels;
+ neighborEdgeWalk = nChannels * size;
+ neighborEdgePerpWalk = -nChannels;
+ break;
+ case CP_EDGE_TOP: //start at upper left and walk left
+ //no change to neighborEdgeStartPtr for this case since it points
+ // to the upper left corner already
+ neighborEdgeWalk = nChannels;
+ neighborEdgePerpWalk = (nChannels * size);
+ break;
+ case CP_EDGE_BOTTOM: //start at lower left and walk left
+ neighborEdgeStartPtr += (size) * (size - 1) * nChannels;
+ neighborEdgeWalk = nChannels;
+ neighborEdgePerpWalk = -(nChannels * size);
+ break;
+ }
+ }
+
+
+ //Perform edge walk, to average across the 12 edges and smoothly propagate change to
+ //nearby neighborhood
+
+ //step ahead one texel on edge
+ edgeStartPtr += edgeWalk;
+ neighborEdgeStartPtr += neighborEdgeWalk;
+
+ // note that this loop does not process the corner texels, since they have already been
+ // averaged across faces across earlier
+ for(j=1; j<(size - 1); j++)
+ {
+ //for each set of taps along edge, average them
+ // and rewrite the results into the edges
+ for(k = 0; k<nChannels; k++)
+ {
+ CP_ITYPE edgeTap, neighborEdgeTap, avgTap; //edge tap, neighborEdgeTap and the average of the two
+ CP_ITYPE edgeTapDev, neighborEdgeTapDev;
+
+ edgeTap = *(edgeStartPtr + k);
+ neighborEdgeTap = *(neighborEdgeStartPtr + k);
+
+ //compute average of tap intensity values
+ avgTap = 0.5f * (edgeTap + neighborEdgeTap);
+
+ //propagate average of taps to edge taps
+ (*(edgeStartPtr + k)) = avgTap;
+ (*(neighborEdgeStartPtr + k)) = avgTap;
+
+ edgeTapDev = edgeTap - avgTap;
+ neighborEdgeTapDev = neighborEdgeTap - avgTap;
+
+ //iterate over taps in direction perpendicular to edge, and
+ // adjust intensity values gradualy to obscure change in intensity values of
+ // edge averaging.
+ for(iFixup = 1; iFixup < fixupDist; iFixup++)
+ {
+ //fractional amount to apply change in tap intensity along edge to taps
+ // in a perpendicular direction to edge
+ CP_ITYPE fixupFrac = (CP_ITYPE)(fixupDist - iFixup) / (CP_ITYPE)(fixupDist);
+ CP_ITYPE fixupWeight;
+
+ switch(a_FixupType )
+ {
+ case CP_FIXUP_PULL_LINEAR:
+ {
+ fixupWeight = fixupFrac;
+ }
+ break;
+ case CP_FIXUP_PULL_HERMITE:
+ {
+ //hermite spline interpolation between 1 and 0 with both pts derivatives = 0
+ // e.g. smooth step
+ // the full formula for hermite interpolation is:
+ //
+ // [ 2 -2 1 1 ][ p0 ]
+ // [t^3 t^2 t 1 ][ -3 3 -2 -1 ][ p1 ]
+ // [ 0 0 1 0 ][ d0 ]
+ // [ 1 0 0 0 ][ d1 ]
+ //
+ // Where p0 and p1 are the point locations and d0, and d1 are their respective derivatives
+ // t is the parameteric coordinate used to specify an interpoltion point on the spline
+ // and ranges from 0 to 1.
+ // if p0 = 0 and p1 = 1, and d0 and d1 = 0, the interpolation reduces to
+ //
+ // p(t) = - 2t^3 + 3t^2
+ fixupWeight = ((-2.0 * fixupFrac + 3.0) * fixupFrac * fixupFrac);
+ }
+ break;
+ case CP_FIXUP_AVERAGE_LINEAR:
+ {
+ fixupWeight = fixupFrac;
+
+ //perform weighted average of edge tap value and current tap
+ // fade off weight linearly as a function of distance from edge
+ edgeTapDev =
+ (*(edgeStartPtr + (iFixup * edgePerpWalk) + k)) - avgTap;
+ neighborEdgeTapDev =
+ (*(neighborEdgeStartPtr + (iFixup * neighborEdgePerpWalk) + k)) - avgTap;
+ }
+ break;
+ case CP_FIXUP_AVERAGE_HERMITE:
+ {
+ fixupWeight = ((-2.0 * fixupFrac + 3.0) * fixupFrac * fixupFrac);
+
+ //perform weighted average of edge tap value and current tap
+ // fade off weight using hermite spline with distance from edge
+ // as parametric coordinate
+ edgeTapDev =
+ (*(edgeStartPtr + (iFixup * edgePerpWalk) + k)) - avgTap;
+ neighborEdgeTapDev =
+ (*(neighborEdgeStartPtr + (iFixup * neighborEdgePerpWalk) + k)) - avgTap;
+ }
+ break;
+ }
+
+ // vary intensity of taps within fixup region toward edge values to hide changes made to edge taps
+ *(edgeStartPtr + (iFixup * edgePerpWalk) + k) -= (fixupWeight * edgeTapDev);
+ *(neighborEdgeStartPtr + (iFixup * neighborEdgePerpWalk) + k) -= (fixupWeight * neighborEdgeTapDev);
+ }
+
+ }
+
+ edgeStartPtr += edgeWalk;
+ neighborEdgeStartPtr += neighborEdgeWalk;
+ }
+ }
+}
diff --git a/Runtime/Graphics/CubemapProcessor.h b/Runtime/Graphics/CubemapProcessor.h
new file mode 100644
index 0000000..b824e8d
--- /dev/null
+++ b/Runtime/Graphics/CubemapProcessor.h
@@ -0,0 +1,35 @@
+#ifndef CUBEMAPPROCESSOR_H
+#define CUBEMAPPROCESSOR_H
+
+//--------------------------------------------------------------------------------------
+// Based on CCubeMapProcessor from CubeMapGen v1.4
+// http://developer.amd.com/archive/gpu/cubemapgen/Pages/default.aspx#download
+// Class for filtering and processing cubemaps
+//
+//
+//--------------------------------------------------------------------------------------
+// (C) 2005 ATI Research, Inc., All rights reserved.
+//--------------------------------------------------------------------------------------
+
+
+#define CP_ITYPE float
+
+struct CImageSurface
+{
+ int m_Width; //image width
+ int m_Height; //image height
+ int m_NumChannels; //number of channels
+ CP_ITYPE *m_ImgData; //cubemap image data
+};
+
+// Edge fixup type (how to perform smoothing near edge region)
+#define CP_FIXUP_NONE 0
+#define CP_FIXUP_PULL_LINEAR 1
+#define CP_FIXUP_PULL_HERMITE 2
+#define CP_FIXUP_AVERAGE_LINEAR 3
+#define CP_FIXUP_AVERAGE_HERMITE 4
+
+
+void FixupCubeEdges (CImageSurface *a_CubeMap, int a_FixupType, int a_FixupWidth);
+
+#endif
diff --git a/Runtime/Graphics/CubemapTexture.cpp b/Runtime/Graphics/CubemapTexture.cpp
new file mode 100644
index 0000000..4fe2d7e
--- /dev/null
+++ b/Runtime/Graphics/CubemapTexture.cpp
@@ -0,0 +1,186 @@
+#include "UnityPrefix.h"
+#include "CubemapTexture.h"
+#include "Runtime/Graphics/CubemapProcessor.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+using namespace std;
+
+Cubemap::Cubemap (MemLabelId label, ObjectCreationMode mode)
+: Super (label, mode)
+{
+ m_SourceTextures.resize (6);
+}
+
+Cubemap::~Cubemap ()
+{
+}
+
+bool Cubemap::InitTexture (int width, int height, TextureFormat format, int flags, int imageCount)
+{
+ Assert (imageCount == 6);
+ Assert (width == height);
+ if( !IsPowerOfTwo (width) || !IsPowerOfTwo (height) )
+ {
+ ErrorStringObject ("Texture has non-power of two size", this);
+ return false;
+ }
+ if( width != height )
+ {
+ ErrorStringObject ("Cubemap faces must be square", this);
+ return false;
+ }
+ SetDirty();
+ return Super::InitTexture (width, width, format, flags, 6);
+}
+
+
+void Cubemap::UploadTexture (bool dontUseSubImage)
+{
+ Assert (GetRawImageData());
+
+ ErrorIf (GetGLWidth() != GetGLHeight() || m_ImageCount != 6);
+ int faceDataSize = GetRawImageData(1)-GetRawImageData(0);
+ int dataSize = faceDataSize * 6;
+ UInt32 uploadFlags = (dontUseSubImage || !m_TextureUploaded) ? GfxDevice::kUploadTextureDontUseSubImage : GfxDevice::kUploadTextureDefault;
+ GetGfxDevice().UploadTextureCube( GetTextureID(), GetRawImageData(), dataSize, faceDataSize, GetGLWidth(), GetTextureFormat(), CountMipmaps(), uploadFlags, GetActiveTextureColorSpace() );
+ Texture::s_TextureIDMap.insert (std::make_pair(GetTextureID(),this));
+
+ m_TextureSettings.m_WrapMode = kTexWrapClamp;
+ ApplySettings();
+ m_TextureUploaded = true;
+
+#if !UNITY_EDITOR
+ if(!m_IsReadable)
+ {
+ DeallocateTextureData (m_TexData.data);
+ m_TexData.data = 0;
+ m_TexData.imageSize = 0;
+ }
+#endif
+}
+
+static void ConvertImageToFloatArray (float* dst, ImageReference const& src)
+{
+ ColorRGBAf* dstRGBA = (ColorRGBAf*)dst;
+ const int width = src.GetWidth ();
+ const int height = src.GetHeight ();
+ for (int y = 0; y < width; ++y)
+ for (int x = 0; x < height; ++x)
+ {
+ *dstRGBA++ = GetImagePixel (src.GetImageData (), width, height, src.GetFormat (), kTexWrapClamp, x, y);
+ }
+}
+
+static void ConvertFloatArrayToImage (ImageReference& dst, float const* src)
+{
+ ColorRGBAf const* srcRGBA = (ColorRGBAf const*)src;
+ const int width = dst.GetWidth ();
+ const int height = dst.GetHeight ();
+ for (int y = 0; y < width; ++y)
+ for (int x = 0; x < height; ++x)
+ {
+ SetImagePixel (dst, x, y, kTexWrapClamp, *srcRGBA++);
+ }
+}
+
+void Cubemap::RebuildMipMap ()
+{
+ TextureRepresentation& rep = m_TexData;
+
+ if (rep.data == NULL || !m_MipMap)
+ return;
+
+ if (IsAnyCompressedTextureFormat(rep.format))
+ {
+ ErrorStringObject ("Rebuilding mipmaps of compressed textures is not supported", this);
+ return;
+ }
+ if (m_ImageCount != 6)
+ {
+ ErrorStringObject ("Cubemap must have 6 faces", this);
+ return;
+ }
+
+ Assert (rep.width == rep.height);
+ const int edge = rep.width;
+ for (int i = 0; i < 6; i++)
+ CreateMipMap (rep.data + rep.imageSize * i, edge, edge, 1, rep.format);
+}
+
+
+void Cubemap::FixupEdges (int fixupWidthInPixels)
+{
+ TextureRepresentation& rep = m_TexData;
+
+ if (rep.data == NULL || !m_MipMap)
+ return;
+
+ const int edge = rep.width;
+ const int channels = 4; CImageSurface cubemapSurfaces[6];
+ for (int q = 0; q < 6; ++q)
+ {
+ cubemapSurfaces[q].m_ImgData = (float*)UNITY_MALLOC (kMemDefault, edge * edge * channels * sizeof(CP_ITYPE));
+ }
+
+ int mipEdge = edge;
+ for (int mip = 0; mip < CountMipmaps (); ++mip)
+ {
+ ImageReference mipImages[6];
+
+ for (int face = 0; face < 6; ++face)
+ {
+ if (!GetWriteImageReference (&mipImages[face], face, mip))
+ {
+ ErrorStringObject ("Can't draw into cubemap", this);
+ return;
+ }
+ ConvertImageToFloatArray (cubemapSurfaces[face].m_ImgData, mipImages[face]);
+ cubemapSurfaces[face].m_Width = cubemapSurfaces[face].m_Height = mipEdge;
+ cubemapSurfaces[face].m_NumChannels = channels;
+ }
+
+ FixupCubeEdges (cubemapSurfaces, CP_FIXUP_AVERAGE_HERMITE, fixupWidthInPixels);
+
+ for (int face = 0; face < 6; ++face)
+ {
+ ConvertFloatArrayToImage (mipImages[face], cubemapSurfaces[face].m_ImgData);
+ }
+
+ mipEdge = max(mipEdge/2, 1);
+ }
+
+ for (int q = 0; q < 6; ++q)
+ {
+ UNITY_FREE (kMemDefault, cubemapSurfaces[q].m_ImgData);
+ }
+}
+
+void Cubemap::SetSourceTexture (CubemapFace face, PPtr<Texture2D> tex)
+{
+ Assert (face < m_SourceTextures.size ());
+
+ m_SourceTextures[face] = tex;
+}
+
+PPtr<Texture2D> Cubemap::GetSourceTexture (CubemapFace face) const
+{
+ Assert (face < m_SourceTextures.size ());
+
+ return m_SourceTextures[face];
+}
+
+template<class TransferFunction>
+void Cubemap::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.Transfer (m_SourceTextures, "m_SourceTextures");
+ transfer.Align ();
+}
+
+IMPLEMENT_CLASS (Cubemap)
+IMPLEMENT_OBJECT_SERIALIZE (Cubemap)
+
diff --git a/Runtime/Graphics/CubemapTexture.h b/Runtime/Graphics/CubemapTexture.h
new file mode 100644
index 0000000..6934926
--- /dev/null
+++ b/Runtime/Graphics/CubemapTexture.h
@@ -0,0 +1,30 @@
+#ifndef CUBEMAPTEXTURE_H
+#define CUBEMAPTEXTURE_H
+
+#include "Texture2D.h"
+
+
+class Cubemap : public Texture2D
+{
+public:
+ REGISTER_DERIVED_CLASS (Cubemap, Texture2D)
+ DECLARE_OBJECT_SERIALIZE (Cubemap)
+
+ Cubemap (MemLabelId label, ObjectCreationMode mode);
+
+ virtual bool InitTexture (int width, int height, TextureFormat format, int flags = kMipmapMask, int imageCount = 1);
+ virtual TextureDimension GetDimension () const { return kTexDimCUBE; }
+ virtual void UploadTexture (bool dontUseSubImage);
+
+ virtual void RebuildMipMap ();
+
+ void FixupEdges (int fixupWidthInPixels = 1);
+
+ void SetSourceTexture (CubemapFace face, PPtr<Texture2D> tex);
+ PPtr<Texture2D> GetSourceTexture (CubemapFace face) const;
+
+private:
+ std::vector<PPtr<Texture2D> > m_SourceTextures;
+};
+
+#endif
diff --git a/Runtime/Graphics/DXTCompression.cpp b/Runtime/Graphics/DXTCompression.cpp
new file mode 100644
index 0000000..b580a32
--- /dev/null
+++ b/Runtime/Graphics/DXTCompression.cpp
@@ -0,0 +1,595 @@
+// Fast DXT compression code adapted from
+// stb_dxt by Sean Barrett: http://nothings.org/stb/stb_dxt.h
+// which is in turn adapted from code by
+// Fabian "ryg" Giesen: http://www.farbrausch.de/~fg/code/dxt/
+
+#include "UnityPrefix.h"
+#include "DXTCompression.h"
+
+// stb_dxt.h - v1.01 - DXT1/DXT5 compressor - public domain
+// original by fabian "ryg" giesen - ported to C by stb
+//
+// version history:
+// v1.xx - fix for big endian archs (Aras)
+// v1.02 - fix alpha encoding bug
+// v1.01 - fix endianness bug that messed up quality, thanks ryg & cbloom
+// v1.00 - first release
+
+#if UNITY_BIG_ENDIAN
+#define STB_BYTESWAP_16(i) ((i<<8)|(i>>8))
+#define STB_BYTESWAP_32(i) ((i>>24) | (i>>8)&0x0000ff00 | (i<<8)&0x00ff0000 | (i<<24))
+#else
+#define STB_BYTESWAP_16(i) (i)
+#define STB_BYTESWAP_32(i) (i)
+#endif
+
+
+static unsigned char stb__Expand5[32];
+static unsigned char stb__Expand6[64];
+static unsigned char stb__OMatch5[256][2];
+static unsigned char stb__OMatch6[256][2];
+static unsigned char stb__QuantRBTab[256+16];
+static unsigned char stb__QuantGTab[256+16];
+
+static int stb__Mul8Bit(int a,int b)
+{
+ int t = a*b + 128;
+ return (t + (t >> 8)) >> 8;
+}
+
+static void stb__From16Bit(unsigned char *out, unsigned short v)
+{
+ int rv = (v & 0xf800) >> 11;
+ int gv = (v & 0x07e0) >> 5;
+ int bv = (v & 0x001f) >> 0;
+
+ out[0] = stb__Expand5[rv];
+ out[1] = stb__Expand6[gv];
+ out[2] = stb__Expand5[bv];
+ out[3] = 0;
+}
+
+static unsigned short stb__As16Bit(int r, int g, int b)
+{
+ return (stb__Mul8Bit(r,31) << 11) + (stb__Mul8Bit(g,63) << 5) + stb__Mul8Bit(b,31);
+}
+
+static void stb__LerpRGB(unsigned char *out, unsigned char *p1, unsigned char *p2, int f)
+{
+ out[0] = p1[0] + stb__Mul8Bit(p2[0] - p1[0],f);
+ out[1] = p1[1] + stb__Mul8Bit(p2[1] - p1[1],f);
+ out[2] = p1[2] + stb__Mul8Bit(p2[2] - p1[2],f);
+}
+
+/****************************************************************************/
+
+// compute table to reproduce constant colors as accurately as possible
+static void stb__PrepareOptTable(unsigned char *Table,const unsigned char *expand,int size)
+{
+ int i,mn,mx;
+ for (i=0;i<256;i++) {
+ int bestErr = 256;
+ for (mn=0;mn<size;mn++) {
+ for (mx=0;mx<size;mx++) {
+ int mine = expand[mn];
+ int maxe = expand[mx];
+ int err = abs(maxe + stb__Mul8Bit(mine-maxe,0x55) - i);
+
+ if(err < bestErr)
+ {
+ Table[i*2+0] = mx;
+ Table[i*2+1] = mn;
+ bestErr = err;
+ }
+ }
+ }
+ }
+}
+
+static void stb__EvalColors(unsigned char *color,unsigned short c0,unsigned short c1)
+{
+ stb__From16Bit(color+ 0, c0);
+ stb__From16Bit(color+ 4, c1);
+ stb__LerpRGB (color+ 8, color+0,color+4, 0x55);
+ stb__LerpRGB (color+12, color+0,color+4, 0xaa);
+}
+
+// Block dithering function. Simply dithers a block to 565 RGB.
+// (Floyd-Steinberg)
+static void stb__DitherBlock(unsigned char *dest, unsigned char *block)
+{
+ int err[8],*ep1 = err,*ep2 = err+4, *et;
+ int ch,y;
+
+ // process channels seperately
+ for (ch=0; ch<3; ++ch) {
+ unsigned char *bp = block+ch, *dp = dest+ch;
+ unsigned char *quant = (ch == 1) ? stb__QuantGTab+8 : stb__QuantRBTab+8;
+ memset(err, 0, sizeof(err));
+ for(y=0; y<4; ++y) {
+ dp[ 0] = quant[bp[ 0] + ((3*ep2[1] + 5*ep2[0]) >> 4)];
+ ep1[0] = bp[ 0] - dp[ 0];
+ dp[ 4] = quant[bp[ 4] + ((7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]) >> 4)];
+ ep1[1] = bp[ 4] - dp[ 4];
+ dp[ 8] = quant[bp[ 8] + ((7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]) >> 4)];
+ ep1[2] = bp[ 8] - dp[ 8];
+ dp[12] = quant[bp[12] + ((7*ep1[2] + 5*ep2[3] + ep2[2]) >> 4)];
+ ep1[3] = bp[12] - dp[12];
+ bp += 16;
+ dp += 16;
+ et = ep1, ep1 = ep2, ep2 = et; // swap
+ }
+ }
+}
+
+// The color matching function
+static unsigned int stb__MatchColorsBlock(unsigned char *block, unsigned char *color,int dither)
+{
+ unsigned int mask = 0;
+ int dirr = color[0*4+0] - color[1*4+0];
+ int dirg = color[0*4+1] - color[1*4+1];
+ int dirb = color[0*4+2] - color[1*4+2];
+ int dots[16];
+ int stops[4];
+ int i;
+ int c0Point, halfPoint, c3Point;
+
+ for(i=0;i<16;i++)
+ dots[i] = block[i*4+0]*dirr + block[i*4+1]*dirg + block[i*4+2]*dirb;
+
+ for(i=0;i<4;i++)
+ stops[i] = color[i*4+0]*dirr + color[i*4+1]*dirg + color[i*4+2]*dirb;
+
+ c0Point = (stops[1] + stops[3]) >> 1;
+ halfPoint = (stops[3] + stops[2]) >> 1;
+ c3Point = (stops[2] + stops[0]) >> 1;
+
+ if(!dither) {
+ // the version without dithering is straightforward
+ for (i=15;i>=0;i--) {
+ int dot = dots[i];
+ mask <<= 2;
+
+ if(dot < halfPoint)
+ mask |= (dot < c0Point) ? 1 : 3;
+ else
+ mask |= (dot < c3Point) ? 2 : 0;
+ }
+ } else {
+ // with floyd-steinberg dithering
+ int err[8],*ep1 = err,*ep2 = err+4;
+ int *dp = dots, y;
+
+ c0Point <<= 4;
+ halfPoint <<= 4;
+ c3Point <<= 4;
+ for(i=0;i<8;i++)
+ err[i] = 0;
+
+ for(y=0;y<4;y++)
+ {
+ int dot,lmask,step;
+
+ dot = (dp[0] << 4) + (3*ep2[1] + 5*ep2[0]);
+ if(dot < halfPoint)
+ step = (dot < c0Point) ? 1 : 3;
+ else
+ step = (dot < c3Point) ? 2 : 0;
+ ep1[0] = dp[0] - stops[step];
+ lmask = step;
+
+ dot = (dp[1] << 4) + (7*ep1[0] + 3*ep2[2] + 5*ep2[1] + ep2[0]);
+ if(dot < halfPoint)
+ step = (dot < c0Point) ? 1 : 3;
+ else
+ step = (dot < c3Point) ? 2 : 0;
+ ep1[1] = dp[1] - stops[step];
+ lmask |= step<<2;
+
+ dot = (dp[2] << 4) + (7*ep1[1] + 3*ep2[3] + 5*ep2[2] + ep2[1]);
+ if(dot < halfPoint)
+ step = (dot < c0Point) ? 1 : 3;
+ else
+ step = (dot < c3Point) ? 2 : 0;
+ ep1[2] = dp[2] - stops[step];
+ lmask |= step<<4;
+
+ dot = (dp[3] << 4) + (7*ep1[2] + 5*ep2[3] + ep2[2]);
+ if(dot < halfPoint)
+ step = (dot < c0Point) ? 1 : 3;
+ else
+ step = (dot < c3Point) ? 2 : 0;
+ ep1[3] = dp[3] - stops[step];
+ lmask |= step<<6;
+
+ dp += 4;
+ mask |= lmask << (y*8);
+ { int *et = ep1; ep1 = ep2; ep2 = et; } // swap
+ }
+ }
+
+ return mask;
+}
+
+// The color optimization function. (Clever code, part 1)
+static void stb__OptimizeColorsBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16)
+{
+ int mind = 0x7fffffff,maxd = -0x7fffffff;
+ unsigned char *minp = NULL, *maxp = NULL;
+ double magn;
+ int v_r,v_g,v_b;
+ static const int nIterPower = 4;
+ float covf[6],vfr,vfg,vfb;
+
+ // determine color distribution
+ int cov[6];
+ int mu[3],min[3],max[3];
+ int ch,i,iter;
+
+ for(ch=0;ch<3;ch++)
+ {
+ const unsigned char *bp = ((const unsigned char *) block) + ch;
+ int muv,minv,maxv;
+
+ muv = minv = maxv = bp[0];
+ for(i=4;i<64;i+=4)
+ {
+ muv += bp[i];
+ if (bp[i] < minv) minv = bp[i];
+ else if (bp[i] > maxv) maxv = bp[i];
+ }
+
+ mu[ch] = (muv + 8) >> 4;
+ min[ch] = minv;
+ max[ch] = maxv;
+ }
+
+ // determine covariance matrix
+ for (i=0;i<6;i++)
+ cov[i] = 0;
+
+ for (i=0;i<16;i++)
+ {
+ int r = block[i*4+0] - mu[0];
+ int g = block[i*4+1] - mu[1];
+ int b = block[i*4+2] - mu[2];
+
+ cov[0] += r*r;
+ cov[1] += r*g;
+ cov[2] += r*b;
+ cov[3] += g*g;
+ cov[4] += g*b;
+ cov[5] += b*b;
+ }
+
+ // convert covariance matrix to float, find principal axis via power iter
+ for(i=0;i<6;i++)
+ covf[i] = cov[i] / 255.0f;
+
+ vfr = (float) (max[0] - min[0]);
+ vfg = (float) (max[1] - min[1]);
+ vfb = (float) (max[2] - min[2]);
+
+ for(iter=0;iter<nIterPower;iter++)
+ {
+ float r = vfr*covf[0] + vfg*covf[1] + vfb*covf[2];
+ float g = vfr*covf[1] + vfg*covf[3] + vfb*covf[4];
+ float b = vfr*covf[2] + vfg*covf[4] + vfb*covf[5];
+
+ vfr = r;
+ vfg = g;
+ vfb = b;
+ }
+
+ magn = fabs(vfr);
+ if (fabs(vfg) > magn) magn = fabs(vfg);
+ if (fabs(vfb) > magn) magn = fabs(vfb);
+
+ if(magn < 4.0f) { // too small, default to luminance
+ v_r = 148;
+ v_g = 300;
+ v_b = 58;
+ } else {
+ magn = 512.0 / magn;
+ v_r = (int) (vfr * magn);
+ v_g = (int) (vfg * magn);
+ v_b = (int) (vfb * magn);
+ }
+
+ // Pick colors at extreme points
+ for(i=0;i<16;i++)
+ {
+ int dot = block[i*4+0]*v_r + block[i*4+1]*v_g + block[i*4+2]*v_b;
+
+ if (dot < mind) {
+ mind = dot;
+ minp = block+i*4;
+ }
+
+ if (dot > maxd) {
+ maxd = dot;
+ maxp = block+i*4;
+ }
+ }
+
+ *pmax16 = stb__As16Bit(maxp[0],maxp[1],maxp[2]);
+ *pmin16 = stb__As16Bit(minp[0],minp[1],minp[2]);
+}
+
+static int stb__sclamp(float y, int p0, int p1)
+{
+ int x = (int) y;
+ if (x < p0) return p0;
+ if (x > p1) return p1;
+ return x;
+}
+
+// The refinement function. (Clever code, part 2)
+// Tries to optimize colors to suit block contents better.
+// (By solving a least squares system via normal equations+Cramer's rule)
+static int stb__RefineBlock(unsigned char *block, unsigned short *pmax16, unsigned short *pmin16, unsigned int mask)
+{
+ static const int w1Tab[4] = { 3,0,2,1 };
+ static const int prods[4] = { 0x090000,0x000900,0x040102,0x010402 };
+ // ^some magic to save a lot of multiplies in the accumulating loop...
+
+ float frb,fg;
+ unsigned short oldMin, oldMax, min16, max16;
+ int i, akku = 0, xx,xy,yy;
+ int At1_r,At1_g,At1_b;
+ int At2_r,At2_g,At2_b;
+ unsigned int cm = mask;
+
+ oldMin = *pmin16;
+ oldMax = *pmax16;
+
+ if((mask ^ (mask<<2)) < 4) // all pixels have the same index?
+ {
+ // yes, linear system would be singular; solve using optimal
+ // single-color match on average color
+ int r = 8, g = 8, b = 8;
+ for (i=0;i<16;++i) {
+ r += block[i*4+0];
+ g += block[i*4+1];
+ b += block[i*4+2];
+ }
+
+ r >>= 4; g >>= 4; b >>= 4;
+
+ max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0];
+ min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1];
+ } else {
+ At1_r = At1_g = At1_b = 0;
+ At2_r = At2_g = At2_b = 0;
+ for (i=0;i<16;++i,cm>>=2) {
+ int step = cm&3;
+ int w1 = w1Tab[step];
+ int r = block[i*4+0];
+ int g = block[i*4+1];
+ int b = block[i*4+2];
+
+ akku += prods[step];
+ At1_r += w1*r;
+ At1_g += w1*g;
+ At1_b += w1*b;
+ At2_r += r;
+ At2_g += g;
+ At2_b += b;
+ }
+
+ At2_r = 3*At2_r - At1_r;
+ At2_g = 3*At2_g - At1_g;
+ At2_b = 3*At2_b - At1_b;
+
+ // extract solutions and decide solvability
+ xx = akku >> 16;
+ yy = (akku >> 8) & 0xff;
+ xy = (akku >> 0) & 0xff;
+
+ frb = 3.0f * 31.0f / 255.0f / (xx*yy - xy*xy);
+ fg = frb * 63.0f / 31.0f;
+
+ // solve.
+ max16 = stb__sclamp((At1_r*yy - At2_r*xy)*frb+0.5f,0,31) << 11;
+ max16 |= stb__sclamp((At1_g*yy - At2_g*xy)*fg +0.5f,0,63) << 5;
+ max16 |= stb__sclamp((At1_b*yy - At2_b*xy)*frb+0.5f,0,31) << 0;
+
+ min16 = stb__sclamp((At2_r*xx - At1_r*xy)*frb+0.5f,0,31) << 11;
+ min16 |= stb__sclamp((At2_g*xx - At1_g*xy)*fg +0.5f,0,63) << 5;
+ min16 |= stb__sclamp((At2_b*xx - At1_b*xy)*frb+0.5f,0,31) << 0;
+ }
+
+ *pmin16 = min16;
+ *pmax16 = max16;
+ return oldMin != min16 || oldMax != max16;
+}
+
+// Color block compression
+static void stb__CompressColorBlock(unsigned char *dest, unsigned char *block,int quality)
+{
+ unsigned int mask;
+ int i;
+ unsigned short max16, min16;
+ unsigned char dblock[16*4],color[4*4];
+
+ // check if block is constant
+ for (i=1;i<16;i++)
+ if (((unsigned int *) block)[i] != ((unsigned int *) block)[0])
+ break;
+
+ if(i == 16) { // constant color
+ int r = block[0], g = block[1], b = block[2];
+ mask = 0xaaaaaaaa;
+ max16 = (stb__OMatch5[r][0]<<11) | (stb__OMatch6[g][0]<<5) | stb__OMatch5[b][0];
+ min16 = (stb__OMatch5[r][1]<<11) | (stb__OMatch6[g][1]<<5) | stb__OMatch5[b][1];
+ } else {
+ // first step: compute dithered version for PCA if desired
+ if(quality)
+ stb__DitherBlock(dblock,block);
+
+ // second step: pca+map along principal axis
+ stb__OptimizeColorsBlock(quality ? dblock : block,&max16,&min16);
+ if (max16 != min16) {
+ stb__EvalColors(color,max16,min16);
+ mask = stb__MatchColorsBlock(block,color,quality != 0);
+ } else
+ mask = 0;
+
+ // third step: refine
+ if (stb__RefineBlock(quality ? dblock : block,&max16,&min16,mask)) {
+ if (max16 != min16) {
+ stb__EvalColors(color,max16,min16);
+ mask = stb__MatchColorsBlock(block,color,quality != 0);
+ } else
+ mask = 0;
+ }
+ }
+
+ // write the color block
+ if(max16 < min16)
+ {
+ unsigned short t = min16;
+ min16 = max16;
+ max16 = t;
+ mask ^= 0x55555555;
+ }
+
+ ((unsigned short *) dest)[0] = STB_BYTESWAP_16(max16);
+ ((unsigned short *) dest)[1] = STB_BYTESWAP_16(min16);
+ ((unsigned int *) dest)[1] = STB_BYTESWAP_32(mask);
+}
+
+// Alpha block compression (this is easy for a change)
+static void stb__CompressAlphaBlock(unsigned char *dest,unsigned char *src,int quality)
+{
+ int i,dist,bias,dist4,dist2,bits,mask;
+
+ // find min/max color
+ int mn,mx;
+ mn = mx = src[3];
+
+ for (i=1;i<16;i++)
+ {
+ if (src[i*4+3] < mn) mn = src[i*4+3];
+ else if (src[i*4+3] > mx) mx = src[i*4+3];
+ }
+
+ // encode them
+ ((unsigned char *)dest)[0] = mx;
+ ((unsigned char *)dest)[1] = mn;
+ dest += 2;
+
+ // determine bias and emit color indices
+ dist = mx-mn;
+ bias = mn*7 - (dist >> 1);
+ dist4 = dist*4;
+ dist2 = dist*2;
+ bits = 0,mask=0;
+
+ for (i=0;i<16;i++) {
+ int a = src[i*4+3]*7 - bias;
+ int ind,t;
+
+ // select index (hooray for bit magic)
+ t = (dist4 - a) >> 31; ind = t & 4; a -= dist4 & t;
+ t = (dist2 - a) >> 31; ind += t & 2; a -= dist2 & t;
+ t = (dist - a) >> 31; ind += t & 1;
+
+ ind = -ind & 7;
+ ind ^= (2 > ind);
+
+ // write index
+ mask |= ind << bits;
+ if((bits += 3) >= 8) {
+ *dest++ = mask;
+ mask >>= 8;
+ bits -= 8;
+ }
+ }
+}
+
+static void stb__InitDXT()
+{
+ int i;
+ for(i=0;i<32;i++)
+ stb__Expand5[i] = (i<<3)|(i>>2);
+
+ for(i=0;i<64;i++)
+ stb__Expand6[i] = (i<<2)|(i>>4);
+
+ for(i=0;i<256+16;i++)
+ {
+ int v = i-8 < 0 ? 0 : i-8 > 255 ? 255 : i-8;
+ stb__QuantRBTab[i] = stb__Expand5[stb__Mul8Bit(v,31)];
+ stb__QuantGTab[i] = stb__Expand6[stb__Mul8Bit(v,63)];
+ }
+
+ stb__PrepareOptTable(&stb__OMatch5[0][0],stb__Expand5,32);
+ stb__PrepareOptTable(&stb__OMatch6[0][0],stb__Expand6,64);
+}
+
+// input: a 4x4 pixel block, r,g,b,a bytes per pixel
+// alpha: true = DXT5, false = DXT1
+// quality: true = slower, with dither; false = faster, no dither
+static void FastCompressDXTBlock( UInt8* dest, const UInt8* src, bool alpha, bool quality)
+{
+ static int init=0;
+ if (!init) {
+ stb__InitDXT();
+ init=1;
+ }
+
+ if (alpha) {
+ stb__CompressAlphaBlock(dest,(unsigned char*) src,quality);
+ dest += 8;
+ }
+
+ stb__CompressColorBlock(dest,(unsigned char*) src,quality);
+}
+
+void FastCompressImage (int width, int height, const UInt8* src, UInt8* dest, bool dxt5, bool dither )
+{
+ // adapted from stbgl__compress: http://nothings.org/stb/stb_gl.h
+
+ const UInt8* destEnd = dest + ((width+3)/4)*((height+3)/4)*(dxt5?16:8);
+
+ for (int y=0; y < height; y += 4) {
+ for (int x=0; x < width; x += 4) {
+ UInt8 block[16*4];
+ int xleft=4;
+ if (x+3 >= width) xleft = width-x;
+ int yleft;
+ for (yleft=0; yleft < 4; ++yleft) {
+ if (y+yleft >= height) break;
+ memcpy(block+yleft*16, src + width*4*(y+yleft) + x*4, xleft*4);
+ }
+ if (xleft < 4) {
+ switch (xleft) {
+ case 0: AssertString("should not happen");
+ case 1:
+ for (int yy=0; yy < yleft; ++yy) {
+ memcpy(block+yy*16+1*4, block+yy*16+0*4, 4);
+ memcpy(block+yy*16+2*4, block+yy*16+0*4, 8);
+ }
+ break;
+ case 2:
+ for (int yy=0; yy < yleft; ++yy)
+ memcpy(block+yy*16+2*4, block+yy*16+0*4, 8);
+ break;
+ case 3:
+ for (int yy=0; yy < yleft; ++yy)
+ memcpy(block+yy*16+3*4, block+yy*16+1*4, 4);
+ break;
+ }
+ }
+ int yy = 0;
+ for(; yleft<4; ++yleft,++yy)
+ memcpy(block+yleft*16, block+yy*16, 4*4);
+ FastCompressDXTBlock( dest, block, dxt5, dither );
+ dest += dxt5 ? 16 : 8;
+ }
+ }
+
+ AssertIf( dest > destEnd );
+ UNUSED(destEnd);
+}
diff --git a/Runtime/Graphics/DXTCompression.h b/Runtime/Graphics/DXTCompression.h
new file mode 100644
index 0000000..414c23e
--- /dev/null
+++ b/Runtime/Graphics/DXTCompression.h
@@ -0,0 +1,13 @@
+// Fast DXT compression code adapted from
+// stb_dxt by Sean Barrett: http://nothings.org/stb/stb_dxt.h
+// which is in turn adapted from code by
+// Fabian "ryg" Giesen: http://www.farbrausch.de/~fg/code/dxt/
+
+#ifndef __DXT_COMPRESSION__
+#define __DXT_COMPRESSION__
+
+// src: 32 bit/pixel width*height image, R,G,B,A bytes in pixel.
+// dest must be (width+3)/4*(height+3)/4 bytes if dxt5=true, and half of that if dxt5=false (dxt1 is used then).
+void FastCompressImage (int width, int height, const UInt8* src, UInt8* dest, bool dxt5, bool dither );
+
+#endif
diff --git a/Runtime/Graphics/DisplayManager.h b/Runtime/Graphics/DisplayManager.h
new file mode 100644
index 0000000..6de5f08
--- /dev/null
+++ b/Runtime/Graphics/DisplayManager.h
@@ -0,0 +1,54 @@
+#pragma once
+#include "UnityPrefix.h"
+
+// we do c-style interface, because display management is way too platfrom specific
+// these function will be used from script
+// UnityDisplayManager prefix is used because some platforms implements this in trampoline
+
+#define SUPPORT_MULTIPLE_DISPLAYS UNITY_IPHONE
+
+#if !SUPPORT_MULTIPLE_DISPLAYS
+ #include "ScreenManager.h"
+#endif
+
+struct RenderSurfaceBase;
+
+#if SUPPORT_MULTIPLE_DISPLAYS
+
+ extern "C" int UnityDisplayManager_DisplayCount();
+ extern "C" bool UnityDisplayManager_DisplayAvailable(void* nativeDisplay);
+ extern "C" void UnityDisplayManager_DisplaySystemResolution(void* nativeDisplay, int* w, int* h);
+ extern "C" void UnityDisplayManager_DisplayRenderingResolution(void* nativeDisplay, int* w, int* h);
+ extern "C" void UnityDisplayManager_DisplayRenderingBuffers(void* nativeDisplay, RenderSurfaceBase** colorBuffer, RenderSurfaceBase** depthBuffer);
+ extern "C" void UnityDisplayManager_SetRenderingResolution(void* nativeDisplay, int w, int h);
+
+#else
+
+ inline int UnityDisplayManager_DisplayCount()
+ {
+ return 1;
+ }
+ inline bool UnityDisplayManager_DisplayAvailable(void*)
+ {
+ return true;
+ }
+ inline void UnityDisplayManager_DisplaySystemResolution(void*, int* w, int* h)
+ {
+ *w = GetScreenManager().GetWidth();
+ *h = GetScreenManager().GetHeight();
+ }
+ inline void UnityDisplayManager_DisplayRenderingResolution(void*, int* w, int* h)
+ {
+ *w = GetScreenManager().GetWidth();
+ *h = GetScreenManager().GetHeight();
+ }
+ inline void UnityDisplayManager_SetRenderingResolution(void*, int w, int h)
+ {
+ GetScreenManager().RequestResolution(w, h, GetScreenManager ().IsFullScreen (), 0);
+ }
+ inline void UnityDisplayManager_DisplayRenderingBuffers(void*, RenderSurfaceBase** color, RenderSurfaceBase** depth)
+ {
+ *color = *depth = 0;
+ }
+
+#endif
diff --git a/Runtime/Graphics/DrawSplashScreenAndWatermarks.cpp b/Runtime/Graphics/DrawSplashScreenAndWatermarks.cpp
new file mode 100644
index 0000000..6735e4a
--- /dev/null
+++ b/Runtime/Graphics/DrawSplashScreenAndWatermarks.cpp
@@ -0,0 +1,449 @@
+#include "UnityPrefix.h"
+#include "DrawSplashScreenAndWatermarks.h"
+#include "Runtime/Graphics/GraphicsHelper.h"
+#include "Runtime/Misc/ReproductionLog.h"
+#include "Runtime/Misc/PlayerSettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Graphics/Texture2D.h"
+#include "Runtime/Graphics/ScreenManager.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Camera/RenderLayers/GUITexture.h"
+#include "Runtime/Misc/SystemInfo.h"
+
+#if WEBPLUG
+double gDisplayFullscreenEscapeTimeout = -1.0F;
+#endif
+
+static bool ConsumeVersionNumber(const char *&version, int &number)
+{
+ number = 0;
+
+ for (int i = 0; i < 8; ++i) // limit to 8 digits
+ {
+ char c = *version;
+
+ if ((c < '0') || (c > '9'))
+ {
+ return (i > 0);
+ }
+
+ ++version;
+
+ number *= 10;
+ number += (c - '0');
+ }
+
+ return false;
+}
+
+static bool ConsumeVersionSeparator(const char *&version)
+{
+ if ('.' == *version)
+ {
+ ++version;
+ return true;
+ }
+
+ return false;
+}
+
+bool IsContentBuiltWithBetaVersion()
+{
+ if (GetBuildSettingsPtr() == NULL)
+ return false;
+
+ const char* version = GetBuildSettings().GetVersion().c_str();
+
+ int temp;
+
+ if (ConsumeVersionNumber(version, temp) && ConsumeVersionSeparator(version)) // major
+ {
+ if (ConsumeVersionNumber(version, temp) && ConsumeVersionSeparator(version)) // minor
+ {
+ if (ConsumeVersionNumber(version, temp)) // fix
+ {
+ char c = *version;
+
+ if (('d' == c) || ('D' == c) || // development
+ ('a' == c) || ('A' == c) || // alpha
+ ('b' == c) || ('B' == c)) // beta
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void DrawWaterMark( bool alwaysDisplay )
+{
+ Texture2D* watermark = GetBuiltinResource<Texture2D>("UnityWaterMark-small.png");
+ if( !watermark )
+ return;
+
+#if !UNITY_EDITOR
+
+ const float kSlideInDelay = 1.5f;
+ const float kSlideInTime = 1.0f;
+ const float kStayTime = 5.0f;
+ const float kSlideOutTime = 1.5f;
+
+ float t = GetTimeManager().GetRealtime();
+ if( !alwaysDisplay && t > kSlideInDelay+kSlideInTime+kStayTime+kSlideOutTime )
+ return;
+
+ float a;
+ if( t < kSlideInDelay+kSlideInTime )
+ a = (t-kSlideInDelay) / kSlideInTime;
+ else if( !alwaysDisplay )
+ a = 1.0f - (t-(kSlideInDelay+kSlideInTime+kStayTime)) / kSlideOutTime;
+ else
+ a = 1.0f;
+
+#else
+ float a = 1.0f;
+#endif
+
+ float pos = SmoothStep( 0.0f, 128.0f, a );
+
+ const Rectf& windowRect = GetRenderManager().GetWindowRect();
+ DeviceMVPMatricesState preserveMVP;
+ SetupPixelCorrectCoordinates();
+
+ DrawGUITexture (Rectf (windowRect.width-pos, 62, 128, -58), watermark, ColorRGBAf(0.5f, 0.5f, 0.5f, 0.5f));
+}
+
+// (0, 0) is at the lower left corner. x increments to the left, y - upwards.
+// specify -(x + 1) to align to the right and -(y + 1) to align to the top
+static int DrawSimpleWatermark(const string &name, float x, float y, const ColorRGBAf& color)
+{
+ Texture2D* texture = GetBuiltinResource<Texture2D>(name);
+ if (!texture)
+ return 0;
+
+ const Rectf& windowRect = GetRenderManager().GetWindowRect();
+ DeviceMVPMatricesState preserveMVP;
+ SetupPixelCorrectCoordinates();
+
+ float width = texture->GetDataWidth();
+ float height = -texture->GetDataHeight();
+
+ if (x < 0)
+ {
+ x = -(x + 1);
+ x = (windowRect.width - width - x);
+ }
+
+ if (y < 0)
+ {
+ y = -(y + 1);
+ y = (windowRect.height + height - y);
+ }
+
+ y -= height;
+
+ DrawGUITexture (Rectf (x, y, width, height), texture, color);
+
+ return texture->GetDataHeight();
+}
+
+static int DrawSimpleWatermark(const string &name, float x, float y)
+{
+ return DrawSimpleWatermark(name, x, y, ColorRGBAf(0.5f, 0.5f, 0.5f, 0.5f));
+}
+
+static void DrawTrialWatermark (int& watermarkoffset)
+{
+ int height = DrawSimpleWatermark("UnityWaterMark-trial.png", -(1 + 1), watermarkoffset);
+ watermarkoffset += height + 3;
+}
+
+static void DrawEducationalWatermark (int& watermarkoffset)
+{
+ int height = DrawSimpleWatermark("UnityWaterMark-edu.png", -(1 + 1), watermarkoffset);
+ watermarkoffset += height + 3;
+}
+
+static void DrawPrototypingWatermark (int& watermarkoffset)
+{
+ int height = DrawSimpleWatermark("UnityWaterMark-proto.png", -(1 + 1), watermarkoffset);
+ watermarkoffset += height + 3;
+}
+
+static void DrawDeveloperWatermark (int& watermarkoffset)
+{
+ int height = DrawSimpleWatermark("UnityWaterMark-dev.png", -(1 + 1), watermarkoffset);
+ watermarkoffset += height + 3;
+}
+
+#if UNITY_FLASH
+static void DrawDebugFlashPlayerWatermark (int& watermarkoffset)
+{
+ int height = DrawSimpleWatermark("UnityWatermark-DebugFlashPlayer.png", -(1 + 1), watermarkoffset);
+ watermarkoffset += height + 3;
+}
+#endif
+
+#if WEBPLUG
+
+
+static void DrawContentBetaWatermark (int& watermarkoffset)
+{
+ int height = DrawSimpleWatermark("UnityWaterMark-beta.png", -(1 + 1), watermarkoffset);
+ watermarkoffset += height + 3;
+}
+
+static void DrawPluginBetaWatermark (int& watermarkoffset)
+{
+ int height = DrawSimpleWatermark("UnityWaterMarkPlugin-beta.png", -(1 + 1), watermarkoffset);
+ watermarkoffset += height + 3;
+}
+
+void ShowFullscreenEscapeWarning ()
+{
+ gDisplayFullscreenEscapeTimeout = GetTimeSinceStartup();
+}
+
+void RenderFullscreenEscapeWarning ()
+{
+ const float kTimeout = 6.0F;
+ if (GetTimeSinceStartup() < gDisplayFullscreenEscapeTimeout + kTimeout && GetScreenManager().IsFullScreen())
+ {
+ float time = GetTimeSinceStartup() - gDisplayFullscreenEscapeTimeout;
+ time = std::max(time, 0.0F);
+
+ Texture2D* background = GetBuiltinResource<Texture2D>("EscToExit_back.png");
+ Texture2D* text = GetBuiltinResource<Texture2D>("EscToExit_text.png");
+ if( background && text )
+ {
+ float a = SmoothStep( 0.5f, 0, (time - 3.5f) * 0.5f );
+ ColorRGBA32 color = ColorRGBAf(0.5f,0.5f,0.5f,a);
+ const float w = 350;
+ const float h = 71;
+ const float yFac = 1.0f - 0.38197f;
+
+ const Rectf& windowRect = GetRenderManager().GetWindowRect();
+ DeviceMVPMatricesState preserveMVP;
+ SetupPixelCorrectCoordinates();
+ DrawGUITexture( Rectf((windowRect.width-w)*0.5f, (windowRect.height-h)*yFac, w, -h), background, 36, 36, 0, 0, color );
+ const float textw = 267;
+ DrawGUITexture( Rectf((windowRect.width-textw)*0.5f, (windowRect.height-h)*yFac-25, textw, -20), text, color );
+ }
+ }
+ else if (!GetScreenManager().HasFullscreenRequested())
+ {
+ gDisplayFullscreenEscapeTimeout = -1000.0;
+ }
+}
+#endif
+
+
+#if UNITY_CAN_SHOW_SPLASH_SCREEN
+
+static double gSplashScreenStartTime;
+static const float kSplashBeforeGameDuration = 4.5f;
+
+void BeginSplashScreenFade()
+{
+ gSplashScreenStartTime = GetTimeSinceStartup();
+}
+
+bool IsSplashScreenFadeComplete()
+{
+ return (GetTimeSinceStartup() >= gSplashScreenStartTime + kSplashBeforeGameDuration);
+}
+
+bool GetShouldShowSplashScreen()
+{
+ const BuildSettings& settings = GetBuildSettings();
+ bool disableWatermark = GetBuildSettings().isNoWatermarkBuild;
+ if ( disableWatermark )
+ return false;
+ bool isTrial = !settings.hasPublishingRights;
+ bool isIndie = !settings.hasPROVersion;
+ return isTrial || isIndie;
+}
+
+#if DEBUGMODE
+ #define DEBUGMSG_ONCE(Message) \
+ {\
+ static bool once = false; \
+ if (!once) { printf_console(Message); once = true; }\
+ }
+#else
+ #define DEBUGMSG_ONCE(Message) {}
+#endif
+
+void DrawSplashScreen (bool fullDraw)
+{
+ AssertIf (!GetShouldShowSplashScreen());
+
+ DEBUGMSG_ONCE("Begin showing splash screen.\n");
+
+ const float kSplashFadeStart = 3.0f;
+ const float kSplashXFadeDuration = 0.4f;
+
+ float timeBegin = fullDraw ? 0.0f : kSplashBeforeGameDuration;
+ float t = GetTimeSinceStartup() - gSplashScreenStartTime - timeBegin;
+ if (!fullDraw && t > kSplashXFadeDuration)
+ {
+ DEBUGMSG_ONCE("End showing splash screen.\n");
+ return;
+ }
+
+ Texture2D* logo = GetBuiltinResource<Texture2D>("UnitySplash.png");
+ if (!logo)
+ return;
+ Texture2D* text = GetBuiltinResource<Texture2D>("UnitySplash2.png");
+ if (!text)
+ return;
+ Texture2D* background = GetBuiltinResource<Texture2D>("UnitySplash3.png");
+ if (!background)
+ return;
+ Texture2D* black = GetBuiltinResource<Texture2D>("UnitySplashBack.png");
+ if (!black)
+ return;
+
+ const Rectf& windowRect = GetRenderManager().GetWindowRect();
+ Vector2f basePos (windowRect.width * .5f - 119, windowRect.height *.5f - 219);
+
+ Rectf (basePos.x, basePos.y, 276,432);
+
+ GfxDevice& device = GetGfxDevice();
+ if (fullDraw)
+ {
+ device.BeginFrame();
+ if (!device.IsValidState())
+ {
+ device.HandleInvalidState();
+ return;
+ }
+ const float kBlack[4] = {0,0,0,0};
+ GraphicsHelper::Clear (kGfxClearAll, kBlack, 1.0f, 0);
+ }
+
+ DeviceMVPMatricesState preserveMVP;
+ SetupPixelCorrectCoordinates();
+
+ if (fullDraw)
+ {
+ float a = SmoothStep( 0.5f, 0.0f, (t - kSplashFadeStart) / (kSplashBeforeGameDuration-kSplashFadeStart));
+ DrawGUITexture( Rectf (basePos.x, basePos.y + 432, 276, -432), background, ColorRGBAf(a,a,a, 0.5f) );
+ DrawGUITexture( Rectf (basePos.x, basePos.y + 432, 276, -432), background, ColorRGBAf(a,a,a, 0.5f) );
+ DrawGUITexture( Rectf (basePos.x + 43, basePos.y + 83, 146, -19), text, ColorRGBAf(a,a,a, 0.5f) );
+ DrawGUITexture( Rectf (basePos.x, basePos.y + 330, 206, -214), logo, ColorRGBAf(a,a,a, 0.5f) );
+ }
+ else
+ {
+ float a = SmoothStep( 0.5f, 0.0f, t / kSplashXFadeDuration);
+ DrawGUITexture(Rectf (0,0,windowRect.width+10, windowRect.height+10), black, ColorRGBAf(0.5f, 0.5f, 0.5f, a));
+ }
+
+ if (fullDraw)
+ {
+ device.EndFrame();
+#if ((UNITY_LINUX && SUPPORT_X11) || UNITY_OSX) && !UNITY_EDITOR && !WEBPLUG
+ GetScreenManager().SetBlitMaterial();
+#endif
+ device.PresentFrame();
+ }
+}
+
+#endif // UNITY_CAN_SHOW_SPLASH_SCREEN
+
+
+#if UNITY_FLASH
+static bool isDebugFlashPlayer;
+static bool haveSetDebugFlashPlayer=false;
+
+static bool IsRunningInDebugFlashPlayer()
+{
+ if (!haveSetDebugFlashPlayer)
+ {
+ __asm("%0 = flash.system.Capabilities.isDebugger ? 1 : 0;" : "=r"(isDebugFlashPlayer));
+ //__asm("trace('Debugger!?!?!');");
+ //__asm("trace(flash.system.Capabilities.isDebugger ? 1 : 0);");
+ haveSetDebugFlashPlayer = true;
+ }
+ return isDebugFlashPlayer;
+}
+#endif
+
+void DrawSplashAndWatermarks()
+{
+#if SUPPORT_REPRODUCE_LOG
+ if (GetReproduceMode() != kNormalPlayback)
+ return;
+#endif
+ // draw splash / watermark
+ RuntimePlatform platform = systeminfo::GetRuntimePlatform();
+ bool disableWatermark = GetBuildSettings().isNoWatermarkBuild;
+ bool isEducational = GetBuildSettings().isEducationalBuild && !UNITY_EDITOR;
+ bool isPrototyping = GetBuildSettings().isPrototypingBuild && !UNITY_EDITOR;
+ bool isTrial = !GetBuildSettings().hasPublishingRights && !UNITY_EDITOR;
+ bool isIndie = !GetBuildSettings().hasPROVersion;
+ bool isIndieAndWebPlayer = isIndie && systeminfo::IsPlatformWebPlayer(platform);
+ bool isDeveloperPlayerBuild = !UNITY_EDITOR && GetBuildSettings().isDebugBuild;
+
+ if (isIndieAndWebPlayer)
+ DrawWaterMark (false);
+
+ int waterMarkOffset = 3;
+
+#if UNITY_FLASH
+ if (IsRunningInDebugFlashPlayer())
+ DrawDebugFlashPlayerWatermark(waterMarkOffset);
+#endif
+
+ const bool isDeveloperBuild = IS_CONTENT_NEWER_OR_SAME(kUnityVersion3_2_a1) && isDeveloperPlayerBuild || (UNITY_DEVELOPER_BUILD && !UNITY_FLASH);
+
+ if (isDeveloperBuild)
+ DrawDeveloperWatermark(waterMarkOffset);
+
+#if !UNITY_FLASH && !UNITY_WP8
+ // Time based licenses will still look like "trial" licenses, so check for educational and prototyping flags first
+ if ( !disableWatermark )
+ {
+ if (isEducational)
+ DrawEducationalWatermark (waterMarkOffset);
+ else if (isPrototyping)
+ DrawPrototypingWatermark (waterMarkOffset);
+ else if (isTrial)
+ DrawTrialWatermark (waterMarkOffset);
+ }
+#endif
+
+#if UNITY_ANDROID || UNITY_BB10
+ // it will be drawn completely transparent, so no need to worry ;-)
+ if( gGraphicsCaps.gles20.needToRenderWatermarkDueToDrawFromMemoryBuggy
+ && GetGfxDevice().GetRenderer() == kGfxRendererOpenGLES20Mobile
+ && !isDeveloperBuild
+ )
+ {
+ DrawSimpleWatermark("UnityWaterMark-dev.png", -(1 + 1), waterMarkOffset, ColorRGBAf(0,0,0,0));
+ }
+#endif
+
+#if WEBPLUG && !UNITY_PEPPER && !UNITY_FLASH
+
+ // Jonas Echterhoff suggested [2012.07.23 18:21:35] to show only one of the messages,
+ // i.e., either plugin beta or content beta, because if you have some unity beta installed
+ // you will always have three watermarks: Made with beta, Running on beta and Development player
+ #if UNITY_IS_BETA
+ DrawPluginBetaWatermark (waterMarkOffset);
+ #else
+ if( IsContentBuiltWithBetaVersion ())
+ DrawContentBetaWatermark (waterMarkOffset);
+ #endif // UNITY_IS_BETA
+#endif
+
+#if UNITY_CAN_SHOW_SPLASH_SCREEN
+ if (GetShouldShowSplashScreen())
+ DrawSplashScreen (false);
+#endif
+}
diff --git a/Runtime/Graphics/DrawSplashScreenAndWatermarks.h b/Runtime/Graphics/DrawSplashScreenAndWatermarks.h
new file mode 100644
index 0000000..b323456
--- /dev/null
+++ b/Runtime/Graphics/DrawSplashScreenAndWatermarks.h
@@ -0,0 +1,18 @@
+#pragma once
+
+void DrawSplashAndWatermarks();
+void DrawWaterMark( bool alwaysDisplay );
+
+#if UNITY_CAN_SHOW_SPLASH_SCREEN
+
+void BeginSplashScreenFade();
+bool IsSplashScreenFadeComplete();
+bool GetShouldShowSplashScreen();
+void DrawSplashScreen (bool fullDraw);
+#endif
+
+#if WEBPLUG
+void ShowFullscreenEscapeWarning ();
+void RenderFullscreenEscapeWarning ();
+extern double gDisplayFullscreenEscapeTimeout;
+#endif
diff --git a/Runtime/Graphics/DrawUtil.cpp b/Runtime/Graphics/DrawUtil.cpp
new file mode 100644
index 0000000..4c17fe8
--- /dev/null
+++ b/Runtime/Graphics/DrawUtil.cpp
@@ -0,0 +1,173 @@
+#include "UnityPrefix.h"
+#include "DrawUtil.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Transform.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/GfxDevice/GfxDeviceStats.h"
+#include "Runtime/GfxDevice/BatchRendering.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Shaders/ComputeShader.h"
+#include "Runtime/Graphics/SpriteFrame.h"
+#if UNITY_PS3
+# include "Runtime/GfxDevice/ps3/GfxGCMVBO.h"
+#endif
+
+PROFILER_INFORMATION(gDrawMeshVBOProfile, "Mesh.DrawVBO", kProfilerRender)
+PROFILER_INFORMATION(gDrawMeshNullProfile, "Graphics.DrawProcedural", kProfilerRender)
+
+static void DrawMeshInternal (const ChannelAssigns& channels, Mesh &mesh, const Matrix4x4f &matrix, int subsetIndex, TransformType transformType);
+
+void DrawUtil::DrawVBOMeshRaw (VBO& vbo, Mesh& mesh, const ChannelAssigns& channels, int subsetIndex, UInt32 channelsInVBO)
+{
+ PROFILER_AUTO(gDrawMeshVBOProfile, &mesh)
+
+ subsetIndex = std::min<unsigned int>(subsetIndex, mesh.GetSubMeshCount()? mesh.GetSubMeshCount()-1:0);
+ SubMesh& submesh = mesh.GetSubMeshFast(subsetIndex);
+
+ int firstVertex, vertexCount;
+
+ firstVertex = submesh.firstVertex;
+ vertexCount = submesh.vertexCount;
+#if UNITY_PS3
+ GfxGCMVBO& gcmVBO = (GfxGCMVBO&)vbo;
+ gcmVBO.DrawSubmesh(channels, subsetIndex, &submesh);
+#else
+ vbo.DrawVBO (channels, submesh.firstByte, submesh.indexCount, submesh.topology, firstVertex, vertexCount);
+#endif
+ GPU_TIMESTAMP();
+}
+
+void DrawUtil::DrawMeshRaw (const ChannelAssigns& channels, Mesh& mesh, int subsetIndex)
+{
+ VBO* vbo = mesh.GetSharedVBO (channels.GetSourceMap());
+ if( vbo != NULL )
+ {
+ DrawVBOMeshRaw (*vbo, mesh, channels, subsetIndex);
+ }
+}
+
+void DrawUtil::DrawMesh (const ChannelAssigns& channels, Mesh &mesh, const Vector3f &position, const Quaternionf &rotation, int subsetIndex)
+{
+ Matrix4x4f matrix;
+ QuaternionToMatrix (rotation, matrix);
+ matrix.SetPosition( position );
+
+ TransformType transformType = kNoScaleTransform;
+ DrawMeshInternal (channels, mesh, matrix, subsetIndex, transformType);
+}
+
+void DrawUtil::DrawMesh (const ChannelAssigns& channels, Mesh &mesh, const Matrix4x4f &matrix, int subsetIndex)
+ {
+ // just assume it is a scaled transform, but don't handle negative scales... oh well! (says @aras_p)
+ TransformType transformType = kUniformScaleTransform;
+ DrawMeshInternal (channels, mesh, matrix, subsetIndex, transformType);
+}
+
+static void DrawMeshInternal (const ChannelAssigns& channels, Mesh &mesh, const Matrix4x4f &matrix, int subsetIndex, TransformType transformType)
+{
+ const Camera* camera = GetCurrentCameraPtr();
+
+ GfxDevice& device = GetGfxDevice();
+ float matWorld[16], matView[16];
+
+ CopyMatrix(device.GetViewMatrix(), matView);
+ CopyMatrix(device.GetWorldMatrix(), matWorld);
+ if (camera)
+ device.SetViewMatrix( camera->GetWorldToCameraMatrix().GetPtr() );
+
+ SetupObjectMatrix (matrix, transformType);
+
+ if (subsetIndex != -1)
+ {
+ DrawUtil::DrawMeshRaw (channels, mesh, subsetIndex);
+ }
+ else
+ {
+ int submeshCount = mesh.GetSubMeshCount();
+ for (int i=0;i<submeshCount;i++)
+ DrawUtil::DrawMeshRaw (channels, mesh, i);
+ }
+
+ device.SetViewMatrix(matView);
+ device.SetWorldMatrix(matWorld);
+}
+
+void DrawUtil::DrawProcedural (GfxPrimitiveType topology, int vertexCount, int instanceCount)
+{
+ if (instanceCount > 1 && !gGraphicsCaps.hasInstancing)
+ {
+ ErrorString ("Can't do instanced Graphics.DrawProcedural");
+ return;
+ }
+
+ PROFILER_AUTO(gDrawMeshNullProfile, NULL)
+
+ GfxDevice& device = GetGfxDevice();
+ device.DrawNullGeometry (topology, vertexCount, instanceCount);
+ device.GetFrameStats().AddDrawCall (vertexCount*instanceCount, vertexCount*instanceCount);
+
+ GPU_TIMESTAMP();
+}
+
+void DrawUtil::DrawProceduralIndirect (GfxPrimitiveType topology, ComputeBuffer* bufferWithArgs, UInt32 argsOffset)
+{
+ if (!gGraphicsCaps.hasInstancing || !gGraphicsCaps.hasComputeShader)
+ {
+ ErrorString ("Can't do indirect Graphics.DrawProcedural");
+ return;
+ }
+ if (!bufferWithArgs)
+ {
+ ErrorString ("Graphics.DrawProcedural with invalid buffer");
+ return;
+ }
+ ComputeBufferID bufferHandle = bufferWithArgs->GetBufferHandle();
+ if (!bufferHandle.IsValid())
+ {
+ ErrorString ("Graphics.DrawProcedural with invalid buffer");
+ return;
+ }
+
+ PROFILER_AUTO(gDrawMeshNullProfile, NULL)
+
+ GfxDevice& device = GetGfxDevice();
+ device.DrawNullGeometryIndirect (topology, bufferHandle, argsOffset);
+ device.GetFrameStats().AddDrawCall (1,1); // unknown primitive count
+
+ GPU_TIMESTAMP();
+}
+
+void DrawUtil::DrawSpriteRaw (const ChannelAssigns& channels, Sprite& sprite, const ColorRGBAf& color)
+{
+ GfxDevice& device = GetGfxDevice();
+
+ const SpriteRenderData& rd = sprite.GetRenderData(false); // Use non-atlased RenderData as input.
+ Assert(rd.texture.IsValid());
+
+ // Get VBO chunk for a rectangle or mesh
+ UInt32 numIndices = rd.indices.size();
+ UInt32 numVertices = rd.vertices.size();
+ if (!numIndices)
+ return;
+
+ const UInt32 channelMask = (1<<kShaderChannelVertex) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor);
+
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ UInt8* __restrict vbPtr;
+ UInt16* __restrict ibPtr;
+ if ( !vbo.GetChunk(channelMask, numVertices, numIndices, DynamicVBO::kDrawIndexedTriangles, (void**)&vbPtr, (void**)&ibPtr) )
+ return;
+
+ TransformSprite (vbPtr, ibPtr, NULL, &rd, device.ConvertToDeviceVertexColor(color), 0);
+ vbo.ReleaseChunk(numVertices, numIndices);
+ vbo.DrawChunk(channels);
+}
diff --git a/Runtime/Graphics/DrawUtil.h b/Runtime/Graphics/DrawUtil.h
new file mode 100644
index 0000000..52bf156
--- /dev/null
+++ b/Runtime/Graphics/DrawUtil.h
@@ -0,0 +1,51 @@
+#ifndef DRAWUTIL_H
+#define DRAWUTIL_H
+
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/Modules/ExportModules.h"
+
+namespace Unity { class Material; }
+class ChannelAssigns;
+using namespace Unity;
+class Vector2f;
+class Vector3f;
+class ColorRGBAf;
+class Quaternionf;
+class Mesh;
+class VBO;
+class Matrix4x4f;
+class ComputeBuffer;
+class Sprite;
+
+
+enum MeshPrimitiveType
+{
+ kTriangles = 0,
+ kLines = 1,
+};
+
+// Utilities for drawing
+struct EXPORT_COREMODULE DrawUtil
+{
+ static void DrawVBOMeshRaw (VBO& vbo, Mesh& mesh, const ChannelAssigns& channels, int materialIndex, UInt32 channelsInVBO = ~0UL);
+
+ /// Draw a mesh with the current matrix
+ static void DrawMeshRaw (const ChannelAssigns& channels, Mesh &mesh, int materialIndex);
+
+ static void DrawMesh (const ChannelAssigns& channels, Mesh &mesh, const Vector3f &position, const Quaternionf &rotation, int materialIndex = -1);
+ static void DrawMesh (const ChannelAssigns& channels, Mesh &mesh, const Matrix4x4f &matrix, int materialIndex);
+
+ static void DrawProcedural (GfxPrimitiveType topology, int vertexCount, int instanceCount);
+
+ // args in ComputeBuffer at given offset:
+ // uint32 vertexCountPerInstance
+ // uint32 instanceCount
+ // uint32 startVertexLocation
+ // uint32 startInstanceLocation
+ static void DrawProceduralIndirect (GfxPrimitiveType topology, ComputeBuffer* bufferWithArgs, UInt32 argsOffset);
+
+
+ static void DrawSpriteRaw (const ChannelAssigns& channels, Sprite& sprite, const ColorRGBAf& color);
+};
+
+#endif
diff --git a/Runtime/Graphics/ETC2Decompression.cpp b/Runtime/Graphics/ETC2Decompression.cpp
new file mode 100644
index 0000000..d2f8169
--- /dev/null
+++ b/Runtime/Graphics/ETC2Decompression.cpp
@@ -0,0 +1,487 @@
+#include "ETC2Decompression.h"
+
+// Utilities
+
+template<typename T>
+static inline T InRange (T v, T a, T b)
+{
+ return a <= v && v <= b;
+}
+
+template<typename T>
+static inline T Clamp (T v, T a, T b)
+{
+ if (v < a) return a;
+ else if (v > b) return b;
+ else return v;
+}
+
+static inline int DivRoundUp (int a, int b)
+{
+ return a/b + ((a%b) ? 1 : 0);
+}
+
+static inline UInt32 GetSingleBit (UInt64 src, int bit)
+{
+ return (src >> bit) & 1;
+}
+
+static inline UInt32 GetBitRange (UInt64 src, int low, int high)
+{
+ int numBits = (high-low) + 1;
+ return (src >> low) & ((1<<numBits)-1);
+}
+
+static inline UInt8 ExtendTo8 (UInt8 src, UInt32 fromBits)
+{
+ return (src << (8-fromBits)) | (src >> (2*fromBits - 8));
+}
+
+static inline SInt8 Extend3BitSignedDelta (UInt8 src)
+{
+ bool isNeg = (src & (1<<2)) != 0;
+ return (SInt8)((isNeg ? ~((1<<3)-1) : 0) | src);
+}
+
+static inline UInt16 Extend11To16 (UInt16 src)
+{
+ return (src << 5) | (src >> 6);
+}
+
+static inline SInt16 Extend11To16WithSign (SInt16 src)
+{
+ if (src < 0)
+ return -(SInt16)Extend11To16(-src);
+ else
+ return (SInt16)Extend11To16(src);
+}
+
+// Block access utilities
+
+static inline UInt64 Get64BitBlock (const UInt8* src, int blockNdx)
+{
+ UInt64 block = 0;
+ for (int i = 0; i < 8; i++)
+ block = (block << 8ull) | (UInt64)(src[blockNdx*8+i]);
+ return block;
+}
+
+static inline UInt64 Get128BitBlockStart (const UInt8* src, int blockNdx)
+{
+ return Get64BitBlock(src, 2*blockNdx);
+}
+
+static inline UInt64 Get128BitBlockEnd (const UInt8* src, int blockNdx)
+{
+ return Get64BitBlock(src, 2*blockNdx + 1);
+}
+
+// Block decompression routines
+
+static void DecompressETC2Block (UInt64 src, UInt8 dst[kETC2UncompressedBlockSizeRGB8], UInt8 alphaDst[kETC2UncompressedBlockSizeA8], bool alphaMode)
+{
+ typedef enum BlockMode_e
+ {
+ kBlockModeIndividual = 0,
+ kBlockModeDifferential,
+ kBlockModeT,
+ kBlockModeH,
+ kBlockModePlanar,
+ kBlockModeCount
+ } BlockMode;
+
+ int diffOpaqueBit = (int)GetSingleBit(src, 33);
+ SInt8 selBR = (SInt8)GetBitRange(src, 59, 63); // 5 bits.
+ SInt8 selBG = (SInt8)GetBitRange(src, 51, 55);
+ SInt8 selBB = (SInt8)GetBitRange(src, 43, 47);
+ SInt8 selDR = Extend3BitSignedDelta((UInt8)GetBitRange(src, 56, 58)); // 3 bits.
+ SInt8 selDG = Extend3BitSignedDelta((UInt8)GetBitRange(src, 48, 50));
+ SInt8 selDB = Extend3BitSignedDelta((UInt8)GetBitRange(src, 40, 42));
+ BlockMode mode;
+
+ if (!alphaMode && diffOpaqueBit == 0)
+ mode = kBlockModeIndividual;
+ else if (!InRange(selBR + selDR, 0, 31))
+ mode = kBlockModeT;
+ else if (!InRange(selBG + selDG, 0, 31))
+ mode = kBlockModeH;
+ else if (!InRange(selBB + selDB, 0, 31))
+ mode = kBlockModePlanar;
+ else
+ mode = kBlockModeDifferential;
+
+ if (mode == kBlockModeIndividual || mode == kBlockModeDifferential)
+ {
+ // A lot of logic is shared between individual and differential mode
+ static const int modifierLUT[8][4] =
+ {
+ // 00 01 10 11
+ { 2, 8, -2, -8 },
+ { 5, 17, -5, -17 },
+ { 9, 29, -9, -29 },
+ { 13, 42, -13, -42 },
+ { 18, 60, -18, -60 },
+ { 24, 80, -24, -80 },
+ { 33, 106, -33, -106 },
+ { 47, 183, -47, -183 }
+ };
+
+ int flipBit = (int)GetSingleBit(src, 32);
+ UInt32 table[2] = { GetBitRange(src, 37, 39), GetBitRange(src, 34, 36) };
+ UInt8 baseR[2];
+ UInt8 baseG[2];
+ UInt8 baseB[2];
+
+ // Base values per mode
+ if (mode == kBlockModeIndividual)
+ {
+ baseR[0] = ExtendTo8((UInt8)GetBitRange(src, 60, 63), 4);
+ baseR[1] = ExtendTo8((UInt8)GetBitRange(src, 56, 59), 4);
+ baseG[0] = ExtendTo8((UInt8)GetBitRange(src, 52, 55), 4);
+ baseG[1] = ExtendTo8((UInt8)GetBitRange(src, 48, 51), 4);
+ baseB[0] = ExtendTo8((UInt8)GetBitRange(src, 44, 47), 4);
+ baseB[1] = ExtendTo8((UInt8)GetBitRange(src, 40, 43), 4);
+ }
+ else
+ {
+ baseR[0] = ExtendTo8(selBR, 5);
+ baseG[0] = ExtendTo8(selBG, 5);
+ baseB[0] = ExtendTo8(selBB, 5);
+
+ baseR[1] = ExtendTo8((UInt8)(selBR + selDR), 5);
+ baseG[1] = ExtendTo8((UInt8)(selBG + selDG), 5);
+ baseB[1] = ExtendTo8((UInt8)(selBB + selDB), 5);
+ }
+
+ for (int pixelNdx = 0; pixelNdx < kETC2BlockHeight*kETC2BlockWidth; pixelNdx++)
+ {
+ int x = pixelNdx / kETC2BlockHeight;
+ int y = pixelNdx % kETC2BlockHeight;
+ int dstOffset = (y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeRGB8;
+ int subBlock = ((flipBit ? y : x) >= 2) ? 1 : 0;
+ UInt32 tableNdx = table[subBlock];
+ UInt32 modifierNdx = (GetSingleBit(src, 16+pixelNdx) << 1) | GetSingleBit(src, pixelNdx);
+ int alphaDstOffset = (y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeA8; // Only needed for PUNCHTHROUGH version.
+
+ if (alphaMode && diffOpaqueBit == 0 && modifierNdx == 2)
+ {
+ // If doing PUNCHTHROUGH version (alphaMode), opaque bit may affect colors.
+ dst[dstOffset+0] = 0;
+ dst[dstOffset+1] = 0;
+ dst[dstOffset+2] = 0;
+ alphaDst[alphaDstOffset] = 0;
+ }
+ else
+ {
+ int modifier;
+
+ // PUNCHTHROUGH version and opaque bit may also affect modifiers.
+ if (alphaMode && diffOpaqueBit == 0 && (modifierNdx == 0 || modifierNdx == 2))
+ modifier = 0;
+ else
+ modifier = modifierLUT[tableNdx][modifierNdx];
+
+ dst[dstOffset+0] = (UInt8)Clamp<int>((int)baseR[subBlock] + modifier, 0, 255);
+ dst[dstOffset+1] = (UInt8)Clamp<int>((int)baseG[subBlock] + modifier, 0, 255);
+ dst[dstOffset+2] = (UInt8)Clamp<int>((int)baseB[subBlock] + modifier, 0, 255);
+
+ if (alphaMode)
+ alphaDst[alphaDstOffset] = 255;
+ }
+ }
+ }
+ else if (mode == kBlockModeT || mode == kBlockModeH)
+ {
+ static const int distTable[8] = { 3, 6, 11, 16, 23, 32, 41, 64 };
+
+ UInt8 paintR[4];
+ UInt8 paintG[4];
+ UInt8 paintB[4];
+
+ if (mode == kBlockModeT)
+ {
+ // T mode, calculate paint values.
+ UInt8 R1a = (UInt8)GetBitRange(src, 59, 60);
+ UInt8 R1b = (UInt8)GetBitRange(src, 56, 57);
+ UInt8 G1 = (UInt8)GetBitRange(src, 52, 55);
+ UInt8 B1 = (UInt8)GetBitRange(src, 48, 51);
+ UInt8 R2 = (UInt8)GetBitRange(src, 44, 47);
+ UInt8 G2 = (UInt8)GetBitRange(src, 40, 43);
+ UInt8 B2 = (UInt8)GetBitRange(src, 36, 39);
+ UInt32 distNdx = (GetBitRange(src, 34, 35) << 1) | GetSingleBit(src, 32);
+ int dist = distTable[distNdx];
+
+ paintR[0] = ExtendTo8(((R1a << 2) | R1b), 4);
+ paintG[0] = ExtendTo8(G1, 4);
+ paintB[0] = ExtendTo8(B1, 4);
+ paintR[2] = ExtendTo8(R2, 4);
+ paintG[2] = ExtendTo8(G2, 4);
+ paintB[2] = ExtendTo8(B2, 4);
+ paintR[1] = (UInt8)Clamp<int>((int)paintR[2] + dist, 0, 255);
+ paintG[1] = (UInt8)Clamp<int>((int)paintG[2] + dist, 0, 255);
+ paintB[1] = (UInt8)Clamp<int>((int)paintB[2] + dist, 0, 255);
+ paintR[3] = (UInt8)Clamp<int>((int)paintR[2] - dist, 0, 255);
+ paintG[3] = (UInt8)Clamp<int>((int)paintG[2] - dist, 0, 255);
+ paintB[3] = (UInt8)Clamp<int>((int)paintB[2] - dist, 0, 255);
+ }
+ else
+ {
+ // H mode, calculate paint values.
+ UInt8 R1 = (UInt8)GetBitRange(src, 59, 62);
+ UInt8 G1a = (UInt8)GetBitRange(src, 56, 58);
+ UInt8 G1b = (UInt8)GetSingleBit(src, 52);
+ UInt8 B1a = (UInt8)GetSingleBit(src, 51);
+ UInt8 B1b = (UInt8)GetBitRange(src, 47, 49);
+ UInt8 R2 = (UInt8)GetBitRange(src, 43, 46);
+ UInt8 G2 = (UInt8)GetBitRange(src, 39, 42);
+ UInt8 B2 = (UInt8)GetBitRange(src, 35, 38);
+ UInt8 baseR[2];
+ UInt8 baseG[2];
+ UInt8 baseB[2];
+ UInt32 baseValue[2];
+ UInt32 distNdx;
+ int dist;
+
+ baseR[0] = ExtendTo8(R1, 4);
+ baseG[0] = ExtendTo8(((G1a << 1) | G1b), 4);
+ baseB[0] = ExtendTo8(((B1a << 3) | B1b), 4);
+ baseR[1] = ExtendTo8(R2, 4);
+ baseG[1] = ExtendTo8(G2, 4);
+ baseB[1] = ExtendTo8(B2, 4);
+ baseValue[0] = (((UInt32)baseR[0]) << 16) | (((UInt32)baseG[0]) << 8) | baseB[0];
+ baseValue[1] = (((UInt32)baseR[1]) << 16) | (((UInt32)baseG[1]) << 8) | baseB[1];
+ distNdx = (GetSingleBit(src, 34) << 2) | (GetSingleBit(src, 32) << 1) | (UInt32)(baseValue[0] >= baseValue[1]);
+ dist = distTable[distNdx];
+
+ paintR[0] = (UInt8)Clamp<int>((int)baseR[0] + dist, 0, 255);
+ paintG[0] = (UInt8)Clamp<int>((int)baseG[0] + dist, 0, 255);
+ paintB[0] = (UInt8)Clamp<int>((int)baseB[0] + dist, 0, 255);
+ paintR[1] = (UInt8)Clamp<int>((int)baseR[0] - dist, 0, 255);
+ paintG[1] = (UInt8)Clamp<int>((int)baseG[0] - dist, 0, 255);
+ paintB[1] = (UInt8)Clamp<int>((int)baseB[0] - dist, 0, 255);
+ paintR[2] = (UInt8)Clamp<int>((int)baseR[1] + dist, 0, 255);
+ paintG[2] = (UInt8)Clamp<int>((int)baseG[1] + dist, 0, 255);
+ paintB[2] = (UInt8)Clamp<int>((int)baseB[1] + dist, 0, 255);
+ paintR[3] = (UInt8)Clamp<int>((int)baseR[1] - dist, 0, 255);
+ paintG[3] = (UInt8)Clamp<int>((int)baseG[1] - dist, 0, 255);
+ paintB[3] = (UInt8)Clamp<int>((int)baseB[1] - dist, 0, 255);
+ }
+
+ for (int pixelNdx = 0; pixelNdx < kETC2BlockHeight*kETC2BlockWidth; pixelNdx++)
+ {
+ int x = pixelNdx / kETC2BlockHeight;
+ int y = pixelNdx % kETC2BlockHeight;
+ int dstOffset = (y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeRGB8;
+ UInt32 paintNdx = (GetSingleBit(src, 16+pixelNdx) << 1) | GetSingleBit(src, pixelNdx);
+ int alphaDstOffset = (y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeA8; // Only needed for PUNCHTHROUGH version.
+
+ if (alphaMode && diffOpaqueBit == 0 && paintNdx == 2)
+ {
+ dst[dstOffset+0] = 0;
+ dst[dstOffset+1] = 0;
+ dst[dstOffset+2] = 0;
+ alphaDst[alphaDstOffset] = 0;
+ }
+ else
+ {
+ dst[dstOffset+0] = (UInt8)Clamp<int>((int)paintR[paintNdx], 0, 255);
+ dst[dstOffset+1] = (UInt8)Clamp<int>((int)paintG[paintNdx], 0, 255);
+ dst[dstOffset+2] = (UInt8)Clamp<int>((int)paintB[paintNdx], 0, 255);
+
+ if (alphaMode)
+ alphaDst[alphaDstOffset] = 255;
+ }
+ }
+ }
+ else
+ {
+ // Planar mode.
+ UInt8 GO1 = (UInt8)GetSingleBit(src, 56);
+ UInt8 GO2 = (UInt8)GetBitRange(src, 49, 54);
+ UInt8 BO1 = (UInt8)GetSingleBit(src, 48);
+ UInt8 BO2 = (UInt8)GetBitRange(src, 43, 44);
+ UInt8 BO3 = (UInt8)GetBitRange(src, 39, 41);
+ UInt8 RH1 = (UInt8)GetBitRange(src, 34, 38);
+ UInt8 RH2 = (UInt8)GetSingleBit(src, 32);
+ UInt8 RO = ExtendTo8((UInt8)GetBitRange(src, 57, 62), 6);
+ UInt8 GO = ExtendTo8(((GO1 << 6) | GO2), 7);
+ UInt8 BO = ExtendTo8(((BO1 << 5) | (BO2 << 3) | BO3), 6);
+ UInt8 RH = ExtendTo8(((RH1 << 1) | RH2), 6);
+ UInt8 GH = ExtendTo8((UInt8)GetBitRange(src, 25, 31), 7);
+ UInt8 BH = ExtendTo8((UInt8)GetBitRange(src, 19, 24), 6);
+ UInt8 RV = ExtendTo8((UInt8)GetBitRange(src, 13, 18), 6);
+ UInt8 GV = ExtendTo8((UInt8)GetBitRange(src, 6, 12), 7);
+ UInt8 BV = ExtendTo8((UInt8)GetBitRange(src, 0, 5), 6);
+
+ for (int y = 0; y < 4; y++)
+ {
+ for (int x = 0; x < 4; x++)
+ {
+ int dstOffset = (y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeRGB8;
+ int unclampedR = (x * ((int)RH-(int)RO) + y * ((int)RV-(int)RO) + 4*(int)RO + 2) >> 2;
+ int unclampedG = (x * ((int)GH-(int)GO) + y * ((int)GV-(int)GO) + 4*(int)GO + 2) >> 2;
+ int unclampedB = (x * ((int)BH-(int)BO) + y * ((int)BV-(int)BO) + 4*(int)BO + 2) >> 2;
+ int alphaDstOffset = (y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeA8; // Only needed for PUNCHTHROUGH version.
+
+ dst[dstOffset+0] = (UInt8)Clamp<int>(unclampedR, 0, 255);
+ dst[dstOffset+1] = (UInt8)Clamp<int>(unclampedG, 0, 255);
+ dst[dstOffset+2] = (UInt8)Clamp<int>(unclampedB, 0, 255);
+
+ if (alphaMode)
+ alphaDst[alphaDstOffset] = 255;
+ }
+ }
+ }
+}
+
+static void DecompressEAC8Block (UInt8 dst[kETC2UncompressedBlockSizeA8], UInt64 src)
+{
+ static const int modifierLUT[16][8] =
+ {
+ { -3, -6, -9, -15, 2, 5, 8, 14 },
+ { -3, -7, -10, -13, 2, 6, 9, 12 },
+ { -2, -5, -8, -13, 1, 4, 7, 12 },
+ { -2, -4, -6, -13, 1, 3, 5, 12 },
+ { -3, -6, -8, -12, 2, 5, 7, 11 },
+ { -3, -7, -9, -11, 2, 6, 8, 10 },
+ { -4, -7, -8, -11, 3, 6, 7, 10 },
+ { -3, -5, -8, -11, 2, 4, 7, 10 },
+ { -2, -6, -8, -10, 1, 5, 7, 9 },
+ { -2, -5, -8, -10, 1, 4, 7, 9 },
+ { -2, -4, -8, -10, 1, 3, 7, 9 },
+ { -2, -5, -7, -10, 1, 4, 6, 9 },
+ { -3, -4, -7, -10, 2, 3, 6, 9 },
+ { -1, -2, -3, -10, 0, 1, 2, 9 },
+ { -4, -6, -8, -9, 3, 5, 7, 8 },
+ { -3, -5, -7, -9, 2, 4, 6, 8 }
+ };
+
+ UInt8 baseCodeword = (UInt8)GetBitRange(src, 56, 63);
+ UInt8 multiplier = (UInt8)GetBitRange(src, 52, 55);
+ UInt32 tableNdx = GetBitRange(src, 48, 51);
+
+ for (int pixelNdx = 0; pixelNdx < kETC2BlockHeight*kETC2BlockWidth; pixelNdx++)
+ {
+ int x = pixelNdx / kETC2BlockHeight;
+ int y = pixelNdx % kETC2BlockHeight;
+ int dstOffset = (y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeA8;
+ int pixelBitNdx = 45 - 3*pixelNdx;
+ UInt32 modifierNdx = (GetSingleBit(src, pixelBitNdx + 2) << 2) | (GetSingleBit(src, pixelBitNdx + 1) << 1) | GetSingleBit(src, pixelBitNdx);
+ int modifier = modifierLUT[tableNdx][modifierNdx];
+
+ dst[dstOffset] = (UInt8)Clamp<int>((int)baseCodeword + (int)multiplier*modifier, 0, 255);
+ }
+}
+
+// Decompression API
+
+void DecompressETC2_RGB8 (UInt8* dst, const UInt8* src, int width, int height)
+{
+ int numBlocksX = DivRoundUp(width, 4);
+ int numBlocksY = DivRoundUp(height, 4);
+ int dstPixelSize = kETC2UncompressedPixelSizeRGBA8;
+ int dstRowPitch = width*dstPixelSize;
+
+ for (int blockY = 0; blockY < numBlocksY; blockY++)
+ {
+ for (int blockX = 0; blockX < numBlocksX; blockX++)
+ {
+ UInt64 compressedBlock = Get64BitBlock(src, blockY*numBlocksX + blockX);
+ UInt8 uncompressedBlock[kETC2UncompressedBlockSizeRGB8];
+
+ DecompressETC2Block(compressedBlock, uncompressedBlock, NULL /* no alpha */, false);
+
+ int baseX = blockX*kETC2BlockWidth;
+ int baseY = blockY*kETC2BlockHeight;
+ for (int y = 0; y < std::min((int)kETC2BlockHeight, height-baseY); y++)
+ {
+ for (int x = 0; x < std::min((int)kETC2BlockWidth, width-baseX); x++)
+ {
+ const UInt8* srcPixel = &uncompressedBlock[(y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeRGB8];
+ UInt8* dstPixel = dst + (baseY+y)*dstRowPitch + (baseX+x)*dstPixelSize;
+
+ dstPixel[0] = srcPixel[0];
+ dstPixel[1] = srcPixel[1];
+ dstPixel[2] = srcPixel[2];
+ dstPixel[3] = 0xff; // Force alpha to 1
+ }
+ }
+ }
+ }
+}
+
+void DecompressETC2_RGB8_A1 (UInt8* dst, const UInt8* src, int width, int height)
+{
+ int numBlocksX = DivRoundUp(width, 4);
+ int numBlocksY = DivRoundUp(height, 4);
+ int dstPixelSize = kETC2UncompressedPixelSizeRGBA8;
+ int dstRowPitch = width*dstPixelSize;
+
+ for (int blockY = 0; blockY < numBlocksY; blockY++)
+ {
+ for (int blockX = 0; blockX < numBlocksX; blockX++)
+ {
+ UInt64 compressedBlockRGBA = Get64BitBlock(src, blockY*numBlocksX + blockX);
+ UInt8 uncompressedBlockRGB[kETC2UncompressedBlockSizeRGB8];
+ UInt8 uncompressedBlockAlpha[kETC2UncompressedBlockSizeA8];
+
+ DecompressETC2Block(compressedBlockRGBA, uncompressedBlockRGB, uncompressedBlockAlpha, true);
+
+ int baseX = blockX*kETC2BlockWidth;
+ int baseY = blockY*kETC2BlockHeight;
+ for (int y = 0; y < std::min((int)kETC2BlockHeight, height-baseY); y++)
+ {
+ for (int x = 0; x < std::min((int)kETC2BlockWidth, width-baseX); x++)
+ {
+ const UInt8* srcPixel = &uncompressedBlockRGB[(y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeRGB8];
+ const UInt8* srcPixelAlpha = &uncompressedBlockAlpha[(y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeA8];
+ UInt8* dstPixel = dst + (baseY+y)*dstRowPitch + (baseX+x)*dstPixelSize;
+
+ dstPixel[0] = srcPixel[0];
+ dstPixel[1] = srcPixel[1];
+ dstPixel[2] = srcPixel[2];
+ dstPixel[3] = srcPixelAlpha[0];
+ }
+ }
+ }
+ }
+}
+
+void DecompressETC2_RGBA8 (UInt8* dst, const UInt8* src, int width, int height)
+{
+ int numBlocksX = DivRoundUp(width, 4);
+ int numBlocksY = DivRoundUp(height, 4);
+ int dstPixelSize = kETC2UncompressedPixelSizeRGBA8;
+ int dstRowPitch = width*dstPixelSize;
+
+ for (int blockY = 0; blockY < numBlocksY; blockY++)
+ {
+ for (int blockX = 0; blockX < numBlocksX; blockX++)
+ {
+ UInt64 compressedBlockAlpha = Get128BitBlockStart(src, blockY*numBlocksX + blockX);
+ UInt64 compressedBlockRGB = Get128BitBlockEnd(src, blockY*numBlocksX + blockX);
+ UInt8 uncompressedBlockAlpha[kETC2UncompressedBlockSizeA8];
+ UInt8 uncompressedBlockRGB[kETC2UncompressedBlockSizeRGB8];
+
+ DecompressETC2Block(compressedBlockRGB, uncompressedBlockRGB, NULL /* no alpha */, false);
+ DecompressEAC8Block(uncompressedBlockAlpha, compressedBlockAlpha);
+
+ int baseX = blockX*kETC2BlockWidth;
+ int baseY = blockY*kETC2BlockHeight;
+ for (int y = 0; y < std::min((int)kETC2BlockHeight, height-baseY); y++)
+ {
+ for (int x = 0; x < std::min((int)kETC2BlockWidth, width-baseX); x++)
+ {
+ const UInt8* srcPixelRGB = &uncompressedBlockRGB[(y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeRGB8];
+ const UInt8* srcPixelAlpha = &uncompressedBlockAlpha[(y*kETC2BlockWidth + x)*kETC2UncompressedPixelSizeA8];
+ UInt8* dstPixel = dst + (baseY+y)*dstRowPitch + (baseX+x)*dstPixelSize;
+
+ dstPixel[0] = srcPixelRGB[0];
+ dstPixel[1] = srcPixelRGB[1];
+ dstPixel[2] = srcPixelRGB[2];
+ dstPixel[3] = srcPixelAlpha[0];
+ }
+ }
+ }
+ }
+}
diff --git a/Runtime/Graphics/ETC2Decompression.h b/Runtime/Graphics/ETC2Decompression.h
new file mode 100644
index 0000000..5e97047
--- /dev/null
+++ b/Runtime/Graphics/ETC2Decompression.h
@@ -0,0 +1,39 @@
+#ifndef ETC2DECOMPRESSION_H
+#define ETC2DECOMPRESSION_H
+
+#include "UnityPrefix.h"
+
+// ETC-2 decompressor
+//
+// Input is expected to be in standard ETC-2 (+EAC) memory layout. In ETC2 block size is 64b
+// and alpha EAC block adds another 64b.
+//
+// Output must be RGBA with 8 bits for each channel, stored in R, G, B, A order
+// (from offset 0 to 3).
+//
+// Texture sizes that are not exactly divisible by block size are supported. In such
+// case block count is rounded up and extra decoded pixels are just ignored.
+
+//! ETC2 memory layout configuration
+enum
+{
+ kETC2BlockWidth = 4,
+ kETC2BlockHeight = 4,
+ kETC2UncompressedPixelSizeA8 = 1,
+ kETC2UncompressedPixelSizeRGB8 = 3,
+ kETC2UncompressedPixelSizeRGBA8 = 4,
+ kETC2UncompressedBlockSizeA8 = kETC2BlockWidth*kETC2BlockHeight*kETC2UncompressedPixelSizeA8,
+ kETC2UncompressedBlockSizeRGB8 = kETC2BlockWidth*kETC2BlockHeight*kETC2UncompressedPixelSizeRGB8,
+ kETC2UncompressedBlockSizeRGBA8 = kETC2BlockWidth*kETC2BlockHeight*kETC2UncompressedPixelSizeRGBA8
+};
+
+//! Decompress ETC2 to RGBA8 buffer
+void DecompressETC2_RGB8 (UInt8* dst, const UInt8* src, int width, int height);
+
+//! Decompress ETC2 with punchthrough alpha to RGBA8 buffer
+void DecompressETC2_RGB8_A1 (UInt8* dst, const UInt8* src, int width, int height);
+
+//! Decompress ETC2+EAC to RGBA8 buffer
+void DecompressETC2_RGBA8 (UInt8* dst, const UInt8* src, int width, int height);
+
+#endif
diff --git a/Runtime/Graphics/FlashATFDecompression.h b/Runtime/Graphics/FlashATFDecompression.h
new file mode 100644
index 0000000..9819dbe
--- /dev/null
+++ b/Runtime/Graphics/FlashATFDecompression.h
@@ -0,0 +1,7 @@
+
+#if HAS_FLASH_ATF_DECOMPRESSOR
+
+// Flash ATFFormat to kTexFormatRGBA32
+void DecompressFlashATFTexture ( const UInt8* srcData, int dstWidth, int dstHeight, int mipLevel, UInt8* dstData );
+
+#endif \ No newline at end of file
diff --git a/Runtime/Graphics/GeneratedTextures.cpp b/Runtime/Graphics/GeneratedTextures.cpp
new file mode 100644
index 0000000..c27eb2c
--- /dev/null
+++ b/Runtime/Graphics/GeneratedTextures.cpp
@@ -0,0 +1,258 @@
+#include "UnityPrefix.h"
+#include "External/shaderlab/Library/properties.h"
+#include "Texture.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "GeneratedTextures.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Camera/Light.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Math/Random/Random.h"
+#include "TextureGenerator.h"
+#include "Runtime/GfxDevice/BuiltinShaderParams.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+
+#include <limits>
+
+static PPtr<Texture2D> gWhiteTex = NULL;
+static PPtr<Texture2D> gBlackTex = NULL;
+static PPtr<Texture2D> gAttenuationTex = NULL;
+static PPtr<Texture2D> gHaloTex = NULL;
+static PPtr<Texture3D> gDitherMaskTex = NULL;
+static PPtr<Texture2D> s_RandomRotationTex;
+static PPtr<Texture2D> s_NormalMapTex;
+static PPtr<Texture2D> s_RedTex;
+static PPtr<Texture2D> s_GrayTex;
+static PPtr<Texture2D> s_GrayRampTex;
+
+
+static TextureID gDefaultTextures[kTexDimCount];
+
+static Rand gRandomSeed = 0;
+
+using namespace ShaderLab;
+using namespace std;
+
+
+
+inline void EmptyNormalMap (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ // make "empty normal map" work in both plain encoding and DXT5nm
+ data[0] = 127; // X=0.5 for plain
+ data[1] = 127; // Y=0.5 for plain & DXT5nm
+ data[2] = 255; // Z=1.0 for plain
+ data[3] = 127; // X=0.5 for DXT5nm
+}
+
+inline void White (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ data[0] = 255;
+ data[1] = 255;
+ data[2] = 255;
+ data[3] = 255;
+}
+
+inline void GrayscaleRamp (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ data[0] = x;
+ data[1] = x;
+ data[2] = x;
+ data[3] = x;
+}
+
+inline void Black (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+}
+
+inline void Red (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ data[0] = 255;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+}
+
+inline void Gray (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ data[0] = 127;
+ data[1] = 127;
+ data[2] = 127;
+ data[3] = 127;
+}
+
+inline float ToUnsigned(const float s)
+{
+ return 0.5f * s + 0.5f;
+}
+
+inline void RandomRotation (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ float randAngle = 2.0f * kPI * Random01(gRandomSeed);
+ data[0] = NormalizedToByte (ToUnsigned ( Cos (randAngle)));
+ data[1] = NormalizedToByte (ToUnsigned (-Sin (randAngle)));
+ data[2] = NormalizedToByte (ToUnsigned ( Sin (randAngle)));
+ data[3] = NormalizedToByte (ToUnsigned ( Cos (randAngle)));
+}
+
+template <typename T>
+inline void LightAttenuation (Texture2D *tex, T *data, int x, int y, int maxX, int maxY)
+{
+ float sqrRange = (float)x / (float)maxX;
+ float val = Light::AttenuateNormalized(sqrRange);
+ T b = RoundfToInt( val * std::numeric_limits<T>::max() );
+ *data = b;
+}
+
+inline void HaloTex (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY)
+{
+ maxX >>= 1;
+ maxY >>= 1;
+ float xFac = (float)(x - maxX) / (float)(maxX);
+ float yFac = (float)(y - maxY) / (float)(maxY);
+ float sqrRange = xFac * xFac + yFac * yFac;
+ if (sqrRange > 1.0f)
+ sqrRange = 1.0f;
+ *data = RoundfToInt((1.0f - sqrRange) * 255.0f);
+ return;
+}
+
+inline void WhiteTex (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ *data = 255;
+}
+
+inline void Empty1D (Texture2D *tex, unsigned char *data, int x, int maxX) {
+ data[0] = data[1] = data[2] = data[3] = 128;
+}
+inline void Empty2D (Texture2D *tex, unsigned char *data, int x, int y, int maxX, int maxY) {
+ data[0] = data[1] = data[2] = data[3] = 128;
+}
+inline void Empty3D( unsigned char *data, int x, int y, int z, int maxX, int maxY, int maxZ ) {
+ data[0] = data[1] = data[2] = data[3] = 128;
+}
+inline void EmptyCube( unsigned char *data, Vector3f normal ) {
+ data[0] = data[1] = data[2] = data[3] = 128;
+}
+
+void GenerateDitherTextures()
+{
+ const int width = 4;
+ const int height = 4;
+ const int numSlices = 16;
+ const int numPixelsPerSlice = width*height;
+
+ const unsigned char mask [numPixelsPerSlice] =
+ {
+ 0,9,3,9,
+ 9,4,9,7,
+ 2,9,1,9,
+ 9,6,9,5,
+ };
+
+ gDitherMaskTex = CreateObjectFromCode<Texture3D>();
+ gDitherMaskTex->SetHideFlags(Object::kHideAndDontSave);
+ gDitherMaskTex->InitTexture (width, height, 16, kTexFormatAlpha8, false);
+ gDitherMaskTex->GetSettings().m_Aniso = 0; // disable aniso
+ gDitherMaskTex->GetSettings().m_FilterMode = 0; // disable aniso
+
+ gDitherMaskTex->ApplySettings ();
+
+ unsigned char* data = (unsigned char*)gDitherMaskTex->GetImageDataPointer();
+ for(int slice = 0; slice < numSlices/2; slice++)
+ {
+ int index = slice;
+ int invIndex = numSlices-1-slice;
+ unsigned char* sliceData0 = data + numPixelsPerSlice * index;
+ unsigned char* sliceData1 = data + numPixelsPerSlice * invIndex;
+ for(int i = 0; i < numPixelsPerSlice; i++)
+ {
+ unsigned char maskValue = mask[i] >= slice ? 0x00 : 0xff;
+ sliceData0[i] = maskValue;
+ sliceData1[i] = ~maskValue;
+ }
+ }
+ gDitherMaskTex->UpdateImageData(false);
+}
+
+
+void builtintex::ReinitBuiltinTextures()
+{
+# define INIT_BUILTIN_TEX(texI, tsI, tex) GetGfxDevice().GetBuiltinParamValues().GetWritableTexEnvParam(texI).InitFromTexture(tex, NULL, tsI)
+
+ INIT_BUILTIN_TEX(kShaderTexEnvWhite, kShaderVecWhiteTexelSize, gWhiteTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvBlack, kShaderVecBlackTexelSize, gBlackTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvRed, kShaderVecRedTexelSize, s_RedTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvGray, kShaderVecGrayTexelSize, s_GrayTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvGrey, kShaderVecGreyTexelSize, s_GrayTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvGrayscaleRamp, kShaderVecGrayscaleRampTexelSize, s_GrayRampTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvGreyscaleRamp, kShaderVecGreyscaleRampTexelSize, s_GrayRampTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvBump, kShaderVecBumpTexelSize, s_NormalMapTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvLightmap, kShaderVecLightmapTexelSize, gBlackTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvUnityLightmap, kShaderVecUnityLightmapTexelSize, gBlackTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvUnityLightmapInd, kShaderVecUnityLightmapIndTexelSize, gBlackTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvUnityLightmapThird, kShaderVecUnityLightmapThirdTexelSize, gBlackTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvDitherMaskLOD, kShaderVecDitherMaskLODSize, gDitherMaskTex);
+ INIT_BUILTIN_TEX(kShaderTexEnvRandomRotation, kShaderVecRandomRotationTexelSize, s_RandomRotationTex);
+
+# undef INIT_BUILTIN_TEX
+}
+
+
+void builtintex::GenerateBuiltinTextures()
+{
+ static bool texturesGenerated = false;
+ if( texturesGenerated )
+ return;
+ texturesGenerated = true;
+
+ s_NormalMapTex = BuildTexture<unsigned char> (4, 4, kTexFormatRGBA32, &EmptyNormalMap);
+
+ gWhiteTex = BuildTexture<unsigned char> (4, 4, kTexFormatRGBA32, &White);
+
+ gBlackTex = BuildTexture<unsigned char> (4, 4, kTexFormatRGBA32, &Black);
+
+ // Random rotation texture for rotating points. Stores unsigned cos(theta) in .r and unsigned sin(theta) in .g
+ s_RandomRotationTex = BuildTexture<unsigned char> (16, 16, kTexFormatRGBA32, &RandomRotation);
+
+ // The red texture will be created with mipmaps. The reason is a driver bug in Nvidia GTX 4xx cards
+ // (Win7, driver 270.61), which makes terrain's FirstPass splat map shader flicker like mad if
+ // the control texture has no mips and is not compressed. This happens on any new terrain
+ // (the red texture is used there by default).
+ s_RedTex = BuildTexture<unsigned char> (4, 4, kTexFormatRGBA32, &Red, true);
+
+ s_GrayTex = BuildTexture<unsigned char> (4, 4, kTexFormatRGBA32, &Gray);
+
+ s_GrayRampTex = BuildTexture<unsigned char> (256, 2, kTexFormatRGBA32, &GrayscaleRamp);
+ s_GrayRampTex->GetSettings().m_WrapMode = kTexWrapClamp;
+ s_GrayRampTex->ApplySettings ();
+
+ gHaloTex = BuildTexture<unsigned char> (64, 64, kTexFormatAlpha8, &HaloTex);
+ gHaloTex->GetSettings ().m_WrapMode = kTexWrapClamp;
+ gHaloTex->ApplySettings ();
+
+ if (gGraphicsCaps.supportsTextureFormat[kTexFormatAlphaLum16])
+ gAttenuationTex = BuildTexture<unsigned short> (1024, 1, kTexFormatAlphaLum16, &LightAttenuation<unsigned short>);
+ else
+ gAttenuationTex = BuildTexture<unsigned char> (1024, 1, kTexFormatAlpha8, &LightAttenuation<unsigned char>);
+ gAttenuationTex->GetSettings ().m_WrapMode = kTexWrapClamp;
+ gAttenuationTex->ApplySettings ();
+
+ // Initialize the textures used when no texture has been bound.
+ gDefaultTextures[kTexDim2D] = BuildTexture<unsigned char> (16,16, kTexFormatRGBA32, &Empty2D)->GetTextureID();
+
+ if (gGraphicsCaps.has3DTexture)
+ gDefaultTextures[kTexDim3D] = Build3DTexture<unsigned char> (1,1,1, kTexFormatRGBA32, &Empty3D)->GetTextureID();
+
+ gDefaultTextures[kTexDimCUBE] = BuildCubeTexture<unsigned char> (1, kTexFormatRGBA32, &EmptyCube)->GetTextureID();
+
+ if (gGraphicsCaps.has3DTexture)
+ GenerateDitherTextures();
+
+ // Make the default for a 'any' be the same as the 2D case
+ gDefaultTextures[kTexDimAny] = gDefaultTextures[kTexDim2D];
+
+ ReinitBuiltinTextures();
+}
+
+Texture2D* builtintex::GetWhiteTexture() { return gWhiteTex; }
+Texture2D* builtintex::GetBlackTexture() { return gBlackTex; }
+Texture3D* builtintex::GetDitherMaskTexture() { return gDitherMaskTex; }
+Texture* builtintex::GetAttenuationTexture() { return gAttenuationTex; }
+Texture* builtintex::GetHaloTexture() { return gHaloTex; }
+TextureID builtintex::GetDefaultTexture( TextureDimension texDim ) { return gDefaultTextures[texDim]; }
+TextureID builtintex::GetBlackTextureID () { return gBlackTex->GetTextureID(); }
diff --git a/Runtime/Graphics/GeneratedTextures.h b/Runtime/Graphics/GeneratedTextures.h
new file mode 100644
index 0000000..bc474c5
--- /dev/null
+++ b/Runtime/Graphics/GeneratedTextures.h
@@ -0,0 +1,28 @@
+#ifndef GENERATEDTEXTURES_H
+#define GENERATEDTEXTURES_H
+
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+class Texture;
+class Texture2D;
+class Texture3D;
+
+namespace builtintex {
+
+void GenerateBuiltinTextures();
+void ReinitBuiltinTextures();
+
+Texture2D* GetWhiteTexture ();
+Texture2D* GetBlackTexture ();
+Texture3D* GetDitherMaskTexture();
+Texture* GetAttenuationTexture ();
+Texture* GetHaloTexture ();
+
+// Get the default texture for a texture dimension
+TextureID GetDefaultTexture( TextureDimension texDim );
+
+TextureID GetBlackTextureID ();
+
+} // namespace
+
+
+#endif
diff --git a/Runtime/Graphics/GraphicsHelper.cpp b/Runtime/Graphics/GraphicsHelper.cpp
new file mode 100644
index 0000000..6a32e72
--- /dev/null
+++ b/Runtime/Graphics/GraphicsHelper.cpp
@@ -0,0 +1,103 @@
+#include "UnityPrefix.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "GraphicsHelper.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Shaders/Shader.h"
+#include "External/shaderlab/Library/intshader.h"
+
+namespace GraphicsHelper {
+
+void Clear( UInt32 clearFlags, const float color[4], float depth, int stencil )
+{
+ GfxDevice &device = GetGfxDevice();
+
+
+#if UNITY_WIN
+ int viewport[4];
+ device.GetViewport(viewport);
+
+ bool canUseNativeClear =
+ gGraphicsCaps.hasNonFullscreenClear ||
+ (viewport[0]==0 && viewport[1]==0 && viewport[2]==device.GetCurrentTargetWidth() && viewport[3]==device.GetCurrentTargetHeight());
+ if (!canUseNativeClear)
+ {
+#if ENABLE_MULTITHREADED_CODE
+ DebugAssert(Thread::CurrentThreadIsMainThread());
+#endif
+
+ // Some devices (e.g. D3D11) can't clear sub-areas of a render target, so must draw a quad instead.
+ Shader* clearShader = Shader::GetScreenClearShader();
+ if (!clearShader || clearShader->GetShaderLabShader()->GetActiveSubShader().GetValidPassCount() != 4)
+ {
+ AssertString ("Valid screen clear shader not found");
+ return;
+ }
+ ShaderLab::SubShader& ss = clearShader->GetShaderLabShader()->GetActiveSubShader();
+ const int clearIndex = clearFlags & 3;
+ ss.GetPass (clearIndex)->ApplyPass(0, NULL);
+
+ bool oldWireframe = device.GetWireframe();
+ device.SetWireframe (false);
+ DeviceMVPMatricesState saveMVPMatrices;
+ LoadFullScreenOrthoMatrix ();
+ device.ImmediateBegin (kPrimitiveQuads);
+ device.ImmediateColor(color[0], color[1], color[2], color[3]);
+ float z = -100.0f - 1e-9f;
+ device.ImmediateVertex (0.0f, 0.0f, z);
+ device.ImmediateVertex (0.0f, 1.0f, z);
+ device.ImmediateVertex (1.0f, 1.0f, z);
+ device.ImmediateVertex (1.0f, 0.0f, z);
+ device.ImmediateEnd ();
+ device.SetWireframe (oldWireframe);
+ return;
+ }
+#endif
+
+ device.Clear(clearFlags, color, depth, stencil);
+}
+
+void SetBlendState( const DeviceBlendState* state, const ShaderLab::FloatVal& alphaRef, const ShaderLab::PropertySheet* props )
+{
+ GfxDevice& device = GetGfxDevice();
+ if (device.IsRecording())
+ device.RecordSetBlendState(state, alphaRef, props);
+ else
+ device.SetBlendState(state, alphaRef.ToFloat(props));
+}
+
+void SetMaterial( const ShaderLab::VectorVal& ambient, const ShaderLab::VectorVal& diffuse, const ShaderLab::VectorVal& specular, const ShaderLab::VectorVal& emissive, const ShaderLab::FloatVal& shininess, const ShaderLab::PropertySheet* props )
+{
+ GfxDevice& device = GetGfxDevice();
+ if (device.IsRecording())
+ device.RecordSetMaterial(ambient, diffuse, specular, emissive, shininess, props);
+ else
+ device.SetMaterial(ambient.Get(props).GetPtr(), diffuse.Get(props).GetPtr(), specular.Get(props).GetPtr(), emissive.Get(props).GetPtr(), shininess.ToFloat(props));
+}
+
+void SetColor( const ShaderLab::VectorVal& color, const ShaderLab::PropertySheet* props )
+{
+ GfxDevice& device = GetGfxDevice();
+ if (device.IsRecording())
+ device.RecordSetColor(color, props);
+ else
+ device.SetColor(color.Get(props).GetPtr());
+}
+
+void EnableFog( FogMode fogMode, const ShaderLab::FloatVal& fogStart, const ShaderLab::FloatVal& fogEnd, const ShaderLab::FloatVal& fogDensity, const ShaderLab::VectorVal& fogColor, const ShaderLab::PropertySheet* props )
+{
+ GfxDevice& device = GetGfxDevice();
+ if (device.IsRecording())
+ device.RecordEnableFog(fogMode, fogStart, fogEnd, fogDensity, fogColor, props);
+ else
+ {
+ GfxFogParams fog;
+ fog.mode = fogMode;
+ fog.color = fogColor.Get(props);
+ fog.start = fogStart.ToFloat(props);
+ fog.end = fogEnd.ToFloat(props);
+ fog.density = fogDensity.ToFloat(props);
+ device.EnableFog(fog);
+ }
+}
+
+} // namespace GfxDeviceHelper \ No newline at end of file
diff --git a/Runtime/Graphics/GraphicsHelper.h b/Runtime/Graphics/GraphicsHelper.h
new file mode 100644
index 0000000..1c23522
--- /dev/null
+++ b/Runtime/Graphics/GraphicsHelper.h
@@ -0,0 +1,98 @@
+#pragma once
+
+// Remove this when SetShaders has been inlined
+#include <Runtime/GfxDevice/GfxDevice.h>
+#include "External/shaderlab/Library/program.h"
+
+namespace ShaderLab {
+ class FloatVal;
+ struct VectorVal;
+ struct TextureBinding;
+ class PropertySheet;
+}
+
+namespace GraphicsHelper
+{
+ void Clear( UInt32 clearFlags, const float color[4], float depth, int stencil );
+
+ void SetBlendState( const DeviceBlendState* state, const ShaderLab::FloatVal& alphaRef, const ShaderLab::PropertySheet* props );
+ void SetMaterial( const ShaderLab::VectorVal& ambient, const ShaderLab::VectorVal& diffuse, const ShaderLab::VectorVal& specular, const ShaderLab::VectorVal& emissive, const ShaderLab::FloatVal& shininess, const ShaderLab::PropertySheet* props );
+ void SetColor( const ShaderLab::VectorVal& color, const ShaderLab::PropertySheet* props );
+ void EnableFog( FogMode fogMode, const ShaderLab::FloatVal& fogStart, const ShaderLab::FloatVal& fogEnd, const ShaderLab::FloatVal& fogDensity, const ShaderLab::VectorVal& fogColor, const ShaderLab::PropertySheet* props );
+
+ //TextureCombinersHandle CreateTextureCombiners( int count, const ShaderLab::TextureBinding* texEnvs, const ShaderLab::PropertySheet* props, bool hasVertexColorOrLighting, bool usesAddSpecular );
+ //void SetTextureCombiners( TextureCombinersHandle textureCombiners, const ShaderLab::PropertySheet* props );
+
+ // This is inlined until GfxDeviceClient has been moved out of GfxDevice module
+ inline void SetShaders (GfxDevice& device, ShaderLab::SubProgram* programs[kShaderTypeCount], const ShaderLab::PropertySheet* props)
+ {
+ // We do this outside of GfxDevice module to avoid dependency on ShaderLab
+
+ if (device.IsThreadable())
+ {
+ // This is a threadable device running in single-threaded mode
+ GfxThreadableDevice& threadableDevice = static_cast<GfxThreadableDevice&>(device);
+
+ // GLSL-like platforms might result in different set of shader parameters
+ // for fog modes.
+ const bool useGLStyleFogParams = (device.GetRenderer() == kGfxRendererOpenGL ||
+ device.GetRenderer() == kGfxRendererOpenGLES20Desktop ||
+ device.GetRenderer() == kGfxRendererOpenGLES20Mobile ||
+ device.GetRenderer() == kGfxRendererOpenGLES30);
+ const FogMode fogMode = useGLStyleFogParams ? device.GetFogParams().mode : kFogDisabled;
+
+ UInt8* paramsBuffer[kShaderTypeCount];
+ for (int pt = 0; pt < kShaderTypeCount; ++pt)
+ {
+ paramsBuffer[pt] = NULL;
+ ShaderLab::SubProgram* prog = programs[pt];
+ if (!prog)
+ continue;
+
+ GpuProgramParameters* params = &prog->GetParams(fogMode);
+ if (params && !params->IsReady())
+ threadableDevice.CreateShaderParameters (prog, fogMode);
+
+ int bufferSize = params->GetValuesSize();
+ if (bufferSize > 0)
+ {
+ paramsBuffer[pt] = ALLOC_TEMP_MANUAL (UInt8, bufferSize);
+ params->PrepareValues (props, paramsBuffer[pt]);
+ }
+ }
+
+ GpuProgram* gpuPrograms[kShaderTypeCount];
+ for (int pt = 0; pt < kShaderTypeCount; ++pt)
+ {
+ if (programs[pt])
+ gpuPrograms[pt] = &programs[pt]->GetGpuProgram();
+ else
+ gpuPrograms[pt] = NULL;
+ }
+
+ const GpuProgramParameters* params[kShaderTypeCount];
+ for (int pt = 0; pt < kShaderTypeCount; ++pt)
+ {
+ if (programs[pt])
+ params[pt] = &programs[pt]->GetParams(fogMode);
+ else
+ params[pt] = NULL;
+ }
+
+ threadableDevice.SetShadersThreadable (gpuPrograms, params, paramsBuffer);
+
+ for (int pt = 0; pt < kShaderTypeCount; ++pt)
+ {
+ if (paramsBuffer[pt])
+ FREE_TEMP_MANUAL(paramsBuffer[pt]);
+ }
+ }
+ else
+ {
+ // Old school device that doesn't support threadable interface,
+ // or GfxDeviceClient that wants to deal with parameters itself
+ device.SetShadersMainThread(programs, props);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/Runtime/Graphics/Image.cpp b/Runtime/Graphics/Image.cpp
new file mode 100644
index 0000000..b4ad02d
--- /dev/null
+++ b/Runtime/Graphics/Image.cpp
@@ -0,0 +1,1657 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+#include "Image.h"
+#include "S3Decompression.h"
+#include "Runtime/Allocator/MemoryMacros.h"
+#include "Runtime/Utilities/Utility.h"
+#include "Runtime/Utilities/FileUtilities.h"
+#include "Runtime/Utilities/LogAssert.h"
+#include "Runtime/Utilities/vector_utility.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "External/ProphecySDK/include/prcore/surface.hpp"
+
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+
+using namespace std;
+
+prcore::PixelFormat GetProphecyPixelFormat (TextureFormat format);
+
+void BlitCopyCompressedImage( TextureFormat format, const UInt8* src, int srcWidth, int srcHeight, UInt8* dst, int dstWidth, int dstHeight, bool fillRest )
+{
+ AssertIf( dstWidth < srcWidth );
+ AssertIf( dstHeight < srcHeight );
+
+ int blockBytes, srcBlocksX, srcBlocksY, dstBlocksX, dstBlocksY;
+
+ {
+ AssertIf( !IsCompressedDXTTextureFormat(format) && !IsCompressedETCTextureFormat(format)
+ && !IsCompressedATCTextureFormat(format) && !IsCompressedETC2TextureFormat(format)
+ && !IsCompressedEACTextureFormat(format));
+
+ blockBytes = (format == kTexFormatDXT1 || format == kTexFormatETC_RGB4 || format == kTexFormatATC_RGB4
+ || format == kTexFormatETC2_RGB || format == kTexFormatETC2_RGBA1
+ || format == kTexFormatEAC_R || format == kTexFormatEAC_R_SIGNED)
+ ? 8 : 16;
+
+ srcBlocksX = (srcWidth + 3) / 4;
+ srcBlocksY = (srcHeight + 3) / 4;
+ dstBlocksX = (dstWidth + 3) / 4;
+ dstBlocksY = (dstHeight + 3) / 4;
+ }
+
+ int srcRowBytes = srcBlocksX * blockBytes;
+ int dstRowBytes = dstBlocksX * blockBytes;
+
+ const UInt8* srcPtr = src;
+ UInt8* dstPtr = dst;
+ int y;
+ for( y = 0; y < srcBlocksY; ++y )
+ {
+ memcpy( dstPtr, srcPtr, srcRowBytes ); // copy DXT blocks
+ if( fillRest )
+ memset( dstPtr + srcRowBytes, 0, dstRowBytes - srcRowBytes ); // fill rest with black/transparent
+ srcPtr += srcRowBytes;
+ dstPtr += dstRowBytes;
+ }
+ if( fillRest )
+ memset( dstPtr, 0, dstRowBytes * (dstBlocksY-srcBlocksY) ); // fill rest with black/transparent
+}
+
+void BlitCopyCompressedDXT1ToDXT5( const UInt8* src, int srcWidth, int srcHeight, UInt8* dst, int dstWidth, int dstHeight )
+{
+ AssertIf( dstWidth < srcWidth );
+ AssertIf( dstHeight < srcHeight );
+
+ int blockBytesSrc = 8;
+ int blockBytesDst = 16;
+
+ int srcBlocksX = (srcWidth + 3) / 4;
+ int srcBlocksY = (srcHeight + 3) / 4;
+ int dstBlocksX = (dstWidth + 3) / 4;
+
+ int srcRowBytes = srcBlocksX * blockBytesSrc;
+ int dstRowBytes = dstBlocksX * blockBytesDst;
+
+ const UInt8* srcPtr = src;
+ UInt8* dstPtr = dst;
+ int y;
+ for( y = 0; y < srcBlocksY; ++y )
+ {
+ for( int x = 0; x < srcBlocksX; ++x )
+ {
+ // set alpha to opaque white
+ memset( dstPtr + x * blockBytesDst, 0xFF, blockBytesDst-blockBytesSrc );
+ // copy block
+ memcpy( dstPtr + x * blockBytesDst + blockBytesSrc, srcPtr + x * blockBytesSrc, blockBytesSrc );
+ }
+ srcPtr += srcRowBytes;
+ dstPtr += dstRowBytes;
+ }
+}
+
+
+void PadImageBorder( ImageReference& image, int sizeX, int sizeY )
+{
+ AssertIf( IsAnyCompressedTextureFormat(image.GetFormat()) );
+
+ int width = image.GetWidth();
+ int height = image.GetHeight();
+ AssertIf( sizeX < 1 || sizeY < 1 || sizeX > width || sizeY > height );
+
+ int y;
+ UInt8* rowptr = image.GetImageData();
+
+ int bpp = GetBytesFromTextureFormat(image.GetFormat());
+ AssertIf(bpp > 4); // we only support up to 4 bpp
+ UInt8 pixel[4];
+
+ // pad rightmost part by repeating last pixel in each row
+ for( y = 0; y < sizeY; ++y )
+ {
+ UInt8* ptr = rowptr + (sizeX-1) * bpp;
+
+ for( int b = 0; b < bpp; ++b )
+ pixel[b] = ptr[b];
+ ptr += bpp;
+ for( int x = sizeX; x < width; ++x )
+ {
+ for( int b = 0; b < bpp; ++b )
+ ptr[b] = pixel[b];
+ ptr += bpp;
+ }
+
+ rowptr += image.GetRowBytes();
+ }
+
+ // pad bottom part by repeating last row
+ UInt8* lastRowPtr = image.GetRowPtr( sizeY - 1 );
+ for( int b = 0; b < bpp; ++b )
+ pixel[b] = lastRowPtr[ (sizeX-1)*bpp + b ];
+
+ for( y = sizeY; y < height; ++y )
+ {
+ UInt8* ptr = rowptr;
+ memcpy( ptr, lastRowPtr, sizeX * bpp ); // copy
+ ptr += sizeX * bpp;
+ for( int x = sizeX; x < width; ++x ) // repeat last pixel
+ {
+ for( int b = 0; b < bpp; ++b )
+ ptr[b] = pixel[b];
+ ptr += bpp;
+ }
+
+ rowptr += image.GetRowBytes();
+ }
+}
+
+
+int CalculateMipMapCount3D (int width, int height, int depth)
+{
+ //AssertIf( !IsPowerOfTwo(width) || !IsPowerOfTwo(height) || !IsPowerOfTwo(depth) );
+
+ // Mip-levels for non-power-of-two textures follow OpenGL's NPOT texture rules: size is divided
+ // by two and floor'ed. This allows just to use same old code I think.
+
+ int minSizeLog2 = HighestBit (width);
+ minSizeLog2 = max (minSizeLog2, HighestBit (height));
+ minSizeLog2 = max (minSizeLog2, HighestBit (depth));
+
+ AssertIf( (width >> minSizeLog2) < 1 && (height >> minSizeLog2) < 1 && (depth >> minSizeLog2) < 1 );
+ AssertIf( (width >> minSizeLog2) > 1 && (height >> minSizeLog2) > 1 && (depth >> minSizeLog2) < 1 );
+
+ return minSizeLog2 + 1;
+}
+
+int CalculateImageSize (int width, int height, TextureFormat format)
+{
+ if (format == kTexFormatDXT1 || format == kTexFormatATC_RGB4)
+ return ((width + 3) / 4) * ((height + 3) / 4) * 8;
+ else if (format == kTexFormatDXT3 || format == kTexFormatDXT5 || format == kTexFormatATC_RGBA8)
+ return ((width + 3) / 4) * ((height + 3) / 4) * 16;
+ else if (format == kTexFormatPVRTC_RGB4 || format == kTexFormatPVRTC_RGBA4)
+ return (max(width, 8) * max(height, 8) * 4 + 7) / 8;
+ else if (format == kTexFormatPVRTC_RGB2 || format == kTexFormatPVRTC_RGBA2)
+ return (max(width, 16) * max(height, 8) * 2 + 7) / 8;
+ else if (format == kTexFormatETC_RGB4 || format == kTexFormatETC2_RGB || format == kTexFormatETC2_RGBA1
+ || format == kTexFormatEAC_R || format == kTexFormatEAC_R_SIGNED)
+ return (max(width, 4) * max(height, 4) * 4 + 7) / 8;
+ else if (format == kTexFormatETC2_RGBA8 || format == kTexFormatEAC_RG || format == kTexFormatEAC_RG_SIGNED)
+ return (max(width, 4) * max(height, 4) * 8 + 7) / 8;
+
+#define STR_(x) #x
+#define STR(x) STR_(x)
+ // The size of the ASTC block is always 128 bits = 16 bytes, width of image in blocks is ceiling(width/blockwidth)
+#define DO_ASTC(bx, by) else if( format == kTexFormatASTC_RGB_##bx##x##by || format == kTexFormatASTC_RGBA_##bx##x##by)\
+ return ( (CeilfToInt(((float)width) / ((float)bx))) * (CeilfToInt(((float)height) / ((float)by))) * 16 )
+
+ DO_ASTC(4, 4);
+ DO_ASTC(5, 5);
+ DO_ASTC(6, 6);
+ DO_ASTC(8, 8);
+ DO_ASTC(10, 10);
+ DO_ASTC(12, 12);
+
+#undef DO_ASTC
+#undef STR_
+#undef STR
+
+ // ATF Format is a container format and has no known size based on width & height
+ else if (format == kTexFormatFlashATF_RGB_DXT1 || format == kTexFormatFlashATF_RGBA_JPG || format == kTexFormatFlashATF_RGB_JPG)
+ return 0;
+ else
+ return GetRowBytesFromWidthAndFormat (width, format) * height;
+}
+
+int CalculateMipMapOffset (int width, int height, TextureFormat format, int miplevel)
+{
+ if (width == 0 || height == 0)
+ return 0;
+
+ // Allow NPOT textures as well.
+ //AssertIf (!IsPowerOfTwo (width) || !IsPowerOfTwo (height));
+ int completeSize = 0;
+ for (int i=0;i < miplevel;i++)
+ completeSize += CalculateImageSize (std::max (width >> i, 1), std::max (height >> i, 1), format);
+ return completeSize;
+}
+
+int CalculateImageMipMapSize (int width, int height, TextureFormat format)
+{
+ return CalculateMipMapOffset( width, height, format, CalculateMipMapCount3D(width, height, 1) );
+}
+
+
+
+void CreateMipMap (UInt8* inData, int width, int height, int depth, TextureFormat format)
+{
+ const int bpp = GetBytesFromTextureFormat (format);
+ int mipCount = CalculateMipMapCount3D (width, height, depth) - 1;
+ UInt8* srcPtr = inData;
+
+ UInt8* tempBuffer = NULL;
+
+ for (int mip = 0; mip < mipCount; ++mip)
+ {
+ int nextWidth = std::max(width/2,1);
+ int nextHeight = std::max(height/2,1);
+ int nextDepth = std::max(depth/2,1);
+
+ UInt8* nextMipPtr = srcPtr + width * height * depth * bpp;
+ UInt8* dstPtr = nextMipPtr;
+ if (depth > 1)
+ {
+ if (!tempBuffer)
+ tempBuffer = ALLOC_TEMP_MANUAL(UInt8, nextWidth*nextHeight*bpp+bpp);
+
+ for (int d = 0; d < nextDepth; ++d)
+ {
+ // blit two slices of this mip level into two 2x2 smaller images
+ ImageReference src1 (width, height, width * bpp, format, srcPtr);
+ srcPtr += width * height * bpp;
+ ImageReference src2 (width, height, width * bpp, format, srcPtr);
+ srcPtr += width * height * bpp;
+
+ ImageReference dst1 (nextWidth, nextHeight, nextWidth * bpp, format, dstPtr); // one directly into dst
+ dst1.BlitImage (src1, ImageReference::BLIT_BILINEAR_SCALE);
+ ImageReference dst2 (nextWidth, nextHeight, nextWidth * bpp, format, tempBuffer); // one into temp buffer
+ dst2.BlitImage (src2, ImageReference::BLIT_BILINEAR_SCALE);
+
+ // now average the two smaller images
+ for (int i = 0; i < nextWidth*nextHeight*bpp; ++i)
+ {
+ unsigned b1 = dstPtr[i];
+ unsigned b2 = tempBuffer[i];
+ dstPtr[i] = (b1 + b2) / 2;
+ }
+
+ dstPtr += nextWidth * nextHeight * bpp;
+ }
+ }
+ else
+ {
+ ImageReference src (width, height, width * bpp, format, srcPtr);
+ ImageReference dst (nextWidth, nextHeight, nextWidth * bpp, format, dstPtr);
+ dst.BlitImage (src, ImageReference::BLIT_BILINEAR_SCALE);
+ }
+
+ srcPtr = nextMipPtr;
+ width = nextWidth;
+ height = nextHeight;
+ depth = nextDepth;
+ }
+
+ FREE_TEMP_MANUAL(tempBuffer);
+}
+
+
+
+ImageReference::ImageReference (int width, int height, int rowbytes, TextureFormat format, void* image)
+{
+ m_Width = width;
+ m_Height = height;
+ m_Format = format;
+ m_RowBytes = rowbytes;
+
+ if (image && CheckImageFormatValid (width, height, format))
+ m_Image = reinterpret_cast<UInt8*> (image);
+ else
+ m_Image = NULL;
+}
+
+ImageReference::ImageReference (int width, int height, TextureFormat format)
+{
+ m_Width = width;
+ m_Height = height;
+ m_Format = format;
+ m_RowBytes = GetRowBytesFromWidthAndFormat (m_Width, format);
+ m_Image = NULL;
+}
+
+void ImageReference::FlipImageY ()
+{
+ if (m_Image)
+ {
+ prcore::Surface srcSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ srcSurface.FlipImageY ();
+ }
+}
+
+#if UNITY_EDITOR
+static inline void SwapPixels( UInt8* a, UInt8* b, int bpp ) {
+ for( int i = 0; i < bpp; ++i ) {
+ UInt8 t = a[i];
+ a[i] = b[i];
+ b[i] = t;
+ }
+}
+
+void ImageReference::FlipImageX ()
+{
+ if (!m_Image)
+ return;
+
+ const int width = GetWidth();
+ const int height = GetHeight();
+ const TextureFormat format = GetFormat();
+ if( IsAnyCompressedTextureFormat(format) ) {
+ AssertString("FlipImageX does not work on compressed formats");
+ return;
+ }
+ const int bpp = GetBytesFromTextureFormat(format);
+ for( int y = 0; y < height; ++y ) {
+ UInt8* rowFirst = GetRowPtr(y);
+ UInt8* rowLast = rowFirst + (width-1) * bpp;
+ for( int x = 0; x < width/2; ++x ) {
+ SwapPixels( rowFirst, rowLast, bpp );
+ rowFirst += bpp;
+ rowLast -= bpp;
+ }
+ }
+}
+#endif
+
+
+void ImageReference::BlitImage (const ImageReference& source, BlitMode mode)
+{
+ if (m_Image && source.m_Image)
+ {
+ prcore::Surface srcSurface (source.GetWidth (), source.GetHeight (), source.GetRowBytes (), GetProphecyPixelFormat (source.GetFormat ()), source.GetImageData ());
+ prcore::Surface dstSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ dstSurface.BlitImage (srcSurface, (prcore::Surface::BlitMode)mode);
+ }
+}
+
+void ImageReference::BlitImage (int x, int y, const ImageReference& source)
+{
+ if (source.m_Image && m_Image)
+ {
+ prcore::Surface srcSurface (source.GetWidth (), source.GetHeight (), source.GetRowBytes (), GetProphecyPixelFormat (source.GetFormat ()), source.GetImageData ());
+ prcore::Surface dstSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ dstSurface.BlitImage (x, y, srcSurface);
+ }
+}
+
+void ImageReference::ClearImage (const ColorRGBA32& color, ClearMode mode)
+{
+ if (m_Image)
+ {
+ prcore::color32 c;
+ c.r = color.r;
+ c.g = color.g;
+ c.b = color.b;
+ c.a = color.a;
+ prcore::Surface dstSurface (GetWidth (), GetHeight (), GetRowBytes (), GetProphecyPixelFormat (GetFormat ()), GetImageData ());
+ dstSurface.ClearImage (c, (prcore::Surface::ClearMode)mode);
+ }
+}
+
+ImageReference ImageReference::ClipImage (int x, int y, int width, int height) const
+{
+ if (m_Image)
+ {
+ x = clamp<int> (x, 0, m_Width);
+ y = clamp<int> (y, 0, m_Height);
+ width = min<int> (m_Width, x + width) - x;
+ height = min<int> (m_Height, y + height) - y;
+ width = max<int> (0, width);
+ height = max<int> (0, height);
+
+ int bytesPerPixel = GetBytesFromTextureFormat( m_Format );
+ return ImageReference( width, height, m_RowBytes, m_Format, m_Image + bytesPerPixel * x + m_RowBytes * y );
+ }
+ else
+ return ImageReference( 0, 0, 0, m_Format, NULL );
+}
+
+bool ImageReference::NeedsReformat(int width, int height, TextureFormat format) const
+{
+ // TODO : it actually doesn't need to reallocate when downsizing!
+ return width != m_Width || height != m_Height || format != m_Format;
+}
+
+Image::Image (int width, int height, TextureFormat format)
+{
+ m_Height = height;
+ m_Width = width;
+ m_Format = format;
+ int bpp = GetBytesFromTextureFormat( m_Format );
+ m_RowBytes = m_Width * bpp;
+ if (CheckImageFormatValid (width, height, format))
+ m_Image = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, m_RowBytes * m_Height + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+ else
+ m_Image = NULL;
+}
+
+Image::Image (int width, int height, int rowbytes, TextureFormat format, void* image)
+{
+ m_Height = height;
+ m_Width = width;
+ m_Format = format;
+ int bpp = GetBytesFromTextureFormat( m_Format );
+ m_RowBytes = m_Width * bpp;
+ if( CheckImageFormatValid (width, height, format) )
+ m_Image = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, m_RowBytes * m_Height + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+ else
+ m_Image = NULL;
+
+ if (image && m_Image)
+ BlitImage (ImageReference (width, height, rowbytes, format, image), BLIT_COPY);
+}
+
+void Image::SetImage(SInt32 width, SInt32 height, UInt32 format, bool shrinkAllowed)
+{
+ // TODO : this size is wrong if it has been shrunk already, but we don't care at the moment,
+ // because we use it for calculation of mipmaps only
+ const int oldSize = m_RowBytes * m_Height + GetBytesFromTextureFormat( m_Format );
+
+ m_Width = width;
+ m_Height = height;
+ m_Format = format;
+ const int bpp = GetBytesFromTextureFormat( m_Format );
+ m_RowBytes = m_Width * bpp;
+
+ const int newSize = m_RowBytes * m_Height + bpp;
+
+ if ((!shrinkAllowed && (oldSize < newSize)) ||
+ (shrinkAllowed && (oldSize != newSize)))
+ {
+ UNITY_FREE(kMemNewDelete, m_Image);
+ m_Image = NULL;
+ if( m_Format != 0 && CheckImageFormatValid (m_Width, m_Height, m_Format) )
+ m_Image = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, m_RowBytes * m_Height + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+ }
+}
+
+void Image::SetImage(const ImageReference& src, bool shrinkAllowed)
+{
+ if (this == &src)
+ return;
+
+ SetImage (src.GetWidth(), src.GetHeight(), src.GetFormat(), shrinkAllowed);
+ BlitImage (src, BLIT_COPY);
+}
+
+void Image::ReformatImage (const ImageReference& image, int width, int height, TextureFormat format, BlitMode mode)
+{
+ AssertIf(!image.NeedsReformat(width, height, format));
+
+ int bpp = GetBytesFromTextureFormat(format);
+ int newRowBytes = width * bpp;
+ UInt8* newImageData = NULL;
+ if (CheckImageFormatValid (width, height, format))
+ newImageData = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, height * newRowBytes + GetMaxBytesPerPixel( m_Format ), kImageDataAlignment); // allocate one pixel more for bilinear blits
+
+ ImageReference newImage (width, height, newRowBytes, format, newImageData);
+ newImage.BlitImage (image, mode);
+ UNITY_FREE(kMemNewDelete, m_Image);
+
+ m_Height = height;
+ m_Width = width;
+ m_Format = format;
+ m_RowBytes = newRowBytes;
+ m_Image = newImageData;
+}
+
+void Image::ReformatImage (int width, int height, TextureFormat format, BlitMode mode)
+{
+ if (!NeedsReformat(width, height, format))
+ return;
+
+ ReformatImage (*this, width, height, format, mode);
+}
+
+bool ImageReference::IsValidImage () const
+{
+ return m_Image != NULL && CheckImageFormatValid (GetWidth(), GetHeight(), GetFormat());
+}
+
+bool CheckImageFormatValid (int width, int height, TextureFormat format)
+{
+ if (width > 0 && height > 0 && format > 0 && (format <= kTexFormatBGR24 || format == kTexFormatBGRA32 || format == kTexFormatRGBA4444))
+ return true;
+ else
+ {
+ if (width < 0) {
+ AssertString ("Image invalid width!");
+ }
+ if (height < 0) {
+ AssertString ("Image invalid height!");
+ }
+ if (format > kTexFormatBGR24 && format != kTexFormatRGBA4444 && format != kTexFormatBGRA32) {
+ AssertString ("Image invalid format!");
+ }
+ return false;
+ }
+}
+
+prcore::PixelFormat GetProphecyPixelFormat (TextureFormat format)
+{
+ switch (format)
+ {
+ case kTexFormatAlpha8:
+ return prcore::PixelFormat (8,0,0xff);
+ case kTexFormatARGB4444:
+ return prcore::PixelFormat (16,0x00000f00,0x000000f0,0x0000000f,0x0000f000);
+ case kTexFormatRGBA4444:
+ return prcore::PixelFormat (16,0x0000f000,0x00000f00,0x000000f0,0x0000000f);
+ case kTexFormatRGB24:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (24,0x00ff0000,0x0000ff00,0x000000ff,0x00000000);
+ #else
+ return prcore::PixelFormat (24,0x000000ff,0x0000ff00,0x00ff0000,0x00000000);
+ #endif
+ case kTexFormatRGBA32:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (32,0xff000000,0x00ff0000,0x0000ff00,0x000000ff);
+ #else
+ return prcore::PixelFormat (32,0x000000ff,0x0000ff00,0x00ff0000,0xff000000);
+ #endif
+ case kTexFormatBGRA32:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (32,0x0000ff00,0x00ff0000,0xff000000,0x000000ff);
+ #else
+ return prcore::PixelFormat (32,0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
+ #endif
+ case kTexFormatARGB32:
+ #if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (32,0x00ff0000,0x0000ff00,0x000000ff,0xff000000);
+ #else
+ return prcore::PixelFormat (32,0x0000ff00,0x00ff0000,0xff000000,0x000000ff);
+ #endif
+ case kTexFormatARGBFloat:
+ return prcore::PixelFormat (128, 2, 4, 8, 1, prcore::PIXELFORMAT_FLOAT32);
+ case kTexFormatRGB565:
+ return prcore::PixelFormat (16,0x0000f800,0x000007e0,0x0000001f,0x00000000);
+ case kTexFormatBGR24:
+#if UNITY_BIG_ENDIAN
+ return prcore::PixelFormat (24,0x00ff0000,0x0000ff00,0x000000ff,0x00000000);
+#else
+ return prcore::PixelFormat (24,0x000000ff,0x0000ff00,0x00ff0000,0x00000000);
+#endif
+ default:
+ DebugAssertIf( true );
+ return prcore::PixelFormat ();
+ }
+}
+
+bool operator == (const ImageReference& lhs, const ImageReference& rhs)
+{
+ if (lhs.GetWidth () != rhs.GetWidth ()) return false;
+ if (lhs.GetHeight () != rhs.GetHeight ()) return false;
+ if (lhs.GetRowBytes () != rhs.GetRowBytes ()) return false;
+ if (lhs.GetFormat () != rhs.GetFormat ()) return false;
+ UInt8* lhsData = lhs.GetImageData ();
+ UInt8* rhsData = rhs.GetImageData ();
+ if (lhsData == NULL || rhsData == NULL)
+ return lhsData == rhsData;
+
+ int size = lhs.GetRowBytes () * lhs.GetHeight ();
+ for (int i=0;i<size / 4;i++)
+ {
+ if (*reinterpret_cast<UInt32*> (lhsData) != *reinterpret_cast<UInt32*> (rhsData))
+ return false;
+ lhsData += 4; rhsData += 4;
+ }
+ for (int i=0;i<size % 4;i++)
+ {
+ if (*lhsData != *rhsData)
+ return false;
+ lhsData++; rhsData++;
+ }
+ return true;
+}
+
+void SwizzleARGB32ToBGRA32 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=4)
+ {
+ UInt8 swizzle[4];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[3];
+ dst[1] = swizzle[2];
+ dst[2] = swizzle[1];
+ dst[3] = swizzle[0];
+ }
+}
+
+void SwizzleRGBA32ToBGRA32 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=4)
+ {
+ UInt8 swizzle[4];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[2];
+ dst[1] = swizzle[1];
+ dst[2] = swizzle[0];
+ dst[3] = swizzle[3];
+ }
+}
+
+void SwizzleBGRAToRGBA32 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=4)
+ {
+ UInt8 swizzle[4];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[2];
+ dst[1] = swizzle[1];
+ dst[2] = swizzle[0];
+ dst[3] = swizzle[3];
+ }
+}
+
+void SwizzleRGB24ToBGR24 (UInt8* bytes, int imageSize)
+{
+ for (int i=0;i<imageSize;i+=3)
+ {
+ UInt8 swizzle[3];
+ UInt8* dst = bytes + i;
+ memcpy(swizzle, dst, sizeof(swizzle));
+
+ dst[0] = swizzle[2];
+ dst[1] = swizzle[1];
+ dst[2] = swizzle[0];
+ }
+}
+
+void Premultiply( ImageReference& image )
+{
+ if (image.GetFormat() == kTexFormatRGBA32)
+ {
+ UInt8* data = image.GetImageData();
+ int size = image.GetRowBytes() * image.GetHeight() / 4;
+ for (int i=0;i<size;i++)
+ {
+ UInt8* rgba = data + 4 * i;
+ rgba[0] = ((int)rgba[0] * (int)rgba[3]) / 255;
+ rgba[1] = ((int)rgba[1] * (int)rgba[3]) / 255;
+ rgba[2] = ((int)rgba[2] * (int)rgba[3]) / 255;
+ }
+ }
+ else if (image.GetFormat() == kTexFormatARGB32)
+ {
+ UInt8* data = image.GetImageData();
+ int size = image.GetRowBytes() * image.GetHeight() / 4;
+ for (int i=0;i<size;i++)
+ {
+ UInt8* rgba = data + 4 * i;
+ rgba[1] = ((int)rgba[1] * (int)rgba[0]) / 255;
+ rgba[2] = ((int)rgba[2] * (int)rgba[0]) / 255;
+ rgba[3] = ((int)rgba[3] * (int)rgba[0]) / 255;
+ }
+ }
+ else
+ {
+ ErrorString("Unsupported");
+ }
+}
+
+inline UInt32 DecodeRGBMChannel(UInt32 val, UInt32 mult, UInt32 mask, UInt8 offset)
+{
+ UInt32 channel = (val & mask) >> offset;
+ channel *= mult;
+ channel /= 255 * 2;
+ channel = channel > 255 ? 255 : channel;
+ channel <<= offset;
+
+ return channel;
+}
+
+// Decodes RGBM encoded lightmaps into doubleLDR in place.
+// Handles kTexFormatRGBA32 and kTexFormatARGB32 data.
+void DecodeRGBM(int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf)
+{
+ UInt8* rowData = data;
+ uint32 rmask = pf.GetRedMask();
+ uint32 gmask = pf.GetGreenMask();
+ uint32 bmask = pf.GetBlueMask();
+ uint32 amask = pf.GetAlphaMask();
+ uint8 roffset = (int)pf.GetRedOffset() - (int)pf.GetRedBits() + 1;
+ uint8 goffset = (int)pf.GetGreenOffset() - (int)pf.GetGreenBits() + 1;
+ uint8 boffset = (int)pf.GetBlueOffset() - (int)pf.GetBlueBits() + 1;
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+ uint32 alpha = ((val & amask) >> aoffset) * kRGBMMaxRange;
+ pixel[c] = DecodeRGBMChannel(val, alpha, rmask, roffset) +
+ DecodeRGBMChannel(val, alpha, gmask, goffset) +
+ DecodeRGBMChannel(val, alpha, bmask, boffset) +
+ (255 << aoffset);
+ }
+ rowData += pitch;
+ }
+}
+
+
+void SetAlphaChannel(int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf, UInt8 alpha)
+{
+ UInt8* rowData = data;
+ uint32 amask = pf.GetAlphaMask();
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+ uint32 properAlpha = (alpha << aoffset);
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+
+ // merge bits from two values according to a mask,
+ // equivalent to: (val & ~amask) | (properAlpha & amask)
+ pixel[c] = val ^ ((val ^ properAlpha) & amask);
+ }
+ rowData += pitch;
+ }
+}
+
+void SetAlphaToRedChannel (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf)
+{
+ UInt8* rowData = data;
+ uint32 rmask = pf.GetRedMask();
+ uint32 amask = pf.GetAlphaMask();
+ uint8 roffset = (int)pf.GetRedOffset() - (int)pf.GetRedBits() + 1;
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+ uint32 red = (val & rmask) >> roffset;
+ pixel[c] = (val & ~amask) | (red << aoffset);
+ }
+ rowData += pitch;
+ }
+}
+
+inline UInt8 XenonToNormalSRGBTexture (UInt32 value)
+{
+ value = UInt32(LinearToGammaSpace(GammaToLinearSpaceXenon (value / 255.0F)) * 255.0F);
+ return std::min<UInt32>(value, 255);
+}
+
+void XenonToNormalSRGBTexture (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf)
+{
+ UInt8* rowData = data;
+ uint32 rmask = pf.GetRedMask();
+ uint32 gmask = pf.GetGreenMask();
+ uint32 bmask = pf.GetBlueMask();
+ uint32 amask = pf.GetAlphaMask();
+ uint8 roffset = (int)pf.GetRedOffset() - (int)pf.GetRedBits() + 1;
+ uint8 goffset = (int)pf.GetGreenOffset() - (int)pf.GetGreenBits() + 1;
+ uint8 boffset = (int)pf.GetBlueOffset() - (int)pf.GetBlueBits() + 1;
+ uint8 aoffset = (int)pf.GetAlphaOffset() - (int)pf.GetAlphaBits() + 1;
+
+ for( int r = 0; r < height; ++r )
+ {
+ uint32* pixel = (uint32*)rowData;
+ for( int c = 0; c < width; ++c )
+ {
+ uint32 val = pixel[c];
+ uint32 r = (val & rmask) >> roffset;
+ uint32 g = (val & gmask) >> goffset;
+ uint32 b = (val & bmask) >> boffset;
+ uint32 a = (val & amask) >> aoffset;
+
+ r = XenonToNormalSRGBTexture (r);
+ g = XenonToNormalSRGBTexture (g);
+ b = XenonToNormalSRGBTexture (b);
+
+ pixel[c] = (r << roffset) | (g << goffset) | (b << boffset) | (a << aoffset);
+ }
+ rowData += pitch;
+ }
+}
+
+
+
+
+// --------------------------------------------------------------------------
+
+
+const char* kUnsupportedGetPixelOpFormatMessage = "Unsupported texture format - needs to be ARGB32, RGBA32, BGRA32, RGB24, Alpha8 or DXT";
+const char* kUnsupportedSetPixelOpFormatMessage = "Unsupported texture format - needs to be ARGB32, RGBA32, RGB24 or Alpha8";
+
+inline int RepeatInt (int x, int width)
+{
+ if (width == 0) return 0;
+ if( x < 0 ) {
+ int times = -(x/width)+1;
+ x += width * times;
+ }
+ x = x % width;
+ return x;
+}
+
+inline int TextureWrap(int x, int max, TextureWrapMode wrapMode)
+{
+ if (wrapMode == kTexWrapRepeat)
+ {
+ return RepeatInt(x, max);
+ }
+ else
+ {
+ if (max <= 0)
+ return 0;
+ return clamp(x, 0, max-1);
+ }
+}
+
+void SetImagePixel (ImageReference& image, int x, int y, TextureWrapMode wrap, const ColorRGBAf& color)
+{
+ int width = image.GetWidth();
+ int height = image.GetHeight();
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ {
+ x = TextureWrap(x, width, wrap);
+ y = TextureWrap(y, height, wrap);
+ }
+
+ if (image.GetFormat() == kTexFormatARGB32)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x * 4;
+ pixel[1] = RoundfToIntPos(clamp01(color.r) * 255.0);
+ pixel[2] = RoundfToIntPos(clamp01(color.g) * 255.0);
+ pixel[3] = RoundfToIntPos(clamp01(color.b) * 255.0);
+ pixel[0] = RoundfToIntPos(clamp01(color.a) * 255.0);
+ }
+ else if (image.GetFormat() == kTexFormatRGBA32)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x * 4;
+ pixel[0] = RoundfToIntPos(clamp01(color.r) * 255.0);
+ pixel[1] = RoundfToIntPos(clamp01(color.g) * 255.0);
+ pixel[2] = RoundfToIntPos(clamp01(color.b) * 255.0);
+ pixel[3] = RoundfToIntPos(clamp01(color.a) * 255.0);
+ }
+ else if (image.GetFormat() == kTexFormatRGB24)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x * 3;
+ pixel[0] = RoundfToIntPos(clamp01(color.r) * 255.0);
+ pixel[1] = RoundfToIntPos(clamp01(color.g) * 255.0);
+ pixel[2] = RoundfToIntPos(clamp01(color.b) * 255.0);
+ }
+ else if (image.GetFormat() == kTexFormatRGB565)
+ {
+ UInt16* pixel = (UInt16 *)(image.GetRowPtr(y) + x * 2);
+ UInt16 r = (UInt16)(RoundfToIntPos( clamp01(color.r) * 31.0f));
+ UInt16 g = (UInt16)(RoundfToIntPos( clamp01(color.g) * 63.0f));
+ UInt16 b = (UInt16)(RoundfToIntPos( clamp01(color.b) * 31.0f));
+ pixel[0] = r<<11 | g<<5 | b;
+ }
+ else if (image.GetFormat() == kTexFormatAlpha8)
+ {
+ UInt8* pixel = image.GetRowPtr(y) + x;
+ pixel[0] = RoundfToIntPos(clamp01(color.a) * 255.0);
+ }
+ else
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ }
+}
+
+
+ColorRGBA32 GetImagePixel (UInt8* data, int width, int height, TextureFormat format, TextureWrapMode wrap, int x, int y)
+{
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ {
+ x = TextureWrap (x, width, wrap);
+ y = TextureWrap (y, height, wrap);
+ }
+
+ if( IsCompressedDXTTextureFormat(format) )
+ {
+ int texWidth = std::max (width, 4);
+
+ UInt32 uncompressed [16];
+ int blockBytes = (format == kTexFormatDXT1 ? 8 : 16);
+
+ const UInt8 *pos = data + ((x/4) + (y/4)*(texWidth/4)) * blockBytes;
+ DecompressNativeTextureFormat (format, 4, 4, (const UInt32*)pos, 4, 4, uncompressed);
+
+ const UInt8* pixel = (const UInt8*)(uncompressed + x%4 + 4* (y%4));
+ return ColorRGBA32(pixel[0], pixel[1], pixel[2], pixel[3]);
+ }
+ else if( IsAnyCompressedTextureFormat (format) )
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ }
+ else
+ {
+ ImageReference image (width, height, GetRowBytesFromWidthAndFormat(width, format), format, data);
+ const UInt8* pixel;
+ if (format == kTexFormatARGB32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ return ColorRGBA32 (pixel[1], pixel[2], pixel[3], pixel[0]);
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ return ColorRGBA32 (pixel[0], pixel[1], pixel[2], pixel[3]);
+ }
+ else if (format == kTexFormatBGRA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ return ColorRGBA32 (pixel[2], pixel[1], pixel[0], pixel[3]);
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ pixel = image.GetRowPtr(y) + x * 3;
+ return ColorRGBA32 (pixel[0], pixel[1], pixel[2], 255);
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ pixel = image.GetRowPtr(y) + x;
+ return ColorRGBA32 (255, 255, 255, pixel[0]);
+ }
+ else if (format == kTexFormatRGBA4444)
+ {
+ pixel = image.GetRowPtr(y) + x * 2;
+ return ColorRGBA32(
+ (pixel[1] & 0xF0) << 4 | (pixel[1] & 0xF0),
+ (pixel[1] & 0x0F) << 4 | (pixel[1] & 0x0F),
+ (pixel[0] & 0xF0) << 4 | (pixel[0] & 0xF0),
+ (pixel[0] & 0x0F) << 4 | (pixel[0] & 0x0F)
+ );
+ }
+ else if (format == kTexFormatARGB4444)
+ {
+ pixel = image.GetRowPtr(y) + x * 2;
+ return ColorRGBA32(
+ (pixel[1] & 0x0F) << 4 | (pixel[1] & 0x0F),
+ (pixel[0] & 0xF0) << 4 | (pixel[0] & 0xF0),
+ (pixel[0] & 0x0F) << 4 | (pixel[0] & 0x0F),
+ (pixel[1] & 0xF0) << 4 | (pixel[1] & 0xF0)
+ );
+ }
+ else if (format == kTexFormatRGB565)
+ {
+ pixel = image.GetRowPtr(y) + x * 2;
+ UInt16 c = *reinterpret_cast<const UInt16*>(pixel);
+ UInt8 r = (UInt8)((c >> 11)&0x1F);
+ UInt8 g = (UInt8)((c >> 5)&0x3F);
+ UInt8 b = (UInt8)((c >> 0)&0x1F);
+ return ColorRGBA32 (r<<3|r>>2, g<<2|g>>4, b<<3|b>>2, 255);
+ }
+ else
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ }
+ }
+ return ColorRGBA32(255,255,255,255);
+}
+
+ColorRGBAf GetImagePixelBilinear (UInt8* data, int width, int height, TextureFormat format, TextureWrapMode wrap, float u, float v)
+{
+ u *= width;
+ v *= height;
+
+ int xBase = FloorfToInt(u);
+ int yBase = FloorfToInt(v);
+
+ float s = u - xBase;
+ float t = v - yBase;
+
+ ColorRGBAf colors[4];
+
+ if (IsCompressedDXTTextureFormat(format))
+ {
+ if (xBase < 0 || xBase+1 >= width || yBase < 0 || yBase+1 >= height)
+ {
+ for (int i=0;i<4;i++)
+ {
+ int x = xBase;
+ int y = yBase;
+ if (i & 1)
+ x++;
+ if (i & 2)
+ y++;
+
+ x = TextureWrap(x, width, wrap);
+ y = TextureWrap(y, height, wrap);
+
+ colors[i] = GetImagePixel (data, width, height, format, wrap, x, y);
+ }
+ }
+ else
+ {
+ GetImagePixelBlock (data, width, height, format, xBase, yBase, 2, 2, colors);
+ }
+ }
+ else if( IsAnyCompressedTextureFormat(format) )
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+ }
+ else
+ {
+ ImageReference image (width, height, GetRowBytesFromWidthAndFormat(width, format), format, data);
+ for (int i=0;i<4;i++)
+ {
+ int x = xBase;
+ int y = yBase;
+ if (i & 1)
+ x++;
+ if (i & 2)
+ y++;
+
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ {
+ x = TextureWrap(x, width, wrap);
+ y = TextureWrap(y, height, wrap);
+ }
+
+ UInt8* pixel;
+ if (format == kTexFormatARGB32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ colors[i] = ColorRGBA32 (pixel[1], pixel[2], pixel[3], pixel[0]);
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ colors[i] = ColorRGBA32 (pixel[0], pixel[1], pixel[2], pixel[3]);
+ }
+ else if (format == kTexFormatBGRA32)
+ {
+ pixel = image.GetRowPtr(y) + x * 4;
+ colors[i] = ColorRGBA32 (pixel[2], pixel[1], pixel[0], pixel[3]);
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ pixel = image.GetRowPtr(y) + x * 3;
+ colors[i] = ColorRGBA32 (pixel[0], pixel[1], pixel[2], 255);
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ pixel = image.GetRowPtr(y) + x;
+ colors[i] = ColorRGBA32 (255, 255, 255, pixel[0]);
+ }
+ else
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+ }
+ }
+ }
+ ColorRGBAf a = Lerp(colors[0], colors[1], s);
+ ColorRGBAf b = Lerp(colors[2], colors[3], s);
+ return Lerp(a, b, t);
+}
+
+bool GetImagePixelBlock (UInt8* data, int dataWidth, int dataHeight, TextureFormat format, int x, int y, int blockWidth, int blockHeight, ColorRGBAf* outColors)
+{
+ // Checks
+ if (blockWidth <= 0 || blockHeight <= 0)
+ {
+ ErrorString ("Width and height must be positive");
+ return false;
+ }
+
+ // TODO: repeat/crop support
+ if (x < 0 || y < 0 || x + blockWidth < 0 || y + blockHeight < 0 || x + blockWidth > dataWidth || y + blockHeight > dataHeight)
+ {
+ char mad[255];
+
+ if (x < 0)
+ sprintf(mad, "Texture rectangle is out of bounds (%d < 0)", x);
+ if (y < 0)
+ sprintf(mad, "Texture rectangle is out of bounds (%d < 0)", y);
+ if (x + blockWidth > dataWidth)
+ sprintf(mad, "Texture rectangle is out of bounds (%d + %d > %d)", x, blockWidth, dataWidth);
+ if (y + blockHeight > dataHeight)
+ sprintf(mad, "Texture rectangle is out of bounds (%d + %d > %d)", y, blockHeight, dataHeight);
+
+ ErrorString(mad);
+ return false;
+ }
+
+ if (IsCompressedDXTTextureFormat(format))
+ {
+ int texWidth = std::max (dataWidth, 4);
+
+ int paddedWidth = x+blockWidth-(x&~3);
+ if(paddedWidth % 4)
+ paddedWidth = (paddedWidth&~3) + 4;
+ int paddedHeight = y+blockHeight-(y&~3);
+ if(paddedHeight % 4)
+ paddedHeight = (paddedHeight&~3) + 4;
+
+ UInt32* uncompressed;
+ ALLOC_TEMP(uncompressed, UInt32, paddedWidth * paddedHeight);
+
+ int blockBytes = (format == kTexFormatDXT1 ? 8 : 16);
+
+ for(int line = 0; line< paddedHeight; line+=4)
+ {
+ UInt8 *pos = data + ((x/4) + ((y+line)/4)*(texWidth/4)) * blockBytes;
+ DecompressNativeTextureFormat(format, paddedWidth, 4, (UInt32*)pos, paddedWidth, 4, uncompressed+(line*paddedWidth));
+ }
+
+ ColorRGBAf* dest = outColors;
+ const UInt8* pixelRow = (UInt8*)(uncompressed + x%4 + (y%4)*paddedWidth);
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[0], pixel[1], pixel[2], pixel[3]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += paddedWidth * 4;
+ }
+ }
+ else
+ {
+ ImageReference image (dataWidth, dataHeight, GetRowBytesFromWidthAndFormat(dataWidth, format), format, data);
+
+ ColorRGBAf* dest = outColors;
+
+ if (format == kTexFormatARGB32)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[1], pixel[2], pixel[3], pixel[0]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[0], pixel[1], pixel[2], pixel[3]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatBGRA32)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[2], pixel[1], pixel[0], pixel[3]);
+ pixel += 4;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x * 3;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32(pixel[0], pixel[1], pixel[2], 255);
+ pixel += 3;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ *dest = ColorRGBA32 (255, 255, 255, pixel[0]);
+ pixel += 1;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB565)
+ {
+ const UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ const UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ UInt16 c = (UInt16)pixel[0] | ((UInt16)pixel[1])<<8;
+ UInt8 r = (UInt8)((c >> 11)&31);
+ UInt8 g = (UInt8)((c >> 5)&63);
+ UInt8 b = (UInt8)((c >> 0)&31);
+
+ *dest = ColorRGBA32 (r<<3|r>>2, g<<2|g>>4, b<<3|b>>2, 255);
+ pixel += 2;
+ ++dest;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else
+ {
+ ErrorString(kUnsupportedGetPixelOpFormatMessage);
+ return false;
+ }
+ }
+ return true;
+}
+
+
+bool SetImagePixelBlock (UInt8* data, int dataWidth, int dataHeight, TextureFormat format, int x, int y, int blockWidth, int blockHeight, int pixelCount, const ColorRGBAf* pixels)
+{
+ if (IsAnyCompressedTextureFormat(format))
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ return false;
+ }
+ if (blockWidth <= 0 || blockHeight <= 0)
+ {
+ ErrorString ("Width and height must be positive");
+ return false;
+ }
+
+ int tmp = blockWidth * blockHeight;
+ if (blockHeight != tmp / blockWidth || blockWidth * blockHeight > pixelCount ) // check for overflow as well
+ {
+ ErrorString ("Array size must be at least width*height");
+ return false;
+ }
+
+ if (x < 0 || y < 0 || x + blockWidth < 0 || y + blockHeight < 0 || x + blockWidth > dataWidth || y + blockHeight > dataHeight)
+ {
+ ErrorString ("Texture rectangle is out of bounds");
+ return false;
+ }
+
+ ImageReference image (dataWidth, dataHeight, GetRowBytesFromWidthAndFormat(dataWidth, format), format, data);
+
+ if (format == kTexFormatARGB32)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[1] = NormalizedToByte( pixels->r);
+ pixel[2] = NormalizedToByte( pixels->g);
+ pixel[3] = NormalizedToByte( pixels->b);
+ pixel[0] = NormalizedToByte( pixels->a);
+ pixel += 4;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGBA32)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x * 4;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[0] = NormalizedToByte( pixels->r);
+ pixel[1] = NormalizedToByte( pixels->g);
+ pixel[2] = NormalizedToByte( pixels->b);
+ pixel[3] = NormalizedToByte( pixels->a);
+ pixel += 4;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB24)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x * 3;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[0] = NormalizedToByte(pixels->r);
+ pixel[1] = NormalizedToByte(pixels->g);
+ pixel[2] = NormalizedToByte(pixels->b);
+ pixel += 3;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatAlpha8)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt8* pixel = pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ pixel[0] = RoundfToIntPos( clamp01(pixels->a) * 255.0f );
+ pixel += 1;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else if (format == kTexFormatRGB565)
+ {
+ UInt8* pixelRow = image.GetRowPtr(y) + x;
+ for( int iy = 0; iy < blockHeight; ++iy )
+ {
+ UInt16* pixel = (UInt16 *)pixelRow;
+ for( int ix = 0; ix < blockWidth; ++ix )
+ {
+ UInt16 r = (UInt16)(RoundfToIntPos(clamp01(pixels->r) * 31.0f));
+ UInt16 g = (UInt16)(RoundfToIntPos(clamp01(pixels->g) * 63.0f));
+ UInt16 b = (UInt16)(RoundfToIntPos(clamp01(pixels->b) * 31.0f));
+ pixel[0] = r<<11 | g<<5 | b;
+ pixel += 1;
+ ++pixels;
+ }
+ pixelRow += image.GetRowBytes();
+ }
+ }
+ else
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ return false;
+ }
+
+ return true;
+}
+
+
+
+// --------------------------------------------------------------------------
+
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+
+SUITE (ImageOpsTests)
+{
+
+TEST (RepeatInt)
+{
+ // valdemar: seems like a CW compiler bug, error: (10139) division by 0
+#if !UNITY_WII
+ // handle zero width
+ CHECK_EQUAL (0, RepeatInt (7,0));
+#endif
+ // handle positive args
+ CHECK_EQUAL (7, RepeatInt (7,13));
+ CHECK_EQUAL (0, RepeatInt (13,13));
+ CHECK_EQUAL (1, RepeatInt (170,13));
+ // handle negative args
+ CHECK_EQUAL (12, RepeatInt (-1,13));
+ CHECK_EQUAL (0, RepeatInt (-13,13));
+}
+
+TEST (TextureWrap)
+{
+ // valdemar: seems like a CW compiler bug, error: (10139) division by 0
+#if !UNITY_WII
+ // handle zero width
+ CHECK_EQUAL (0, TextureWrap (7,0,kTexWrapClamp));
+ CHECK_EQUAL (0, TextureWrap (7,0,kTexWrapRepeat));
+#endif
+ // repeat mode
+ CHECK_EQUAL (7, TextureWrap (7,13,kTexWrapRepeat));
+ CHECK_EQUAL (1, TextureWrap (170,13,kTexWrapRepeat));
+ CHECK_EQUAL (12, TextureWrap (-1,13,kTexWrapRepeat));
+ // clamp mode
+ CHECK_EQUAL (7, TextureWrap (7,13,kTexWrapClamp));
+ CHECK_EQUAL (0, TextureWrap (-1,13,kTexWrapClamp));
+ CHECK_EQUAL (12, TextureWrap (13,13,kTexWrapClamp));
+}
+
+TEST (SetGetImagePixelARGB)
+{
+ UInt8 data[4][4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 8, kTexFormatARGB32, data);
+ SetImagePixel (image, 0, 0, kTexWrapRepeat, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0]
+ CHECK (data[0][1]==255 && data[0][2]==128 && data[0][3]==77 && data[0][0]==51);
+ SetImagePixel (image, 3, 8, kTexWrapRepeat, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1] due to repeat
+ CHECK (data[1][1]==26 && data[1][2]==51 && data[1][3]==77 && data[1][0]==102);
+ SetImagePixel (image, -3, 1, kTexWrapClamp, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2] due to clamp
+ CHECK (data[2][1]==77 && data[2][2]==102 && data[2][3]==128 && data[2][0]==153);
+
+ CHECK (data[3][1]==13 && data[3][2]==13 && data[3][3]==13 && data[3][0]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.5f,0.3f,0.2f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.1f,0.2f,0.3f,0.4f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.3f,0.4f,0.5f,0.6f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelRGBA)
+{
+ UInt8 data[4][4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 8, kTexFormatRGBA32, data);
+ SetImagePixel (image, 0, 0, kTexWrapRepeat, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0]
+ CHECK (data[0][0]==255 && data[0][1]==128 && data[0][2]==77 && data[0][3]==51);
+ SetImagePixel (image, 3, 8, kTexWrapRepeat, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1] due to repeat
+ CHECK (data[1][0]==26 && data[1][1]==51 && data[1][2]==77 && data[1][3]==102);
+ SetImagePixel (image, -3, 1, kTexWrapClamp, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2] due to clamp
+ CHECK (data[2][0]==77 && data[2][1]==102 && data[2][2]==128 && data[2][3]==153);
+
+ CHECK (data[3][1]==13 && data[3][2]==13 && data[3][3]==13 && data[3][0]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.5f,0.3f,0.2f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.1f,0.2f,0.3f,0.4f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.3f,0.4f,0.5f,0.6f)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelRGB)
+{
+ UInt8 data[4][3];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 6, kTexFormatRGB24, data);
+ SetImagePixel (image, 0, 0, kTexWrapClamp, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0]
+ CHECK (data[0][0]==255 && data[0][1]==128 && data[0][2]==77);
+ SetImagePixel (image, 1, 0, kTexWrapClamp, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1]
+ CHECK (data[1][0]==26 && data[1][1]==51 && data[1][2]==77);
+ SetImagePixel (image, 0, 1, kTexWrapClamp, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2]
+ CHECK (data[2][0]==77 && data[2][1]==102 && data[2][2]==128);
+
+ CHECK (data[3][0]==13 && data[3][1]==13 && data[3][2]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.5f,0.3f,1)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.1f,0.2f,0.3f,1)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.3f,0.4f,0.5f,1)) == GetImagePixel (&data[0][0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelAlpha)
+{
+ UInt8 data[4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (2, 2, 2, kTexFormatAlpha8, data);
+ SetImagePixel (image, -3, -2, kTexWrapClamp, ColorRGBAf(1.0f,0.5f,0.3f,0.2f)); // sets [0] due to clamp
+ CHECK (data[0]==51);
+ SetImagePixel (image, 1, -4, kTexWrapRepeat, ColorRGBAf(0.1f,0.2f,0.3f,0.4f)); // sets [1] due to repeat
+ CHECK (data[1]==102);
+ SetImagePixel (image, -4, 7, kTexWrapRepeat, ColorRGBAf(0.3f,0.4f,0.5f,0.6f)); // sets [2] due to repeat
+ CHECK (data[2]==153);
+
+ CHECK (data[3]==13); // [3] left untouched
+
+ CHECK(ColorRGBA32(ColorRGBAf(1,1,1,0.2f)) == GetImagePixel (&data[0], 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(1,1,1,0.4f)) == GetImagePixel (&data[0], 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(1,1,1,0.6f)) == GetImagePixel (&data[0], 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+}
+
+TEST (SetGetImagePixelRGB565)
+{
+ UInt16 data[4];
+ memset (data, 0xab, sizeof(data));
+ ImageReference image (2, 2, 4, kTexFormatRGB565, data);
+ SetImagePixel (image, 0, 0, kTexWrapClamp, ColorRGBAf(1.0f,0.0f,0.0f,0.2f)); // sets [0]
+ CHECK (data[0]==0xf800);
+ SetImagePixel (image, 1, 0, kTexWrapClamp, ColorRGBAf(0.0f,1.0f,0.0f,0.4f)); // sets [1]
+ CHECK (data[1]==0x07e0);
+ SetImagePixel (image, 0, 1, kTexWrapClamp, ColorRGBAf(0.0f,0.0f,1.0f,0.6f)); // sets [2]
+ CHECK (data[2]==0x001f);
+ CHECK (data[3]==0xabab); // [3] still left untouched
+
+ ColorRGBAf gray(14.0f/31.0f, 31.0f/63.0f, 16.0f/31.0f, 1.0f);
+ SetImagePixel (image, 1, 1, kTexWrapClamp, gray); // sets [3]
+ CHECK (data[3]==0x73f0);
+
+ UInt8* srcData = reinterpret_cast<UInt8*>(&data[0]);
+ CHECK(ColorRGBA32(ColorRGBAf(1.0f,0.0f,0.0f,1)) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapRepeat, 2, 2)); // gets [0] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.0f,1.0f,0.0f,1)) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapRepeat, 5, -2)); // gets [1] due to repeat
+ CHECK(ColorRGBA32(ColorRGBAf(0.0f,0.0f,1.0f,1)) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapClamp, -1, 1)); // gets [2] due to clamp
+ CHECK(ColorRGBA32(gray) == GetImagePixel (srcData, 2, 2, image.GetFormat(), kTexWrapClamp, 2, 2)); // gets [3] due to clamp
+}
+
+TEST (SetImagePixelBlockARGB)
+{
+ UInt8 data[16][16][4];
+ memset (data, 13, sizeof(data));
+ ImageReference image (16, 16, 16*4, kTexFormatARGB32, data);
+
+ ColorRGBAf color (1,0,1,0);
+ SetImagePixelBlock (&data[0][0][0], 16, 16, kTexFormatARGB32, 15,15,1,1, 1, &color);
+ CHECK (data[15][15][1]==255 && data[15][15][2]==0 && data[15][15][3]==255 && data[15][15][0]==0);
+}
+
+#if UNITY_EDITOR
+TEST (BlitBilinearARGBToFloat)
+{
+ Image src (128, 256, kTexFormatARGB32);
+ Image dst (16, 16, kTexFormatARGB32);
+ // ProphecySDK's bilinear blitter from integer source to floating point destination
+ // when sizes are different allocates temporary intermediate image, and blit there
+ // can result in access violation for the last pixel. This unit test would crash then, especially
+ // on Macs. ProphecySDK modified to allocate one pixel more for temporary images, just like
+ // we do for ours.
+ dst.ReformatImage (src, 128, 128, kTexFormatARGBFloat, Image::BLIT_BILINEAR_SCALE);
+}
+#endif
+
+TEST (CreateMipMap2x2)
+{
+ ColorRGBA32 data[4+1+1]; // 2x2, 1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ CreateMipMap ((UInt8*)data, 2, 2, 1, kTexFormatARGB32);
+
+ // next mip level of 1x1 size
+ CHECK(ColorRGBA32(255,191,127,63) == data[4]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[5]);
+}
+TEST (CreateMipMap4x1)
+{
+ ColorRGBA32 data[4+2+1+1]; // 4x1, 2x1, 1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ CreateMipMap ((UInt8*)data, 4, 1, 1, kTexFormatARGB32);
+
+ // next mip level of 2x1 size
+ CHECK(ColorRGBA32(255,255,255,127) == data[4]);
+ CHECK(ColorRGBA32(255,127,0,0) == data[5]);
+
+ // next mip level of 1x1 size
+ CHECK(ColorRGBA32(255,191,127,63) == data[6]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[7]);
+}
+TEST (CreateMipMap4x1x2)
+{
+ ColorRGBA32 data[8+2+1+1]; // 4x1x2, 2x1x1, 1x1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ data[4] = ColorRGBA32(128,128,128,128);
+ data[5] = ColorRGBA32(128,128,128,0);
+ data[6] = ColorRGBA32(128,128,0,0);
+ data[7] = ColorRGBA32(128,0,0,0);
+ CreateMipMap ((UInt8*)data, 4, 1, 2, kTexFormatARGB32);
+
+ // next mip level, 2x1x1 size
+ CHECK(ColorRGBA32(191,191,191,95) == data[8]);
+ CHECK(ColorRGBA32(191,95,0,0) == data[9]);
+
+ // next mip level, 1x1x1 size
+ CHECK(ColorRGBA32(191,143,95,47) == data[10]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[11]);
+}
+TEST (CreateMipMap4x1x3)
+{
+ ColorRGBA32 data[12+2+1+1]; // 4x1x2, 2x1x1, 1x1x1, one extra for out-of-bounds check
+ memset (data, 13, sizeof(data));
+ data[0] = ColorRGBA32(255,255,255,255);
+ data[1] = ColorRGBA32(255,255,255,0);
+ data[2] = ColorRGBA32(255,255,0,0);
+ data[3] = ColorRGBA32(255,0,0,0);
+ data[4] = ColorRGBA32(128,128,128,128);
+ data[5] = ColorRGBA32(128,128,128,0);
+ data[6] = ColorRGBA32(128,128,0,0);
+ data[7] = ColorRGBA32(128,0,0,0);
+ data[8] = ColorRGBA32(64,64,64,64);
+ data[9] = ColorRGBA32(64,64,64,0);
+ data[10] = ColorRGBA32(64,64,0,0);
+ data[11] = ColorRGBA32(64,0,0,0);
+ CreateMipMap ((UInt8*)data, 4, 1, 3, kTexFormatARGB32);
+
+ // next mip level, 2x1x1 size
+ CHECK(ColorRGBA32(191,191,191,95) == data[12]);
+ CHECK(ColorRGBA32(191,95,0,0) == data[13]);
+
+ // next mip level, 1x1x1 size
+ CHECK(ColorRGBA32(191,143,95,47) == data[14]);
+
+ // data after that should be untouched
+ CHECK(ColorRGBA32(13,13,13,13) == data[15]);
+}
+
+
+} // SUITE
+
+#endif // ENABLE_UNIT_TESTS
diff --git a/Runtime/Graphics/Image.h b/Runtime/Graphics/Image.h
new file mode 100644
index 0000000..ef75c9e
--- /dev/null
+++ b/Runtime/Graphics/Image.h
@@ -0,0 +1,178 @@
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "TextureFormat.h"
+#include "Runtime/Serialize/SerializeUtility.h"
+#include "Runtime/Allocator/MemoryMacros.h"
+
+class Image;
+class ColorRGBA32;
+class ColorRGBAf;
+class ImageReference;
+namespace prcore { class PixelFormat; }
+
+prcore::PixelFormat GetProphecyPixelFormat (TextureFormat format);
+
+
+// Inside image, leaves sizeX x sizeY portion untouched, fills the rest by
+// repeating border pixels.
+void PadImageBorder( ImageReference& image, int sizeX, int sizeY );
+
+// Copies source compressed image into (possibly larger) destination compressed image.
+void BlitCopyCompressedImage( TextureFormat format, const UInt8* src, int srcWidth, int srcHeight, UInt8* dst, int dstWidth, int dstHeight, bool fillRest );
+void BlitCopyCompressedDXT1ToDXT5( const UInt8* src, int srcWidth, int srcHeight, UInt8* dst, int dstWidth, int dstHeight );
+
+// Generates a mipmap chain
+void CreateMipMap (UInt8* inData, int width, int height, int depth, TextureFormat format);
+
+// Calculate the amount of mipmaps that can be created from an image of given size.
+// (The original image is counted as mipmap also)
+int CalculateMipMapCount3D (int width, int height, int depth);
+
+int CalculateImageSize (int width, int height, TextureFormat format);
+int CalculateImageMipMapSize (int width, int height, TextureFormat format);
+int CalculateMipMapOffset (int width, int height, TextureFormat format, int miplevel);
+
+void Premultiply( ImageReference& image );
+
+// Range of RGBM lightmaps is [0;8]
+enum { kRGBMMaxRange = 8 };
+void DecodeRGBM (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf);
+void SetAlphaChannel (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf, UInt8 alpha);
+void SetAlphaToRedChannel (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf);
+void XenonToNormalSRGBTexture (int width, int height, UInt8* data, int pitch, const prcore::PixelFormat& pf);
+
+#if UNITY_XENON
+enum { kImageDataAlignment = 4096 };
+#else
+enum { kImageDataAlignment = kDefaultMemoryAlignment };
+#endif
+
+class ImageReference
+{
+protected:
+ UInt32 m_Format;
+ SInt32 m_Width;
+ SInt32 m_Height;
+ SInt32 m_RowBytes;
+ UInt8* m_Image;
+
+public:
+
+ enum ClearMode
+ {
+ CLEAR_COLOR = 1,
+ CLEAR_ALPHA = 2,
+ CLEAR_COLOR_ALPHA = CLEAR_COLOR | CLEAR_ALPHA
+ };
+
+ enum BlitMode
+ {
+ BLIT_COPY,
+ BLIT_SCALE,
+ BLIT_BILINEAR_SCALE,
+ };
+
+ ImageReference () { m_Image = NULL; m_Width = 0; m_Height = 0; m_Format = 0; }
+ ImageReference (int width, int height, int rowbytes, TextureFormat format, void* image);
+ ImageReference (int width, int height, TextureFormat format);
+
+ // Returns true if the image is exactly the same by camping width, height, and image data
+ friend bool operator == (const ImageReference& lhs, const ImageReference& rhs);
+
+ // Returns a subpart of the image
+ ImageReference ClipImage (int x, int y, int width, int height) const;
+
+ UInt8* GetImageData () const { return m_Image; }
+ int GetRowBytes () const { return m_RowBytes; }
+ UInt8* GetRowPtr (int y) const { DebugAssertIf(y >= m_Height || y < 0); return m_Image + m_RowBytes * y; }
+ int GetWidth () const { return m_Width; }
+ int GetHeight () const { return m_Height; }
+ TextureFormat GetFormat() const { return (TextureFormat)m_Format; }
+
+ void BlitImage (const ImageReference& source, BlitMode mode = BLIT_COPY);
+ void BlitImage (int x, int y, const ImageReference& source);
+
+ void ClearImage (const ColorRGBA32& color, ClearMode mode = CLEAR_COLOR_ALPHA);
+ void FlipImageY ();
+ #if UNITY_EDITOR
+ void FlipImageX ();
+ #endif
+
+ bool IsValidImage () const;
+
+ bool NeedsReformat(int width, int height, TextureFormat format) const;
+};
+
+class Image : public ImageReference
+{
+public:
+
+ DECLARE_SERIALIZE (Image)
+
+ Image (int width, int height, TextureFormat format);
+ Image (int width, int height, int rowbytes, TextureFormat format, void* image);
+ Image () { }
+
+ // TODO : should be removed, because they implicitly have worse performance - use SetImage instead
+ Image (const Image& image) : ImageReference() { SetImage (image); }
+ Image (const ImageReference& image) { SetImage (image); }
+
+ // TODO : should be removed, because they implicitly have worse performance - use SetImage instead
+ void operator = (const ImageReference& image) { SetImage (image); }
+ void operator = (const Image& image) { SetImage (image); }
+
+ ~Image () { UNITY_FREE(kMemNewDelete, m_Image); }
+
+ // Reformats given image into itself
+ void ReformatImage (const ImageReference& image, int width, int height, TextureFormat format, BlitMode mode = BLIT_COPY);
+ // Reformats itself
+ void ReformatImage (int width, int height, TextureFormat format, BlitMode mode = BLIT_COPY);
+
+ // To initialize an image use: someImage = ImageReference (width, height, format);
+
+ // shrinkAllowed - controls if memory should be reallocated when Image needs less memory,
+ // if shrinkAllowed is set to false it won't reallocate memory if Image needs less memory
+ void SetImage(const ImageReference& src, bool shrinkAllowed = true);
+ void SetImage(SInt32 width, SInt32 height, UInt32 format, bool shrinkAllowed);
+};
+
+bool CheckImageFormatValid (int width, int height, TextureFormat format);
+
+
+extern const char* kUnsupportedGetPixelOpFormatMessage;
+extern const char* kUnsupportedSetPixelOpFormatMessage;
+void SetImagePixel (ImageReference& image, int x, int y, TextureWrapMode wrap, const ColorRGBAf& color);
+ColorRGBA32 GetImagePixel (UInt8* data, int width, int height, TextureFormat format, TextureWrapMode wrap, int x, int y);
+ColorRGBAf GetImagePixelBilinear (UInt8* data, int width, int height, TextureFormat format, TextureWrapMode wrap, float u, float v);
+bool GetImagePixelBlock (UInt8* data, int dataWidth, int dataHeight, TextureFormat format, int x, int y, int blockWidth, int blockHeight, ColorRGBAf* outColors);
+bool SetImagePixelBlock (UInt8* data, int dataWidth, int dataHeight, TextureFormat format, int x, int y, int blockWidth, int blockHeight, int pixelCount, const ColorRGBAf* pixels);
+
+void SwizzleARGB32ToBGRA32 (UInt8* bytes, int imageSize);
+void SwizzleRGBA32ToBGRA32 (UInt8* bytes, int imageSize);
+void SwizzleRGB24ToBGR24 (UInt8* bytes, int imageSize);
+void SwizzleBGRAToRGBA32 (UInt8* bytes, int imageSize);
+
+template<class TransferFunction> inline
+void Image::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (m_Format);
+ TRANSFER (m_Width);
+ TRANSFER (m_Height);
+ TRANSFER (m_RowBytes);
+
+ unsigned completeImageSize = m_RowBytes * std::max<int>(m_Height, 0);
+
+ transfer.TransferTypeless (&completeImageSize, "image data");
+ if (transfer.IsReading ())
+ {
+ UNITY_FREE(kMemNewDelete, m_Image);
+ m_Image = NULL;
+ if (completeImageSize != 0 && CheckImageFormatValid (m_Width, m_Height, m_Format))
+ m_Image = (UInt8*)UNITY_MALLOC_ALIGNED(kMemNewDelete, completeImageSize, kImageDataAlignment);
+ }
+
+ transfer.TransferTypelessData (completeImageSize, m_Image);
+}
+
+#endif
diff --git a/Runtime/Graphics/ImageConversion.cpp b/Runtime/Graphics/ImageConversion.cpp
new file mode 100644
index 0000000..fe0b462
--- /dev/null
+++ b/Runtime/Graphics/ImageConversion.cpp
@@ -0,0 +1,621 @@
+#include "UnityPrefix.h"
+#include "ImageConversion.h"
+#include "Runtime/Utilities/File.h"
+#include "Texture2D.h"
+#include "Image.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Math/Color.h"
+#include "DXTCompression.h"
+#if ENABLE_PNG_JPG
+#include "External/ProphecySDK/src/extlib/pnglib/png.h"
+#include "External/ProphecySDK/src/extlib/jpglib/jpeglib.h"
+#include "Runtime/Export/JPEGMemsrc.h"
+#endif
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include <setjmp.h>
+#include "Runtime/Network/PlayerCommunicator/PlayerConnection.h"
+
+using namespace std;
+
+// --------------------------------------------------------------------------
+// PNG
+
+#if ENABLE_PNG_JPG
+static void PngWriteToMemoryFunc( png_structp png, png_bytep data, png_size_t size )
+{
+ MemoryBuffer* buffer = (MemoryBuffer*)png->io_ptr;
+ buffer->insert( buffer->end(), data, data+size );
+}
+static void PngWriteFlushFunc( png_structp png )
+{
+}
+
+
+bool ConvertImageToPNGBuffer( const ImageReference& inputImage, MemoryBuffer& buffer )
+{
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,(png_voidp)NULL, NULL, NULL);
+ if (!png_ptr)
+ return false;
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr)
+ return false;
+
+ if (setjmp(png_jmpbuf(png_ptr)))
+ return false;
+
+ buffer.reserve( 4096 );
+ png_set_write_fn( png_ptr, &buffer, PngWriteToMemoryFunc, PngWriteFlushFunc );
+
+ png_set_compression_level(png_ptr, Z_BEST_SPEED);
+
+ int format = kTexFormatRGBA32;
+ if (inputImage.GetFormat() == kTexFormatRGB24 || inputImage.GetFormat() == kTexFormatRGB565)
+ format = kTexFormatRGB24;
+
+ Image image( inputImage.GetWidth(), inputImage.GetHeight(), format );
+ image.BlitImage( inputImage );
+
+ png_set_IHDR(png_ptr, info_ptr,
+ inputImage.GetWidth(),
+ inputImage.GetHeight(),
+ 8,
+ format == kTexFormatRGB24 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ png_write_info(png_ptr, info_ptr);
+ for (int i = 0; i < image.GetHeight(); i++)
+ png_write_row(png_ptr, image.GetRowPtr(image.GetHeight() - i - 1));
+
+ png_write_end(png_ptr, info_ptr);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ return !buffer.empty();
+}
+
+
+#if CAPTURE_SCREENSHOT_AVAILABLE
+
+bool ConvertImageToPNGFile (const ImageReference& inputImage, const string& path)
+{
+ MemoryBuffer buffer;
+ if( !ConvertImageToPNGBuffer( inputImage, buffer ) )
+ return false;
+
+ #if UNITY_PEPPER
+ // no file access in pepper. write screenshot to stdout.
+ // we need to write hex, as string reading in mono may perform
+ // string encoding translation on stdout, breaking the binary data.
+ printf_console("SCREENSHOT_MARKER=%sSCREENSHOTSTART_MARKER", path.c_str());
+ for (int i=0; i<buffer.size(); i++)
+ printf_console("%02x", buffer[i]);
+ printf_console("SCREENSHOTEND_MARKER\n");
+ return true;
+ #else
+#if ENABLE_PLAYERCONNECTION
+ TransferFileOverPlayerConnection(path, &buffer[0], buffer.size());
+#endif
+ return WriteBytesToFile (&buffer[0], buffer.size(), path);
+ #endif
+}
+
+#endif
+
+struct PngMemoryReadContext {
+ const unsigned char* inputPointer;
+ size_t inputSizeLeft;
+};
+
+static void PngReadFromMemoryFunc( png_structp png_ptr, png_bytep data, png_size_t len )
+{
+ PngMemoryReadContext* context = (PngMemoryReadContext*)png_ptr->io_ptr;
+ // check for overflow
+ if( len > context->inputSizeLeft )
+ len = context->inputSizeLeft;
+
+ memcpy( data, context->inputPointer, len );
+ context->inputPointer += len;
+ context->inputSizeLeft -= len;
+}
+
+
+static void PngReadWarningFunc( png_struct* png_ptr, png_const_charp warning_msg )
+{
+}
+
+bool ImageSizeBoundCheck(unsigned int width, unsigned int height) {
+ int tmp, tmp2;
+ if (width + 3 < width ||
+ height + 3 < height) {
+ return 0;
+ }
+
+ tmp = width * height;
+ if (width != 0 && height != tmp / width) {
+ return 0;
+ }
+
+ tmp2 = tmp * 16;
+ if (tmp != tmp2/16) {
+ return 0;
+ }
+ return 1;
+}
+
+static bool LoadPngIntoTexture( Texture2D& texture, const void* data, size_t size, bool compressTexture, UInt8** outRGBABaseLevelForDXTMips )
+{
+ PngMemoryReadContext context;
+ context.inputPointer = static_cast<const unsigned char*>( data );
+ context.inputSizeLeft = size;
+
+ if( !data )
+ return false;
+
+ // check png header
+ if( size < 8 || !png_check_sig( const_cast<unsigned char*>(context.inputPointer), 8 ) )
+ return false;
+
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_uint_32 width, height;
+ int bit_depth, color_type, interlace_type;
+
+ double image_gamma = 0.45;
+ int number_passes = 0;
+
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,&PngReadWarningFunc);
+ if( png_ptr == NULL )
+ return false;
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if( info_ptr == NULL )
+ {
+ png_destroy_read_struct(&png_ptr,(png_infopp)NULL,(png_infopp)NULL);
+ return false;
+ }
+
+ if( setjmp(png_ptr->jmpbuf) )
+ {
+ png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)NULL);
+ return false;
+ }
+
+ png_set_read_fn( png_ptr, &context, &PngReadFromMemoryFunc );
+ png_read_info( png_ptr, info_ptr );
+
+ png_get_IHDR( png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL );
+
+ png_set_strip_16(png_ptr); // strip 16 bit channels to 8 bit
+ png_set_packing(png_ptr); // separate palettized channels
+
+ // palette -> rgb
+ if( color_type == PNG_COLOR_TYPE_PALETTE )
+ {
+ png_set_expand(png_ptr);
+ }
+
+ // grayscale -> 8 bits
+ if( !(color_type & PNG_COLOR_MASK_COLOR) && bit_depth < 8 ) png_set_expand(png_ptr);
+
+ // if exists, expand tRNS to alpha channel
+ if( png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS) ) png_set_expand(png_ptr);
+
+ // expand gray to RGB
+ if( color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
+ png_set_gray_to_rgb(png_ptr);
+
+ // we need to get ARGB format for raw textures, and RGBA if we will compress it
+ if( !compressTexture )
+ png_set_swap_alpha(png_ptr);
+
+ png_set_filler( png_ptr, 0xFF, PNG_FILLER_BEFORE ); // force alpha byte
+
+ // Only apply gamma correction if the image has gamma information. In this case
+ // assume our display gamma is 2.0 (a compromise between Mac and PC...).
+ double screen_gamma = 2.0f;
+ image_gamma = 0.0;
+ if ( png_get_gAMA(png_ptr,info_ptr,&image_gamma) )
+ {
+ png_set_gamma(png_ptr,screen_gamma,image_gamma);
+ }
+
+ number_passes = png_set_interlace_handling(png_ptr);
+ png_read_update_info(png_ptr,info_ptr); // update gamma, etc.
+
+ // If texture size or format differs, reformat the texture.
+ TextureFormat wantedFormat = compressTexture ? kTexFormatDXT5 : kTexFormatARGB32;
+ bool mipMaps = texture.HasMipMap();
+ if( width != texture.GetDataWidth () || height != texture.GetDataHeight () || kTexFormatARGB32 != texture.GetTextureFormat())
+ texture.InitTexture( width, height, wantedFormat, mipMaps ? Texture2D::kMipmapMask : Texture2D::kNoMipmap, 1 );
+
+ ImageReference ref;
+ AssertIf( !outRGBABaseLevelForDXTMips );
+ if( compressTexture ) {
+ int imageByteSize;
+ if (mipMaps) {
+ int miplevel = CalculateMipMapCount3D(width, height, 1);
+ int iter;
+ unsigned int completeSize = 0;
+ unsigned int prevcompleteSize = 0;
+
+ if (!ImageSizeBoundCheck(width, height)) {
+ longjmp(png_ptr->jmpbuf, 1);
+ }
+
+ for (iter = 0; iter < miplevel; iter++) {
+ prevcompleteSize = completeSize;
+ completeSize += CalculateImageSize (std::max (width >> iter, (png_uint_32)1), std::max (height >> iter, (png_uint_32)1), kTexFormatRGBA32);
+ if (completeSize < prevcompleteSize) {
+ longjmp(png_ptr->jmpbuf, 1);
+ }
+ }
+ imageByteSize = CalculateImageMipMapSize( width, height, kTexFormatRGBA32 );
+ } else {
+ if (!ImageSizeBoundCheck(width, height)) {
+ longjmp(png_ptr->jmpbuf, 1);
+ }
+ imageByteSize = CalculateImageSize( width, height, kTexFormatRGBA32 );
+ }
+
+ *outRGBABaseLevelForDXTMips = new UInt8[imageByteSize];
+ ref = ImageReference( width, height, width*4, kTexFormatRGBA32, *outRGBABaseLevelForDXTMips );
+ } else {
+ if( !texture.GetWriteImageReference(&ref, 0, 0) ) {
+ png_destroy_read_struct( &png_ptr,&info_ptr,(png_infopp)NULL );
+ return false;
+ }
+ }
+
+ size_t len = sizeof(png_bytep) * height;
+
+ /* boundcheck for integer overflow */
+ if (height != len / sizeof(png_bytep) ) {
+ png_destroy_read_struct( &png_ptr,&info_ptr,(png_infopp)NULL );
+ return false;
+ }
+
+ png_bytep* row_pointers = new png_bytep[height];
+ for( png_uint_32 row = 0; row<height; ++row )
+ {
+ row_pointers[row] = ref.GetRowPtr(height-1-row);
+ }
+
+ for( int pass = 0; pass < number_passes; pass++ )
+ {
+ png_read_rows( png_ptr,row_pointers,NULL,height );
+ }
+
+ // cleanup
+ png_read_end( png_ptr,info_ptr );
+ png_destroy_read_struct( &png_ptr,&info_ptr,(png_infopp)NULL );
+ delete[] row_pointers;
+
+ return true;
+}
+
+// --------------------------------------------------------------------------
+// JPG
+
+/* Custom error manager. As the default one in libjpeg crashes Unity on error */
+struct unity_jpeg_error_mgr {
+ struct jpeg_error_mgr pub; /* "public" fields */
+ jmp_buf setjmp_buffer; /* for return to caller */
+};
+typedef struct unity_jpeg_error_mgr * unity_jpeg_error_ptr;
+
+/*
+ * Here's the routine that will replace the standard error_exit method:
+ */
+METHODDEF(void)
+unity_jpeg_error_exit (j_common_ptr cinfo)
+{
+ /* cinfo->err really points to a unity_jpeg_error_mgr struct, so coerce pointer */
+ unity_jpeg_error_ptr myerr = (unity_jpeg_error_ptr) cinfo->err;
+ /* Always display the message. */
+ /* We could postpone this until after returning, if we chose. */
+ (*cinfo->err->output_message) (cinfo);
+ /* Return control to the setjmp point */
+ longjmp(myerr->setjmp_buffer, 1);
+}
+
+static void HandleError (struct jpeg_decompress_struct& cinfo, Texture2D* tex)
+{
+ jpeg_destroy_decompress (&cinfo);
+}
+
+static int LoadJpegIntoTexture(Texture2D& tex, const UInt8* jpegData, size_t jpegDataSz, bool compressTexture, UInt8** outRGBABaseLevelForDXTMips)
+{
+#if UNITY_WII
+ AssertIf ("ERROR: LoadJpegIntoTexture is not supported!");
+ return 0;
+#else
+ struct jpeg_decompress_struct cinfo;
+
+ /* We use our private extension JPEG error handler.
+ * Note that this struct must live as long as the main JPEG parameter
+ * struct, to avoid dangling-pointer problems.
+ */
+ struct unity_jpeg_error_mgr jerr;
+
+ JSAMPARRAY in;
+ int row_stride;
+ unsigned char *out;
+
+ // set up the decompression.
+ cinfo.err = jpeg_std_error(&jerr.pub);
+ jerr.pub.error_exit = unity_jpeg_error_exit;
+
+ /* Establish the setjmp return context for my_error_exit to use. */
+ if (setjmp(jerr.setjmp_buffer)) {
+ /* If we get here, the JPEG code has signaled an error.
+ * We need to clean up the JPEG object, close the input file, and return.
+ */
+ HandleError(cinfo, &tex);
+ return 0;
+ }
+
+ jpeg_create_decompress (&cinfo);
+
+ // inititalize the source
+ jpeg_memory_src (&cinfo, (unsigned char*)jpegData, jpegDataSz);
+
+ // initialize decompression
+ (void) jpeg_read_header (&cinfo, TRUE);
+ (void) jpeg_start_decompress (&cinfo);
+
+ // set up the width and height for return
+ int width = cinfo.image_width;
+ int height = cinfo.image_height;
+
+ // initialize the input buffer - we'll use the in-built memory management routines in the
+ // JPEG library because it will automatically free the used memory for us when we destroy
+ // the decompression structure. cool.
+ row_stride = cinfo.output_width * cinfo.output_components;
+ if (cinfo.output_width != 0 && cinfo.output_components != row_stride / cinfo.output_width) {
+ HandleError(cinfo, &tex);
+ return 0;
+ }
+ in = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
+
+ // we only support three channel RGB or one channel grayscale formats
+
+ if (cinfo.output_components != 3 && cinfo.output_components != 1)
+ {
+ HandleError(cinfo, &tex);
+ return 0;
+ }
+
+ // If texture size or format differs, reformat the texture.
+ TextureFormat wantedFormat = compressTexture ? kTexFormatDXT1 : kTexFormatRGB24;
+ bool mipMaps = tex.HasMipMap();
+ if (width != tex.GetDataWidth () || height != tex.GetDataHeight () || wantedFormat != tex.GetTextureFormat())
+ tex.InitTexture (width, height, wantedFormat, mipMaps ? Texture2D::kMipmapMask : Texture2D::kNoMipmap, 1);
+
+ ImageReference ref;
+ AssertIf( !outRGBABaseLevelForDXTMips );
+ if( compressTexture ) {
+ int imageByteSize;
+ if (mipMaps) {
+ int miplevel = CalculateMipMapCount3D(width, height, 1);
+ int iter;
+ unsigned int completeSize = 0;
+ unsigned int prevcompleteSize = 0;
+
+ if (!ImageSizeBoundCheck(width, height)) {
+ HandleError(cinfo, &tex);
+ return 0;
+ }
+
+ for (iter = 0; iter < miplevel; iter++) {
+ prevcompleteSize = completeSize;
+ completeSize += CalculateImageSize (std::max (width >> iter, 1), std::max (height >> iter, 1), kTexFormatRGBA32);
+ if (completeSize < prevcompleteSize) {
+ HandleError(cinfo, &tex);
+ return 0;
+ }
+ }
+ imageByteSize = CalculateImageMipMapSize( width, height, kTexFormatRGBA32 );
+ } else {
+ if (!ImageSizeBoundCheck(width, height)) {
+ HandleError(cinfo, &tex);
+ return 0;
+ }
+ imageByteSize = CalculateImageSize( width, height, kTexFormatRGBA32 );
+ }
+ *outRGBABaseLevelForDXTMips = new UInt8[imageByteSize];
+ ref = ImageReference( width, height, width*4, kTexFormatRGBA32, *outRGBABaseLevelForDXTMips );
+ } else {
+ if( !tex.GetWriteImageReference(&ref, 0, 0) ) {
+ jpeg_destroy_decompress (&cinfo);
+ return 0;
+ }
+ }
+
+ UInt8* dst;
+ int scanline = height-1;
+ int result = 1;
+
+ if (scanline < 0) {
+ HandleError(cinfo, &tex);
+ return 0;
+ }
+
+ // three channel output
+ if( cinfo.output_components == 3 )
+ {
+ while (cinfo.output_scanline < cinfo.output_height)
+ {
+ jpeg_read_scanlines(&cinfo, in, 1);
+ dst = ref.GetRowPtr(scanline);
+ out = in[0];
+ if( !compressTexture ) {
+ for( int i = 0; i < row_stride; i += 3, dst += 3 )
+ {
+ dst[0] = out[i+2];
+ dst[1] = out[i+1];
+ dst[2] = out[i];
+ }
+ } else {
+ for( int i = 0; i < row_stride; i += 3, dst += 4 )
+ {
+ dst[0] = out[i+2];
+ dst[1] = out[i+1];
+ dst[2] = out[i];
+ dst[3] = 255;
+ }
+ }
+ --scanline;
+ }
+ }
+ // one channel output
+ else if( cinfo.output_components == 1 )
+ {
+ while (cinfo.output_scanline < cinfo.output_height)
+ {
+ jpeg_read_scanlines(&cinfo, in, 1);
+ dst = ref.GetRowPtr(scanline);
+ out = in[0];
+ if( !compressTexture ) {
+ for( int i = 0; i < row_stride; ++i, dst += 3 )
+ {
+ UInt8 v = out[i];
+ dst[0] = v;
+ dst[1] = v;
+ dst[2] = v;
+ }
+ } else {
+ for( int i = 0; i < row_stride; ++i, dst += 4 )
+ {
+ UInt8 v = out[i];
+ dst[0] = v;
+ dst[1] = v;
+ dst[2] = v;
+ dst[4] = 255;
+ }
+ }
+ --scanline;
+ }
+ }
+ else
+ {
+ AssertString("unsupported number of output components in JPEG");
+ result = 0;
+ }
+
+ // finish decompression and destroy the jpeg
+ (void) jpeg_finish_decompress (&cinfo);
+ jpeg_destroy_decompress (&cinfo);
+
+
+ return result;
+#endif
+}
+
+#endif
+
+// --------------------------------------------------------------------------
+// Generic byte->texture loading
+
+
+// Simple image to load into the texture on error: "?"
+static const char* kDummyErrorImage =
+"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff"
+"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff\xff\xff\xff"
+"\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff"
+"\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\xff\xff"
+"\xff\xff\xff\xff\xff\xff\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\x00\x00\xff\xff\xff\xff\xff\xff";
+
+#if UNITY_FLASH
+//Leaky temporary workaround to deal with not having setjmp/longjmp on Flash. This is only to be able to return correctly, but doesn't clean up anything...beware!
+
+__attribute__ ((noinline)) static int LoadJpegIntoTextureFlash(Texture2D& tex, const UInt8* jpegData, size_t jpegDataSz, bool compressTexture, UInt8** outRGBABaseLevelForDXTMips)
+{
+ __asm __volatile__("try{");
+ int val = LoadJpegIntoTexture(tex,jpegData,jpegDataSz,compressTexture,outRGBABaseLevelForDXTMips);
+ __asm __volatile__("}catch(e:Error){r_g0 = 0; return;}");
+ return val;
+}
+
+__attribute__ ((noinline)) static bool LoadPngIntoTextureFlash( Texture2D& texture, const void* data, size_t size, bool compressTexture, UInt8** outRGBABaseLevelForDXTMips )
+{
+ __asm __volatile__("try{");
+ bool val = LoadPngIntoTexture(texture,data,size,compressTexture,outRGBABaseLevelForDXTMips);
+ __asm __volatile__("}catch(e:Error){r_g0 = 0; return;}");
+ return val;
+}
+
+#define LoadJpegIntoTexture LoadJpegIntoTextureFlash
+#define LoadPngIntoTexture LoadPngIntoTextureFlash
+#endif
+
+bool LoadMemoryBufferIntoTexture( Texture2D& tex, const UInt8* data, size_t size, LoadImageCompression compression, bool markNonReadable )
+{
+ UInt8* rgbaBaseLevel = NULL;
+ if( !gGraphicsCaps.hasS3TCCompression )
+ compression = kLoadImageUncompressed;
+
+#if ENABLE_PNG_JPG
+ if( !LoadJpegIntoTexture( tex, data, size, compression!=kLoadImageUncompressed, &rgbaBaseLevel ) )
+ {
+ delete[] rgbaBaseLevel; rgbaBaseLevel = NULL;
+ if( !LoadPngIntoTexture( tex, data, size, compression!=kLoadImageUncompressed, &rgbaBaseLevel ) )
+ {
+ // Store a dummy image into the texture
+ tex.InitTexture( 8, 8, kTexFormatRGB24, Texture2D::kNoMipmap, 1 );
+ unsigned char* textureData = (unsigned char*)tex.GetRawImageData();
+ memcpy( textureData, kDummyErrorImage, 8*8*3 );
+ }
+ }
+#endif
+ // Compress the image if needed
+ bool isDXTCompressed = IsCompressedDXTTextureFormat(tex.GetTextureFormat());
+ if( isDXTCompressed ) {
+ #if !GFX_SUPPORTS_DXT_COMPRESSION
+ delete[] rgbaBaseLevel;
+ return false;
+ #else
+
+ AssertIf( !rgbaBaseLevel );
+ // do final image compression
+ int width = tex.GetDataWidth();
+ int height = tex.GetDataHeight();
+ TextureFormat texFormat = tex.GetTextureFormat();
+ AssertIf( texFormat != kTexFormatDXT1 && texFormat != kTexFormatDXT5 );
+ FastCompressImage( width, height, rgbaBaseLevel, tex.GetRawImageData(), texFormat == kTexFormatDXT5, compression==kLoadImageDXTCompressDithered );
+
+ bool mipMaps = tex.HasMipMap();
+ if( mipMaps ) {
+ // compute mip representations from the base level
+ CreateMipMap (rgbaBaseLevel, width, height, 1, kTexFormatRGBA32);
+ // DXT compress them
+ int mipCount = tex.CountDataMipmaps();
+ for( int mip = 1; mip < mipCount; ++mip )
+ {
+ const UInt8* srcMipData = rgbaBaseLevel + CalculateMipMapOffset(width, height, kTexFormatRGBA32, mip);
+ UInt8* dstMipData = tex.GetRawImageData() + CalculateMipMapOffset(width, height, texFormat, mip);
+ FastCompressImage( std::max(width>>mip,1), std::max(height>>mip,1), srcMipData, dstMipData, texFormat == kTexFormatDXT5, compression==kLoadImageDXTCompressDithered );
+ }
+ }
+ #endif // GFX_SUPPORTS_DXT_COMPRESSION
+ }
+ delete[] rgbaBaseLevel;
+
+ if(markNonReadable)
+ {
+ tex.SetIsReadable(false);
+ tex.SetIsUnreloadable(true);
+ }
+
+ if( !IsCompressedDXTTextureFormat(tex.GetTextureFormat()) )
+ tex.UpdateImageData();
+ else
+ tex.UpdateImageDataDontTouchMipmap();
+
+ return true;
+}
diff --git a/Runtime/Graphics/ImageConversion.h b/Runtime/Graphics/ImageConversion.h
new file mode 100644
index 0000000..31ed2e0
--- /dev/null
+++ b/Runtime/Graphics/ImageConversion.h
@@ -0,0 +1,28 @@
+#ifndef _IMAGECONVERSION_H
+#define _IMAGECONVERSION_H
+
+#include "Runtime/Utilities/dynamic_array.h"
+
+class ImageReference;
+class Texture2D;
+typedef dynamic_array<UInt8> MemoryBuffer;
+
+bool ConvertImageToPNGBuffer( const ImageReference& image, MemoryBuffer& buffer );
+
+enum LoadImageCompression {
+ kLoadImageUncompressed = 0,
+ kLoadImageDXTCompress,
+ kLoadImageDXTCompressDithered,
+};
+bool LoadMemoryBufferIntoTexture( Texture2D& tex, const UInt8* data, size_t size, LoadImageCompression compression, bool markNonReadable=false );
+
+
+// hack: this is used only by capture screenshot. Compile code out if it's not
+// available
+#include "Runtime/Misc/CaptureScreenshot.h"
+#if CAPTURE_SCREENSHOT_AVAILABLE
+bool ConvertImageToPNGFile( const ImageReference& image, const std::string& path );
+#endif
+
+
+#endif
diff --git a/Runtime/Graphics/LightProbeGroup.cpp b/Runtime/Graphics/LightProbeGroup.cpp
new file mode 100644
index 0000000..1b19bcb
--- /dev/null
+++ b/Runtime/Graphics/LightProbeGroup.cpp
@@ -0,0 +1,50 @@
+#include "UnityPrefix.h"
+#include "LightProbeGroup.h"
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+using namespace std;
+
+#if UNITY_EDITOR
+static LightProbeGroupList gAllLightProbeGroups;
+#endif
+
+LightProbeGroup::LightProbeGroup (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+#if UNITY_EDITOR
+ , m_LightProbeGroupNode (this)
+#endif
+{
+}
+
+LightProbeGroup::~LightProbeGroup ()
+{
+}
+
+IMPLEMENT_CLASS (LightProbeGroup)
+IMPLEMENT_OBJECT_SERIALIZE (LightProbeGroup)
+
+template<class T> inline
+void LightProbeGroup::Transfer (T& transfer)
+{
+ Super::Transfer (transfer);
+
+ TRANSFER_EDITOR_ONLY (m_SourcePositions);
+}
+
+#if UNITY_EDITOR
+void LightProbeGroup::AddToManager ()
+{
+ gAllLightProbeGroups.push_back (m_LightProbeGroupNode);
+}
+
+void LightProbeGroup::RemoveFromManager ()
+{
+ m_LightProbeGroupNode.RemoveFromList ();
+}
+
+LightProbeGroupList& GetLightProbeGroups ()
+{
+ return gAllLightProbeGroups;
+}
+#endif
diff --git a/Runtime/Graphics/LightProbeGroup.h b/Runtime/Graphics/LightProbeGroup.h
new file mode 100644
index 0000000..3ca61a3
--- /dev/null
+++ b/Runtime/Graphics/LightProbeGroup.h
@@ -0,0 +1,41 @@
+#ifndef LIGHTPROBEGROUP_H
+#define LIGHTPROBEGROUP_H
+
+#include "Runtime/GameCode/Behaviour.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+class LightProbeGroup : public Behaviour
+{
+public:
+ REGISTER_DERIVED_CLASS (LightProbeGroup, Behaviour)
+ DECLARE_OBJECT_SERIALIZE (LightProbeGroup)
+
+ LightProbeGroup (MemLabelId label, ObjectCreationMode mode);
+
+#if UNITY_EDITOR
+ void SetPositions(Vector3f* data, int size) { m_SourcePositions.assign(data, data + size); SetDirty(); }
+ Vector3f* GetPositions() { return m_SourcePositions.size() > 0 ? &m_SourcePositions[0] : NULL; }
+ int GetPositionsSize() { return m_SourcePositions.size(); }
+
+ virtual void AddToManager ();
+ virtual void RemoveFromManager ();
+#else
+ virtual void AddToManager () {}
+ virtual void RemoveFromManager () {}
+#endif
+
+private:
+#if UNITY_EDITOR
+ dynamic_array<Vector3f> m_SourcePositions;
+ ListNode<LightProbeGroup> m_LightProbeGroupNode;
+#endif
+};
+
+#if UNITY_EDITOR
+typedef List< ListNode<LightProbeGroup> > LightProbeGroupList;
+LightProbeGroupList& GetLightProbeGroups ();
+#endif
+
+#endif
diff --git a/Runtime/Graphics/LightmapSettings.cpp b/Runtime/Graphics/LightmapSettings.cpp
new file mode 100644
index 0000000..a2ff547
--- /dev/null
+++ b/Runtime/Graphics/LightmapSettings.cpp
@@ -0,0 +1,153 @@
+#include "UnityPrefix.h"
+#include "LightmapSettings.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Scripting/Scripting.h"
+
+const char* const LightmapSettings::kLightmapsModeNames[] =
+{
+ "Single Lightmaps",
+ "Dual Lightmaps",
+ "Directional Lightmaps",
+ "RNM"
+};
+
+template<class T>
+void LightmapData::Transfer (T& transfer)
+{
+ TRANSFER(m_Lightmap);
+ TRANSFER(m_IndirectLightmap);
+}
+
+template<class T>
+void LightmapSettings::Transfer (T& transfer)
+{
+ Super::Transfer(transfer);
+ TRANSFER(m_LightProbes);
+ TRANSFER(m_Lightmaps);
+ TRANSFER(m_LightmapsMode);
+ TRANSFER(m_BakedColorSpace);
+ TRANSFER(m_UseDualLightmapsInForward);
+ transfer.Align ();
+ TRANSFER_EDITOR_ONLY(m_LightmapEditorSettings);
+}
+
+LightmapSettings::LightmapSettings(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_LightmapTextures(NULL)
+, m_LightmapTextureCount(0)
+, m_LightmapsMode(kDualLightmapsMode)
+, m_UseDualLightmapsInForward(false)
+, m_BakedColorSpace(0)
+{
+}
+
+LightmapSettings::~LightmapSettings ()
+{
+ delete[] m_LightmapTextures;
+}
+
+void LightmapSettings::AwakeFromLoad(AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad(awakeMode);
+ Rebuild ();
+}
+
+void LightmapSettings::Rebuild ()
+{
+ delete[] m_LightmapTextures;
+
+ const size_t lightmapCount = m_Lightmaps.size();
+
+ m_LightmapTextures = new TextureTriple[lightmapCount];
+ m_LightmapTextureCount = lightmapCount;
+
+ for (size_t i = 0; i < lightmapCount; ++i)
+ {
+ Texture2D* tex = m_Lightmaps[i].m_Lightmap;
+ Texture2D* texInd = m_Lightmaps[i].m_IndirectLightmap;
+ Texture2D* texThird = m_Lightmaps[i].m_ThirdLightmap;
+ TextureTriple p;
+ p.first = tex ? tex->GetTextureID() : TextureID();
+ p.second = texInd ? texInd->GetTextureID() : TextureID();
+ p.third = texThird ? texThird->GetTextureID() : TextureID();
+ m_LightmapTextures[i] = p;
+ }
+}
+
+void LightmapSettings::ClearLightmaps()
+{
+ m_Lightmaps.clear();
+ Rebuild();
+ SetDirty();
+}
+
+void LightmapSettings::SetLightmaps (const std::vector<LightmapData>& data)
+{
+ m_Lightmaps = data;
+ Rebuild();
+ SetDirty();
+}
+
+void LightmapSettings::SetLightProbes (LightProbes* lightProbes)
+{
+ if (lightProbes && !GetBuildSettings().hasAdvancedVersion)
+ {
+ ErrorString ("Light probes require Unity Pro.");
+ return;
+ }
+
+ m_LightProbes = lightProbes;
+ SetDirty();
+}
+
+LightProbes* LightmapSettings::GetLightProbes ()
+{
+ return m_LightProbes;
+}
+
+bool LightmapSettings::GetUseDualLightmapsInForward () const
+{
+ if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion3_5_a1))
+ return (m_LightmapsMode == kDualLightmapsMode) && m_UseDualLightmapsInForward;
+ else
+ return m_UseDualLightmapsInForward;
+}
+
+void LightmapSettings::AppendLightmaps (const std::vector<LightmapData>& data)
+{
+ int originalSize = m_Lightmaps.size();
+ int dataSize = data.size();
+
+ if (originalSize + data.size() > LightmapSettings::kMaxLightmaps)
+ {
+ int newDataSize = max(0, LightmapSettings::kMaxLightmaps - originalSize);
+ ErrorString(Format(
+ "Can't append %i lightmaps, since that would exceed the %i lightmaps limit. "
+ "Appending only %i lightmaps. Objects that use lightmaps past that limit won't get proper lightmaps.",
+ dataSize, LightmapSettings::kMaxLightmaps, newDataSize));
+ dataSize = newDataSize;
+ }
+ if ( dataSize <= 0 ) return;
+ m_Lightmaps.resize(originalSize + dataSize);
+ std::copy(data.begin(), data.begin() + dataSize, m_Lightmaps.begin() + originalSize);
+
+ Rebuild();
+ SetDirty();
+}
+
+#if ENABLE_SCRIPTING
+void LightmapDataToMono (const LightmapData &src, LightmapDataMono &dest) {
+ dest.m_Lightmap = Scripting::ScriptingWrapperFor (src.m_Lightmap);
+ dest.m_IndirectLightmap = Scripting::ScriptingWrapperFor (src.m_IndirectLightmap);
+}
+void LightmapDataToCpp (LightmapDataMono &src, LightmapData &dest) {
+ dest.m_Lightmap = ScriptingObjectToObject<Texture2D> (src.m_Lightmap);
+ dest.m_IndirectLightmap = ScriptingObjectToObject<Texture2D> (src.m_IndirectLightmap);
+}
+#endif
+
+IMPLEMENT_CLASS (LightmapSettings)
+IMPLEMENT_OBJECT_SERIALIZE (LightmapSettings)
+GET_MANAGER (LightmapSettings)
diff --git a/Runtime/Graphics/LightmapSettings.h b/Runtime/Graphics/LightmapSettings.h
new file mode 100644
index 0000000..c8e8a65
--- /dev/null
+++ b/Runtime/Graphics/LightmapSettings.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "Runtime/Math/Color.h"
+#include "Texture2D.h"
+#include "Runtime/BaseClasses/GameManager.h"
+#include "Runtime/Utilities/NonCopyable.h"
+#include "Runtime/Utilities/triple.h"
+#if UNITY_EDITOR
+#include "Editor/Src/LightmapEditorSettings.h"
+#endif
+#include "Runtime/Camera/LightProbes.h"
+
+class LightmapData
+{
+public:
+ PPtr<Texture2D> m_Lightmap;
+ PPtr<Texture2D> m_IndirectLightmap;
+ PPtr<Texture2D> m_ThirdLightmap;
+
+ DECLARE_SERIALIZE(LightmapData)
+};
+
+struct LightmapDataMono {
+ ScriptingObjectPtr m_Lightmap;
+ ScriptingObjectPtr m_IndirectLightmap;
+};
+void LightmapDataToMono (const LightmapData &src, LightmapDataMono &dest);
+void LightmapDataToCpp (LightmapDataMono &src, LightmapData &dest);
+
+class LightmapSettings : public LevelGameManager
+{
+public:
+ REGISTER_DERIVED_CLASS (LightmapSettings, LevelGameManager)
+ DECLARE_OBJECT_SERIALIZE(LightmapSettings)
+
+ // Lightmap index is stored as UInt8,
+ // 2 indices are reserved.
+ static const int kMaxLightmaps = 254;
+
+ LightmapSettings(MemLabelId label, ObjectCreationMode mode);
+
+ typedef triple<TextureID> TextureTriple;
+ TextureTriple GetLightmapTexture (UInt32 index) const
+ {
+ if (index < m_LightmapTextureCount)
+ return m_LightmapTextures[index];
+ else
+ return triple<TextureID>(TextureID(),TextureID(), TextureID());
+ }
+
+ const std::vector<LightmapData>& GetLightmaps () { return m_Lightmaps; }
+ void ClearLightmaps();
+ void SetLightmaps (const std::vector<LightmapData>& data);
+ void AppendLightmaps (const std::vector<LightmapData>& data);
+
+ void SetBakedColorSpace(ColorSpace colorSpace) { m_BakedColorSpace = (int)colorSpace; }
+ ColorSpace GetBakedColorSpace() { return (ColorSpace)m_BakedColorSpace; }
+
+ enum LightmapsMode {
+ kSingleLightmapsMode = 0,
+ kDualLightmapsMode = 1,
+ kDirectionalLightmapsMode = 2,
+ kRNMMode = 3,
+ kLightmapsModeCount // keep this last
+ };
+
+ static const char* const kLightmapsModeNames[kLightmapsModeCount];
+
+ GET_SET(int, LightmapsMode, m_LightmapsMode);
+ bool GetUseDualLightmapsInForward () const;
+
+ #if UNITY_EDITOR
+ LightmapEditorSettings& GetLightmapEditorSettings() { return m_LightmapEditorSettings; };
+ #endif
+
+ void AwakeFromLoad(AwakeFromLoadMode mode);
+
+ void SetLightProbes (LightProbes* lightProbes);
+ LightProbes* GetLightProbes ();
+
+private:
+ void Rebuild();
+
+private:
+ TextureTriple* m_LightmapTextures;
+ int m_LightmapTextureCount;
+
+ PPtr<LightProbes> m_LightProbes;
+
+ std::vector<LightmapData> m_Lightmaps;
+ int m_LightmapsMode; // LightmapsMode
+ int m_BakedColorSpace;
+ bool m_UseDualLightmapsInForward;
+
+ #if UNITY_EDITOR
+ LightmapEditorSettings m_LightmapEditorSettings;
+ #endif
+};
+
+LightmapSettings& GetLightmapSettings ();
+
diff --git a/Runtime/Graphics/LowerResBlitTexture.h b/Runtime/Graphics/LowerResBlitTexture.h
new file mode 100644
index 0000000..b96af45
--- /dev/null
+++ b/Runtime/Graphics/LowerResBlitTexture.h
@@ -0,0 +1,61 @@
+#ifndef LowerResBlitTexture_h
+#define LowerResBlitTexture_h
+
+#include "Runtime/Graphics/Texture.h"
+#include "Runtime/GfxDevice/TextureIdMap.h"
+
+// This is used by the OSX/Linux/iOS standalones for scaling up the viewport to the screen resolution.
+class LowerResBlitTexture
+: public Texture
+{
+public:
+
+ LowerResBlitTexture(MemLabelId label, ObjectCreationMode mode) : Texture(label, mode) {}
+
+ unsigned w, h;
+
+ void Create(unsigned tex, unsigned w_, unsigned h_)
+ {
+ #if ENABLE_TEXTUREID_MAP
+ TextureIdMap::UpdateTexture(m_TexID, tex);
+ #else
+ m_TexID = TextureID(tex);
+ #endif
+
+
+
+ w = w_; h = h_;
+ m_TexelSizeX = 1.0f / w_;
+ m_TexelSizeY = 1.0f / h_;
+ }
+
+ virtual TextureDimension GetDimension () const { return kTexDim2D; }
+ virtual bool ExtractImage (ImageReference* /*image*/, int /*imageIndex*/) const { return false; }
+ virtual int GetStorageMemorySize() const { return 0; }
+
+ virtual int GetDataWidth() const { return w; }
+ virtual int GetDataHeight() const { return h; }
+
+ virtual bool HasMipMap () const { return false; }
+ virtual int CountMipmaps () const { return 1; }
+
+ virtual void UnloadFromGfxDevice(bool forceUnloadAll) {}
+ virtual void UploadToGfxDevice() {}
+ virtual void ApplySettings () {}
+};
+
+
+static LowerResBlitTexture* CreateBlitTexture()
+{
+ static LowerResBlitTexture* _BlitTex = 0;
+ if(_BlitTex == 0)
+ {
+ _BlitTex = CreateObjectFromCode<LowerResBlitTexture>();
+ _BlitTex->SetHideFlags(Object::kHideAndDontSave);
+ }
+
+ return _BlitTex;
+}
+
+
+#endif
diff --git a/Runtime/Graphics/MatrixStack.cpp b/Runtime/Graphics/MatrixStack.cpp
new file mode 100644
index 0000000..58e6f12
--- /dev/null
+++ b/Runtime/Graphics/MatrixStack.cpp
@@ -0,0 +1,72 @@
+#include "UnityPrefix.h"
+#include "MatrixStack.h"
+#include "Runtime/Math/Matrix4x4.h"
+
+void MatrixStack::Reset()
+{
+ m_Depth = 1;
+ m_Matrices[0].SetIdentity();
+}
+
+Matrix4x4f& MatrixStack::GetMatrix4x4f( int index )
+{
+ Assert(index >= 0 && index < m_Depth);
+ return m_Matrices[index];
+}
+
+void MatrixStack::Push()
+{
+ if( m_Depth >= kStackDepth )
+ {
+ ErrorString( "Matrix stack full depth reached" );
+ return;
+ }
+ ++m_Depth;
+ CopyMatrix (m_Matrices[m_Depth-2].GetPtr(), m_Matrices[m_Depth-1].GetPtr());
+}
+
+void MatrixStack::Push (const float matrix[16])
+{
+ if( m_Depth >= kStackDepth )
+ {
+ ErrorString( "Matrix stack full depth reached" );
+ return;
+ }
+ ++m_Depth;
+ CopyMatrix (matrix, m_Matrices[m_Depth-2].GetPtr());
+}
+
+void MatrixStack::Pop()
+{
+ if( m_Depth < 2 )
+ {
+ ErrorString( "Matrix stack empty" );
+ return;
+ }
+ --m_Depth;
+}
+
+void MatrixStack::SetMatrix (const float matrix[16])
+{
+ CopyMatrix (matrix, m_Matrices[m_Depth-1].GetPtr());
+}
+
+void MatrixStack::SetCurrentIdentity()
+{
+ m_Matrices[m_Depth-1].SetIdentity();
+}
+
+void MatrixStack::MultMatrix( const float matrix[16] )
+{
+ const Matrix4x4f& a = *reinterpret_cast<const Matrix4x4f*>(matrix);
+ Matrix4x4f& b = GetMatrix4x4f( m_Depth-1 );
+ Matrix4x4f c;
+ MultiplyMatrices4x4 (&b, &a, &c);
+ CopyMatrix (c.GetPtr(), b.GetPtr());
+}
+
+const Matrix4x4f& MatrixStack::GetMatrix() const
+{
+ Assert(m_Depth >= 1 && m_Depth <= kStackDepth);
+ return m_Matrices[m_Depth-1];
+}
diff --git a/Runtime/Graphics/MatrixStack.h b/Runtime/Graphics/MatrixStack.h
new file mode 100644
index 0000000..fe11a18
--- /dev/null
+++ b/Runtime/Graphics/MatrixStack.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "Runtime/Math/Matrix4x4.h"
+
+
+class MatrixStack
+{
+public:
+ enum { kStackDepth = 16 };
+
+public:
+ MatrixStack() { Reset(); }
+
+ void Reset();
+
+ void SetMatrix( const float matrix[16] );
+ void SetCurrentIdentity();
+ void MultMatrix( const float matrix[16] );
+ const Matrix4x4f& GetMatrix() const;
+
+ void Push(const float* matrix);
+ void Push();
+ void Pop();
+
+ int GetCurrentDepth() const { return m_Depth; }
+
+private:
+ Matrix4x4f& GetMatrix4x4f (int index);
+
+ Matrix4x4f m_Matrices[kStackDepth];
+ int m_Depth;
+};
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp
new file mode 100644
index 0000000..362fbff
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp
@@ -0,0 +1,102 @@
+#include "UnityPrefix.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "ClampVelocityModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Misc/BuildSettings.h"
+
+inline float DampenOutsideLimit (float v, float limit, float dampen)
+{
+ float sgn = Sign (v);
+ float abs = Abs (v);
+ if (abs > limit)
+ abs = Lerp(abs, limit, dampen);
+ return abs * sgn;
+}
+
+ClampVelocityModule::ClampVelocityModule () : ParticleSystemModule(false)
+, m_SeparateAxis (false)
+, m_InWorldSpace (false)
+, m_Dampen (1.0f)
+{
+}
+
+template<ParticleSystemCurveEvalMode mode>
+void MagnitudeUpdateTpl(const MinMaxCurve& magnitude, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, float dampen)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ float limit = Evaluate<mode> (magnitude, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemClampVelocityCurveId));
+ Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ vel = NormalizeSafe (vel) * DampenOutsideLimit (Magnitude (vel), limit, dampen);
+ ps.velocity[q] = vel - ps.animatedVelocity[q];
+ }
+}
+
+void ClampVelocityModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ if (m_SeparateAxis)
+ {
+ Matrix4x4f matrix;
+ Matrix4x4f invMatrix;
+ bool transform;
+ if(IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_0_a1))
+ transform = GetTransformationMatrices(matrix, invMatrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+ else
+ transform = false; // Revert to old broken behavior
+
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemClampVelocityCurveId);
+ const float time = NormalizedTime(ps, q);
+
+ Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ if(transform)
+ vel = matrix.MultiplyVector3 (vel);
+ const Vector3f limit (Evaluate (m_X, time, random.x), Evaluate (m_Y, time, random.y), Evaluate (m_Z, time, random.z));
+ vel.x = DampenOutsideLimit (vel.x, limit.x, m_Dampen);
+ vel.y = DampenOutsideLimit (vel.y, limit.y, m_Dampen);
+ vel.z = DampenOutsideLimit (vel.z, limit.z, m_Dampen);
+ vel = vel - ps.animatedVelocity[q];
+ if(transform)
+ vel = invMatrix.MultiplyVector3 (vel);
+ ps.velocity[q] = vel;
+ }
+ }
+ else
+ {
+ if(m_Magnitude.minMaxState == kMMCScalar)
+ MagnitudeUpdateTpl<kEMScalar> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ else if(m_Magnitude.IsOptimized() && m_Magnitude.UsesMinMax())
+ MagnitudeUpdateTpl<kEMOptimizedMinMax> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ else if(m_Magnitude.IsOptimized())
+ MagnitudeUpdateTpl<kEMOptimized> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ else
+ MagnitudeUpdateTpl<kEMSlow> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ }
+}
+
+void ClampVelocityModule::CheckConsistency ()
+{
+ m_Dampen = clamp<float> (m_Dampen, 0.0f, 1.0f);
+ m_X.SetScalar(std::max<float> (0.0f, m_X.GetScalar()));
+ m_Y.SetScalar(std::max<float> (0.0f, m_Y.GetScalar()));
+ m_Z.SetScalar(std::max<float> (0.0f, m_Z.GetScalar()));
+ m_Magnitude.SetScalar(std::max<float> (0.0f, m_Magnitude.GetScalar()));
+}
+
+
+template<class TransferFunction>
+void ClampVelocityModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_X, "x");
+ transfer.Transfer (m_Y, "y");
+ transfer.Transfer (m_Z, "z");
+ transfer.Transfer (m_Magnitude, "magnitude");
+ transfer.Transfer (m_SeparateAxis, "separateAxis");
+ transfer.Transfer (m_InWorldSpace, "inWorldSpace"); transfer.Align ();
+ transfer.Transfer (m_Dampen, "dampen");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ClampVelocityModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h
new file mode 100644
index 0000000..e315d61
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h
@@ -0,0 +1,29 @@
+#ifndef SHURIKENMODULECLAMPVELOCITY_H
+#define SHURIKENMODULECLAMPVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class ClampVelocityModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ClampVelocityModule)
+ ClampVelocityModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void CheckConsistency ();
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_X;
+ MinMaxCurve m_Y;
+ MinMaxCurve m_Z;
+ MinMaxCurve m_Magnitude;
+ bool m_InWorldSpace;
+ bool m_SeparateAxis;
+ float m_Dampen;
+};
+
+#endif // SHURIKENMODULECLAMPVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp
new file mode 100644
index 0000000..9e16bde
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp
@@ -0,0 +1,603 @@
+#include "UnityPrefix.h"
+#include <float.h>
+#include "CollisionModule.h"
+
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Interfaces/IRaycast.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+#include "Editor/Src/Utility/DebugPrimitives.h"
+
+struct ParticleSystemCollisionParameters
+{
+ float bounceFactor;
+ float energyLossOnCollision;
+ float minKillSpeedSqr;
+ float particleRadius;
+ float dampen;
+
+ PlaneColliderCache* planeColliderCache;
+ IRaycast* raycaster;
+ size_t rayBudget;
+ size_t nextParticleToTrace;
+ float voxelSize;
+};
+
+struct ColliderInfo
+{
+ Plane m_CollisionPlane;
+ bool m_Traced;
+ int m_ColliderInstanceID;
+ int m_RigidBodyOrColliderInstanceID;
+};
+
+struct CollisionInfo
+{
+ CollisionInfo():m_NumWorldCollisions(0),m_NumCachedCollisions(0),m_NumPlaneCollisions(0),m_Colliders(NULL) {}
+
+ size_t m_NumWorldCollisions;
+ size_t m_NumCachedCollisions;
+ size_t m_NumPlaneCollisions;
+ ColliderInfo* m_Colliders;
+
+ size_t AllCollisions() const { return m_NumWorldCollisions+m_NumCachedCollisions+m_NumPlaneCollisions; }
+};
+
+/// @TODO: Why does Vector3f has a constructor? WTF.
+inline void CalculateCollisionResponse(const ParticleSystemReadOnlyState& roState,
+ ParticleSystemState& state,
+ ParticleSystemParticles& ps,
+ const size_t q,
+ const ParticleSystemCollisionParameters& params,
+ const Vector3f& position,
+ const Vector3f& velocity,
+ const HitInfo& hitInfo)
+{
+ // Reflect + dampen
+ Vector3f positionOffset = ReflectVector (position - hitInfo.intersection, hitInfo.normal) * params.dampen;
+ Vector3f newVelocity = ReflectVector(velocity, hitInfo.normal) * params.dampen;
+
+ // Apply bounce
+ positionOffset -= hitInfo.normal * (Dot(positionOffset, hitInfo.normal)) * params.bounceFactor;
+ newVelocity -= hitInfo.normal * Dot(newVelocity, hitInfo.normal) * params.bounceFactor;
+
+ ps.position[q] = hitInfo.intersection + positionOffset;
+ ps.velocity[q] = newVelocity - ps.animatedVelocity[q];
+
+ for(int s = 0; s < state.numCachedSubDataCollision; s++)
+ {
+ ParticleSystemEmissionState emissionState;
+ RecordEmit(emissionState, state.cachedSubDataCollision[s], roState, state, ps, kParticleSystemSubTypeCollision, s, q, 0.0f, 0.0001f, 1.0f);
+ }
+ ps.lifetime[q] -= params.energyLossOnCollision * ps.startLifetime[q];
+
+ if (ps.GetUsesCollisionEvents () && !(hitInfo.colliderInstanceID == 0))
+ {
+ Vector3f wcIntersection = hitInfo.intersection;
+ Vector3f wcNormal = hitInfo.normal;
+ Vector3f wcVelocity = velocity;
+ if ( roState.useLocalSpace )
+ {
+ wcIntersection = state.localToWorld.MultiplyPoint3 (wcIntersection);
+ wcNormal = state.localToWorld.MultiplyVector3 (wcNormal);
+ wcVelocity = state.localToWorld.MultiplyVector3 (wcVelocity);
+ }
+ ps.collisionEvents.AddEvent (ParticleCollisionEvent (wcIntersection, wcNormal, wcVelocity, hitInfo.colliderInstanceID, hitInfo.rigidBodyOrColliderInstanceID));
+ //printf_console (Format ("Intersected '%s' -> id: %d -> go id: %d.\n", hitInfo.collider->GetName (), hitInfo.collider->GetInstanceID (), hitInfo.collider->GetGameObject ().GetInstanceID ()).c_str ());
+}
+}
+
+CollisionModule::CollisionModule () : ParticleSystemModule(false)
+, m_Type(0)
+, m_Dampen(0.0f)
+, m_Bounce(1.0f)
+, m_EnergyLossOnCollision(0.0f)
+, m_MinKillSpeed(0.0f)
+, m_ParticleRadius(0.01f)
+, m_Quality(0)
+, m_VoxelSize(0.5f)
+, m_CollisionMessages(false)
+{
+ m_CollidesWith.m_Bits = 0xFFFFFFFF;
+}
+
+void CollisionModule::AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state)
+{
+ Assert(!state.cachedCollisionPlanes);
+
+ Matrix4x4f matrix = Matrix4x4f::identity;
+ if(roState.useLocalSpace)
+ matrix = state.worldToLocal;
+
+ state.numCachedCollisionPlanes = 0;
+ for (unsigned i=0; i<kMaxNumPrimitives; ++i)
+ {
+ if (m_Primitives[i].IsNull ())
+ continue;
+ state.numCachedCollisionPlanes++;
+ }
+
+ state.cachedCollisionPlanes = ALLOC_TEMP_MANUAL(Plane, state.numCachedCollisionPlanes);
+
+ int planeCount = 0;
+
+ for (unsigned i=0; i<kMaxNumPrimitives; ++i)
+ {
+ if (m_Primitives[i].IsNull ())
+ continue;
+
+ const Transform* transform = m_Primitives[i]->QueryComponent(Transform);
+ const Vector3f position = matrix.MultiplyPoint3(transform->GetPosition());
+
+ Assert(planeCount <= state.numCachedCollisionPlanes);
+ const Vector3f normal = matrix.MultiplyVector3(RotateVectorByQuat (transform->GetRotation (), Vector3f::yAxis));
+ state.cachedCollisionPlanes[planeCount].SetNormalAndPosition (normal, position);
+ state.cachedCollisionPlanes[planeCount].NormalizeRobust();
+ planeCount++;
+ }
+}
+
+void CollisionModule::FreeCache(ParticleSystemState& state)
+{
+ if(state.cachedCollisionPlanes)
+ {
+ FREE_TEMP_MANUAL(state.cachedCollisionPlanes);
+ state.cachedCollisionPlanes = 0;
+ state.numCachedCollisionPlanes = 0;
+ }
+}
+
+// read the plane cache for a given range
+size_t ReadCache(const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, CollisionInfo& collision, const size_t fromIndex, const size_t& toIndex, const float dt)
+{
+ size_t numIntersections = 0;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ // initialise plane to value that guarantees no intersection
+ collision.m_Colliders[q].m_CollisionPlane.distance = FLT_MAX;
+ collision.m_Colliders[q].m_Traced = false;
+ collision.m_Colliders[q].m_ColliderInstanceID = -1;
+ collision.m_Colliders[q].m_RigidBodyOrColliderInstanceID = -1;
+
+ // build start/end points
+ Vector3f to = ps.position[q];
+ const Vector3f v = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f d = v * dt;
+ Vector3f from = to - d;
+ // convert to WC
+ if ( roState.useLocalSpace )
+ {
+ from = state.localToWorld.MultiplyPoint3(from);
+ to = state.localToWorld.MultiplyPoint3(to);
+ }
+
+ // lookup the cache
+ Plane plane;
+ int colliderInstanceID;
+ int rigidBodyOrColliderInstanceID;
+ if ( params.planeColliderCache->Find (from, to-from, plane, colliderInstanceID, rigidBodyOrColliderInstanceID, params.voxelSize) )
+ {
+ collision.m_Colliders[q].m_CollisionPlane = plane;
+ collision.m_Colliders[q].m_ColliderInstanceID = colliderInstanceID;
+ collision.m_Colliders[q].m_RigidBodyOrColliderInstanceID = rigidBodyOrColliderInstanceID;
+ numIntersections++;
+ }
+ }
+ return numIntersections;
+}
+
+// Perform ray casts. Ray casts are done in WC and results are transformed back into sim space if necessary.
+// There are three sets of indices:
+// [fromIndex ; params.nextParticleToTrace[ for these we do caching if the cache is available
+// [params.nextParticleToTrace ; params.nextParticleToTrace + params.rayBudget[ for these we trace fresh rays
+// [params.nextParticleToTrace + params.rayBudget ; toIndex[ for these we do caching if the cache is available
+CollisionInfo WorldCollision(const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, const size_t fromIndex, const int filter, const float dt)
+{
+ CollisionInfo collisionCounter;
+ const bool approximate = ( params.planeColliderCache ? true : false );
+ size_t numIntersections = 0;
+
+ const size_t toIndex = ps.array_size();
+
+ // pre range that is cached
+ const size_t traceRangeFrom = approximate ? params.nextParticleToTrace : fromIndex;
+ const size_t traceRangeTo = approximate ? std::min(params.nextParticleToTrace+params.rayBudget, toIndex) : toIndex;
+
+ const size_t fromIndex0 = fromIndex;
+ const size_t toIndex0 = traceRangeFrom;
+ // range to actually ray trace
+ const size_t fromIndex1 = traceRangeFrom;
+ const size_t toIndex1 = traceRangeTo;
+ // post range that is cached
+ const size_t fromIndex2 = traceRangeTo;
+ const size_t toIndex2 = toIndex;
+
+ collisionCounter.m_Colliders = ALLOC_TEMP_MANUAL(ColliderInfo, ps.array_size());
+ collisionCounter.m_NumCachedCollisions = (toIndex0-fromIndex0)+(toIndex2-fromIndex2);
+ collisionCounter.m_NumWorldCollisions = toIndex1-fromIndex1;
+
+ if ( toIndex1-fromIndex1 > 0 )
+ {
+ // batch trace selected range
+ dynamic_array< BatchedRaycast > rayBatch(toIndex1-fromIndex1,kMemTempAlloc);
+ dynamic_array< BatchedRaycastResult > rayResults(toIndex1-fromIndex1,kMemTempAlloc);
+ // build request array
+ size_t i = 0;
+ for (size_t q = fromIndex1; q < toIndex1; ++q, ++i)
+ {
+ // build start/end points
+ const Vector3f to = ps.position[q];
+ const Vector3f v = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f d = v * dt;
+ const Vector3f from = to - d;
+
+ if ( approximate )
+ {
+ // extend ray to trace across the entire voxel (and then some)
+ const float voxSize = std::max(params.voxelSize,params.voxelSize*kVoxelHeightMultiplier);
+ const Vector3f displacement = to-from;
+
+ ///@TODO: handle magnitude 0. Should we skip the raycast???
+ const Vector3f direction = NormalizeFast(displacement);
+ const Vector3f extendedTo = from + direction * voxSize;
+
+ // insert into batch
+ rayBatch[i] = BatchedRaycast(q,from,extendedTo);
+ }
+ else
+ rayBatch[i] = BatchedRaycast(q,from,to);
+
+ // initialise plane to value that guarantees no intersection
+ collisionCounter.m_Colliders[q].m_CollisionPlane.distance = FLT_MAX;
+ collisionCounter.m_Colliders[q].m_Traced = (!approximate);
+ collisionCounter.m_Colliders[q].m_ColliderInstanceID = -1;
+ collisionCounter.m_Colliders[q].m_RigidBodyOrColliderInstanceID = -1;
+ }
+ // convert to WC
+ if ( roState.useLocalSpace )
+ {
+ const Matrix4x4f m = state.localToWorld;
+ for (size_t i = 0; i < rayBatch.size(); ++i)
+ {
+ rayBatch[i].from = m.MultiplyPoint3(rayBatch[i].from);
+ rayBatch[i].to = m.MultiplyPoint3(rayBatch[i].to);
+ }
+ }
+ // trace the rays
+ const size_t numIx = params.raycaster->BatchIntersect( rayBatch, rayResults, filter, approximate );
+
+ // convert back to local space
+ if ( roState.useLocalSpace )
+ {
+ const Matrix4x4f m = state.worldToLocal;
+ for (size_t i = 0; i < numIx; ++i)
+ {
+ // the plane intersection was computed in WC, transform intersection to local space.
+ rayResults[i].hitInfo.intersection = m.MultiplyPoint3( rayResults[i].hitInfo.intersection );
+ rayResults[i].hitInfo.normal = m.MultiplyVector3( rayResults[i].hitInfo.normal );
+ }
+ }
+
+ // store planes in the particles that intersected something
+ for (size_t i = 0; i < numIx; ++i)
+ {
+ const size_t q = rayBatch[rayResults[i].index].index;
+ collisionCounter.m_Colliders[q].m_CollisionPlane.normal = rayResults[i].hitInfo.normal;
+ collisionCounter.m_Colliders[q].m_CollisionPlane.distance = -Dot(rayResults[i].hitInfo.intersection, rayResults[i].hitInfo.normal);
+ collisionCounter.m_Colliders[q].m_ColliderInstanceID = rayResults[i].hitInfo.colliderInstanceID;
+ collisionCounter.m_Colliders[q].m_RigidBodyOrColliderInstanceID = rayResults[i].hitInfo.rigidBodyOrColliderInstanceID;
+ }
+
+ // store intersections in cache
+ if (approximate)
+ {
+ for (size_t i = 0; i < numIx; ++i)
+ {
+ const size_t r = rayResults[i].index;
+ const size_t q = rayBatch[rayResults[i].index].index;
+ params.planeColliderCache->Replace (rayBatch[r].from, rayBatch[r].to-rayBatch[r].from, Plane(collisionCounter.m_Colliders[q].m_CollisionPlane), collisionCounter.m_Colliders[q].m_ColliderInstanceID, collisionCounter.m_Colliders[q].m_RigidBodyOrColliderInstanceID, params.voxelSize);
+ }
+ }
+ numIntersections += numIx;
+ }
+
+ // process pre cache range
+ if ( toIndex0-fromIndex0 > 0 )
+ {
+ numIntersections += ReadCache(roState, state, ps, params, collisionCounter, fromIndex0, toIndex0, dt);
+ }
+ // process post cache range
+ if ( toIndex2-fromIndex2 > 0 )
+ {
+ numIntersections += ReadCache(roState, state, ps, params, collisionCounter, fromIndex2, toIndex2, dt);
+ }
+
+ return collisionCounter;
+}
+
+
+// Plane collide all particles in simulation space (remember the cached planes are defined in sim space).
+CollisionInfo PlaneCollision(const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, const int fromIndex, const float dt)
+{
+ CollisionInfo collisionInfo;
+ collisionInfo.m_Colliders = ALLOC_TEMP_MANUAL(ColliderInfo, ps.array_size());
+
+ const bool newBehaviour = IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_0_a1);
+
+ size_t toIndex = ps.array_size();
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ // initialise to value that guarantees no intersection
+ collisionInfo.m_Colliders[q].m_CollisionPlane.distance = FLT_MAX;
+
+ const Vector3f position = ps.position[q];
+ const Vector3f velocity = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f displacement = velocity * dt;
+ const Vector3f origin = position - displacement;
+
+ // walk the planes
+ for (unsigned i=0; i<state.numCachedCollisionPlanes; ++i)
+ {
+ const Plane plane = state.cachedCollisionPlanes[i];
+
+ if ( newBehaviour )
+ {
+ // new behaviour:
+ // plane collisions are single sided only, all particles 'behind' the plane will be forced onto the plane
+ const float dist = plane.GetDistanceToPoint(position);
+ if (dist > params.particleRadius)
+ continue;
+ }
+ else
+ {
+ // old (but fixed) behaviour, particles can collide with both front and back plane
+ const float d0 = plane.GetDistanceToPoint(origin);
+ const float d1 = plane.GetDistanceToPoint(position);
+ const bool sameSide = ( (d0 > 0.0f && d1 > 0.0f) || (d0 <= 0.0f && d1 <= 0.0f) );
+ const float aD0 = Abs(d0);
+ const float aD1 = Abs(d1);
+ if ( sameSide && aD0 > params.particleRadius && aD1 > params.particleRadius )
+ continue;
+ }
+
+ collisionInfo.m_Colliders[q].m_CollisionPlane = plane;
+ collisionInfo.m_Colliders[q].m_ColliderInstanceID = 0;
+ collisionInfo.m_Colliders[q].m_RigidBodyOrColliderInstanceID = 0;
+ collisionInfo.m_NumPlaneCollisions++;
+ break;
+ }
+ }
+
+ return collisionInfo;
+}
+
+// Compute the collision plane to use for each particle
+CollisionInfo UpdateCollisionPlanes(bool worldCollision, UInt32 collisionFilter, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, const ParticleSystemCollisionParameters& params, ParticleSystemParticles& ps, size_t fromIndex, float dt)
+{
+ if( worldCollision )
+ {
+ // Check if we support raycasting
+ if (params.raycaster == NULL)
+ return CollisionInfo();
+
+ ////@TODO: dtPerParticle
+ return WorldCollision(roState, state, ps, params, fromIndex, collisionFilter, dt);
+ }
+ else
+ {
+ ////@TODO: dtPerParticle
+ return PlaneCollision(roState, state, ps, params, fromIndex, dt);
+ }
+}
+
+// Collide the particles against their selected planes and update collision response - this is done purely in simulation space
+static const float kEpsilon = 0.000001f;
+static const float kRayEpsilon = 0.00001f;
+void PerformPlaneCollisions(bool worldCollision, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, const CollisionInfo& collisionInfo, const int fromIndex, const float dt)
+{
+ // for world collisions we use and epsilon but for plane intersections we use the particle radius
+ const float radius = worldCollision ? kRayEpsilon : params.particleRadius;
+ const bool newBehaviour = IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_0_a1);
+ //const bool approximate = ( params.planeColliderCache ? true : false );
+
+ // collide with plane
+ size_t toIndex = ps.array_size();
+
+ for (size_t q = fromIndex; q < toIndex; q++)
+ {
+ // check if the actual ray was traced in which case we want to disable the ray testing. Note: does not apply to approximate mode as we trace longer rays to improve cache quality
+ const bool tracedParticle = collisionInfo.m_Colliders[q].m_Traced;
+ const Plane plane = collisionInfo.m_Colliders[q].m_CollisionPlane;
+ if ( plane.distance != FLT_MAX )
+ {
+ const Vector3f position = ps.position[q];
+ const Vector3f velocity = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f displacement = velocity * dt;
+ const Vector3f origin = position - displacement;
+
+ HitInfo hit;
+ hit.normal = plane.GetNormal();
+ hit.colliderInstanceID = collisionInfo.m_Colliders[q].m_ColliderInstanceID;
+ hit.rigidBodyOrColliderInstanceID = collisionInfo.m_Colliders[q].m_RigidBodyOrColliderInstanceID;
+
+ if ( worldCollision )
+ {
+ // plane ray dot product
+ const float VdN = Dot( plane.GetNormal(), displacement );
+ if ( !tracedParticle && VdN >= 0 )
+ continue; // only pick up front face intersections
+
+ // Recreate hit point.
+ const float t = -( Dot( origin, plane.GetNormal() ) + plane.distance ) / VdN;
+ if ( !tracedParticle && (t < 0 || t > 1) )
+ continue;
+
+ // build intersection description from t value and ray
+ hit.intersection = origin + displacement * t;
+
+ // Adjust intersection along normal to make sure the particle doesn't fall through geometry when it comes to rest.
+ // This is also an issue when dampen and bounce is zero in which case CalculateCollisionResponse will set the particle
+ // position to *exactly* the intersection point, which will then have issues next time the intersection is executed
+ // where it will try and compare two floating point numbers which are equal and the intersection test will come out
+ // either way depending on fp accuracy
+ //
+ // for world collisions we use and epsilon but for plane intersections we use the particle radius
+ hit.intersection += radius * hit.normal;
+ }
+ else
+ {
+ if (newBehaviour)
+ {
+ const float dist = plane.GetDistanceToPoint(position);
+
+ if (dist > radius)
+ continue;
+
+ const float VdN = Dot( plane.GetNormal(), velocity );
+ if (VdN == 0.0F || VdN == -0.0F)
+ continue;
+
+ const float t = -( Dot( position, plane.GetNormal() ) + plane.distance - radius ) / VdN;
+
+ hit.intersection = position + velocity * t;
+ }
+ else
+ {
+ const float OriginDotPlane = Dot (plane.GetNormal(), origin);
+ const float signedDistanceToOrigin = OriginDotPlane + plane.distance;
+ const float PositionDotPlane = Dot (plane.GetNormal(), position);
+ const float signedDistanceToPosition = PositionDotPlane + plane.distance;
+ const bool originInside = Abs(signedDistanceToOrigin)<radius;
+ const bool positionInside = Abs(signedDistanceToPosition)<=radius;
+ const bool oppositeSide = !( (signedDistanceToOrigin > 0.0f && signedDistanceToPosition > 0.0f) || (signedDistanceToOrigin <= 0.0f && signedDistanceToPosition <= 0.0f) );
+
+ // if the points are both inside or outside the radius we can bail if they're not on opposite sides outside the radius
+ if ( originInside==positionInside && !(oppositeSide && !originInside && !positionInside ) )
+ continue;
+
+ // compute the side of the face we are on - this determines if we are trying to intersect with the front or back face of the plane
+ const float signOrigin = signedDistanceToOrigin < 0.0 ? -1.f : 1.f;
+ const float signedRadius = radius*signOrigin;
+
+ // check the direction of the ray is opposite to the plane normal (the sign flips the normal as appropriate)
+ const float VdN = Dot( plane.GetNormal(), velocity );
+ if (VdN*signOrigin >= 0)
+ continue;
+
+ // calculate intersection point
+ const float t = -( signedDistanceToPosition - signedRadius ) / VdN;
+ hit.intersection = position + velocity * t;
+ }
+ }
+
+ // compute the bounce
+ CalculateCollisionResponse(roState, state, ps, q, params, ps.position[q], ps.velocity[q] + ps.animatedVelocity[q], hit);
+
+ // Kill particle?
+ const float speedSqr = SqrMagnitude(ps.velocity[q] + ps.animatedVelocity[q]);
+ if (ps.lifetime[q] < 0.0f || speedSqr < params.minKillSpeedSqr)
+ {
+ // when killing a particle the last element from the array is pulled into the position of the killed particle and toIndex is updated accordingly
+ // we do a q-- in order to make sure the new particle at q is also collided
+ collisionInfo.m_Colliders[q] = collisionInfo.m_Colliders[toIndex-1];
+ KillParticle(roState, state, ps, q, toIndex);
+ q--;
+ }
+ }
+ }
+
+ // resize array to account for killed particles
+ ps.array_resize(toIndex);
+}
+
+// Update collision for the particle system.
+void CollisionModule::Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt )
+{
+ // any work todo?
+ if (fromIndex == ps.array_size())
+ return;
+
+ ps.SetUsesCollisionEvents (m_CollisionMessages);
+
+ // setup params
+ ParticleSystemCollisionParameters params;
+ params.bounceFactor = 1.0f - m_Bounce;
+ params.energyLossOnCollision = m_EnergyLossOnCollision;
+ params.minKillSpeedSqr = m_MinKillSpeed * m_MinKillSpeed;
+ params.particleRadius = m_ParticleRadius;
+ params.dampen = 1.0f - m_Dampen;
+ params.planeColliderCache = ( IsApproximate() ? &m_ColliderCache : NULL );
+ params.raycaster = GetRaycastInterface();
+ params.rayBudget = state.rayBudget;
+ params.voxelSize = m_VoxelSize;
+ params.nextParticleToTrace = ( state.nextParticleToTrace >= ps.array_size() ? fromIndex : std::max(state.nextParticleToTrace,fromIndex) );
+
+ // update particle collision planes
+ const CollisionInfo collisionCounter = UpdateCollisionPlanes(m_Type != kPlaneCollision, m_CollidesWith.m_Bits, roState, state, params, ps, fromIndex, dt);
+
+ // update collider index
+ state.nextParticleToTrace = params.nextParticleToTrace + params.rayBudget;
+
+ // decrement ray budget
+ state.rayBudget = (state.rayBudget>collisionCounter.m_NumWorldCollisions ? state.rayBudget-collisionCounter.m_NumWorldCollisions : 0);
+
+ // early out if there were no collisions at all
+ if ( collisionCounter.AllCollisions() <= 0 )
+ {
+ FREE_TEMP_MANUAL(collisionCounter.m_Colliders);
+ return;
+ }
+
+ // perform plane collisions
+ PerformPlaneCollisions(m_Type != kPlaneCollision, roState, state, ps, params, collisionCounter, fromIndex, dt);
+
+ FREE_TEMP_MANUAL(collisionCounter.m_Colliders);
+
+ if (ps.GetUsesCollisionEvents ())
+ {
+ ps.collisionEvents.SortCollisionEventThreadArray ();
+ }
+}
+
+void CollisionModule::CheckConsistency ()
+{
+ m_Dampen = clamp<float> (m_Dampen, 0.0f, 1.0f);
+ m_Bounce = clamp<float> (m_Bounce, 0.0f, 2.0f);
+ m_EnergyLossOnCollision = clamp<float> (m_EnergyLossOnCollision, 0.0f, 1.0f);
+ m_ParticleRadius = max<float>(m_ParticleRadius, 0.01f);
+}
+
+template<class TransferFunction>
+void CollisionModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Type, "type");
+
+ const char* kPrimitiveNames [kMaxNumPrimitives] = { "plane0", "plane1", "plane2", "plane3", "plane4", "plane5"};
+ for(int i = 0; i < kMaxNumPrimitives; i++)
+ transfer.Transfer (m_Primitives[i], kPrimitiveNames[i]);
+
+ transfer.Transfer (m_Dampen, "dampen");
+ transfer.Transfer (m_Bounce, "bounce");
+ transfer.Transfer (m_EnergyLossOnCollision, "energyLossOnCollision");
+ transfer.Transfer (m_MinKillSpeed, "minKillSpeed");
+ transfer.Transfer (m_ParticleRadius, "particleRadius");
+ transfer.Align();
+ transfer.Transfer (m_CollidesWith, "collidesWith");
+ transfer.Transfer (m_Quality, "quality");
+ transfer.Align();
+ transfer.Transfer (m_VoxelSize, "voxelSize");
+ transfer.Transfer (m_CollisionMessages, "collisionMessages");
+}
+
+INSTANTIATE_TEMPLATE_TRANSFER(CollisionModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h
new file mode 100644
index 0000000..77b5541
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h
@@ -0,0 +1,62 @@
+#pragma once
+#include "ParticleSystemModule.h"
+#include "Runtime/BaseClasses/BitField.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Utilities/SpatialHash.h"
+
+struct ParticleSystemParticles;
+class Transform;
+
+class CollisionModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (CollisionModule)
+ CollisionModule ();
+
+ void AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state);
+ static void FreeCache(ParticleSystemState& state);
+
+#if UNITY_EDITOR
+ float GetEnergyLoss() const { return m_EnergyLossOnCollision; }
+ void SetEnergyLoss(float value) { m_EnergyLossOnCollision = value; };
+ float GetMinKillSpeed() const { return m_MinKillSpeed; }
+ void SetMinKillSpeed(float value) { m_MinKillSpeed = value; };
+#endif
+
+ bool GetUsesCollisionMessages () const {return m_CollisionMessages;}
+ bool IsWorldCollision() const {return m_Type==kWorldCollision;}
+ bool IsApproximate() const {return m_Quality>0;}
+ int GetQuality() const {return m_Quality;}
+
+ void Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt);
+
+ void CheckConsistency ();
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ void ClearPrimitives();
+
+ enum { kPlaneCollision, kWorldCollision };
+ enum { kMaxNumPrimitives = 6 };
+
+ PlaneColliderCache m_ColliderCache;
+
+ // Serialized
+ int m_Type;
+ float m_Dampen;
+ float m_Bounce;
+ float m_EnergyLossOnCollision;
+ float m_MinKillSpeed;
+ float m_ParticleRadius;
+ /// Collides the particles with every collider whose layerMask & m_CollidesWith != 0
+ BitField m_CollidesWith;
+ /// Perform approximate world particle collisions
+ int m_Quality; // selected quality, 0 is high (no approximations), 1 is medium (approximate), 2 is low (approximate)
+ float m_VoxelSize;
+ bool m_CollisionMessages;
+ PPtr<Transform> m_Primitives [kMaxNumPrimitives];
+};
+
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp
new file mode 100644
index 0000000..0437322
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp
@@ -0,0 +1,60 @@
+#include "UnityPrefix.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "ColorByVelocityModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+template<MinMaxGradientEvalMode mode>
+void UpdateTpl(const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, const MinMaxGradient& gradient, const OptimizedMinMaxGradient& optGradient, const Vector2f offsetScale, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ const float time = InverseLerpFast01 (offsetScale, Magnitude(vel));
+ const int random = GenerateRandomByte(ps.randomSeed[q] + kParticleSystemColorByVelocityGradientId);
+
+ ColorRGBA32 value;
+ if(mode == kGEMGradient)
+ value = EvaluateGradient (optGradient, time);
+ else if(mode == kGEMGradientMinMax)
+ value = EvaluateRandomGradient (optGradient, time, random);
+ else
+ value = Evaluate (gradient, time, random);
+
+ colorTemp[q] *= value;
+ }
+}
+
+ColorBySpeedModule::ColorBySpeedModule () : ParticleSystemModule(false)
+, m_Range (0.0f, 1.0f)
+{}
+
+void ColorBySpeedModule::Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size());
+
+ Vector2f offsetScale = CalculateInverseLerpOffsetScale (m_Range);
+ OptimizedMinMaxGradient gradient;
+ m_Gradient.InitializeOptimized(gradient);
+ if(m_Gradient.minMaxState == kMMGGradient)
+ UpdateTpl<kGEMGradient>(ps, colorTemp, m_Gradient, gradient, offsetScale, fromIndex, toIndex);
+ else if(m_Gradient.minMaxState == kMMGRandomBetweenTwoGradients)
+ UpdateTpl<kGEMGradientMinMax>(ps, colorTemp, m_Gradient, gradient, offsetScale, fromIndex, toIndex);
+ else
+ UpdateTpl<kGEMSlow>(ps, colorTemp, m_Gradient, gradient, offsetScale, fromIndex, toIndex);
+}
+
+void ColorBySpeedModule::CheckConsistency ()
+{
+ const float MyEpsilon = 0.001f;
+ m_Range.x = std::min (m_Range.x, m_Range.y - MyEpsilon);
+}
+
+template<class TransferFunction>
+void ColorBySpeedModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Gradient, "gradient");
+ transfer.Transfer (m_Range, "range");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ColorBySpeedModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h
new file mode 100644
index 0000000..f140e68
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULECOLORBYVELOCITY_H
+#define SHURIKENMODULECOLORBYVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h"
+#include "Runtime/Math/Vector2.h"
+
+class ColorBySpeedModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ColorBySpeedModule)
+ ColorBySpeedModule ();
+
+ void Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxGradient& GetGradient() { return m_Gradient; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxGradient m_Gradient;
+ Vector2f m_Range;
+};
+
+#endif // SHURIKENMODULECOLORBYVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp
new file mode 100644
index 0000000..c9e78c0
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp
@@ -0,0 +1,51 @@
+#include "UnityPrefix.h"
+#include "ColorModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+template<MinMaxGradientEvalMode mode>
+void UpdateTpl(const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, const MinMaxGradient& gradient, const OptimizedMinMaxGradient& optGradient, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float time = NormalizedTime(ps, q);
+ const int random = GenerateRandomByte(ps.randomSeed[q] + kParticleSystemColorGradientId);
+
+ ColorRGBA32 value;
+ if(mode == kGEMGradient)
+ value = EvaluateGradient (optGradient, time);
+ else if(mode == kGEMGradientMinMax)
+ value = EvaluateRandomGradient (optGradient, time, random);
+ else
+ value = Evaluate (gradient, time, random);
+
+ colorTemp[q] *= value;
+ }
+}
+
+ColorModule::ColorModule () : ParticleSystemModule(false)
+{}
+
+void ColorModule::Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size());
+
+ OptimizedMinMaxGradient gradient;
+ m_Gradient.InitializeOptimized(gradient);
+ if(m_Gradient.minMaxState == kMMGGradient)
+ UpdateTpl<kGEMGradient>(ps, colorTemp, m_Gradient, gradient, fromIndex, toIndex);
+ else if(m_Gradient.minMaxState == kMMGRandomBetweenTwoGradients)
+ UpdateTpl<kGEMGradientMinMax>(ps, colorTemp, m_Gradient, gradient, fromIndex, toIndex);
+ else
+ UpdateTpl<kGEMSlow>(ps, colorTemp, m_Gradient, gradient, fromIndex, toIndex);
+}
+
+template<class TransferFunction>
+void ColorModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Gradient, "gradient");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ColorModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorModule.h b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.h
new file mode 100644
index 0000000..1fe7250
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.h
@@ -0,0 +1,25 @@
+#ifndef SHURIKENMODULECOLOR_H
+#define SHURIKENMODULECOLOR_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h"
+
+class ColorModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ColorModule)
+ ColorModule ();
+
+ void Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex);
+ void CheckConsistency() {};
+
+ inline MinMaxGradient& GetGradient() { return m_Gradient; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxGradient m_Gradient;
+};
+
+#endif // SHURIKENMODULECOLOR_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp
new file mode 100644
index 0000000..111b06f
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp
@@ -0,0 +1,124 @@
+#include "UnityPrefix.h"
+#include "EmissionModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Math/Vector2.h"
+
+static int AccumulateBursts (const ParticleSystemEmissionData& emissionData, float t0, float t1)
+{
+ int burstParticles = 0;
+ const size_t count = emissionData.burstCount;
+ for (size_t q = 0; q < count; ++q)
+ {
+ if (emissionData.burstTime[q] >= t0 && emissionData.burstTime[q] < t1)
+ burstParticles += emissionData.burstParticleCount[q];
+ }
+ return burstParticles;
+}
+
+static float AccumulateContinuous(const ParticleSystemEmissionData& emissionData, const float length, const float toT, const float dt)
+{
+ DebugAssert (length > 0.0001f);
+ DebugAssert (toT >= 0.0f);
+ DebugAssert (toT <= length);
+ float normalizedT = toT / length;
+ return std::max<float> (0.0f, Evaluate (emissionData.rate, normalizedT)) * dt;
+};
+
+EmissionModule::EmissionModule () : ParticleSystemModule(true)
+{
+ m_EmissionData.burstCount = 0;
+ m_EmissionData.type = kEmissionTypeTime;
+ for(int i = 0; i < ParticleSystemEmissionData::kMaxNumBursts; i++)
+ {
+ m_EmissionData.burstParticleCount[i] = 30;
+ m_EmissionData.burstTime[i] = 0.0f;
+ }
+}
+
+void EmissionModule::Emit (ParticleSystemEmissionState& emissionState, size_t& amountOfParticlesToEmit, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length)
+{
+ const float epsilon = 0.0001f;
+ if(kEmissionTypeTime == emissionData.type)
+ {
+ float rate = 0.0f;
+ float t0 = std::max<float> (0.0f, fromT);
+ float t1 = std::max<float> (0.0f, toT);
+ if (t1 < t0) // handle loop
+ {
+ rate += AccumulateContinuous (emissionData, length, t1, t1); // from start to current time
+ t1 = length; // from last time to end
+ }
+ rate += AccumulateContinuous (emissionData, length, t1, t1 - t0); // from start to current time
+
+ const float newParticles = rate;
+ if(newParticles >= epsilon)
+ emissionState.m_ParticleSpacing = 1.0f / newParticles;
+ else
+ emissionState.m_ParticleSpacing = 1.0f;
+
+ emissionState.m_ToEmitAccumulator += newParticles;
+ amountOfParticlesToEmit = (int)emissionState.m_ToEmitAccumulator;
+ emissionState.m_ToEmitAccumulator -= (float)amountOfParticlesToEmit;
+
+ // Continuous emits
+ numContinuous = amountOfParticlesToEmit;
+
+ // Bursts
+ t0 = std::max<float> (0.0f, fromT);
+ t1 = std::max<float> (0.0f, toT);
+ if (t1 < t0) // handle loop
+ {
+ amountOfParticlesToEmit += AccumulateBursts (emissionData, 0.0f, t1); // from start to current time
+ t1 = length + epsilon; // from last time to end
+ }
+ amountOfParticlesToEmit += AccumulateBursts (emissionData, t0, t1); // from start to current time
+ }
+ else
+ {
+ float newParticles = AccumulateContinuous (emissionData, length, toT, dt) * Magnitude (velocity); // from start to current time
+ if(newParticles >= epsilon)
+ emissionState.m_ParticleSpacing = 1.0f / newParticles;
+ else
+ emissionState.m_ParticleSpacing = 1.0f;
+
+ emissionState.m_ToEmitAccumulator += newParticles;
+ amountOfParticlesToEmit = (int)emissionState.m_ToEmitAccumulator;
+ emissionState.m_ToEmitAccumulator -= (float)amountOfParticlesToEmit;
+
+ // Continuous emits
+ numContinuous = amountOfParticlesToEmit;
+ }
+}
+
+void EmissionModule::CheckConsistency ()
+{
+ m_EmissionData.rate.SetScalar(std::max<float> (0.0f, m_EmissionData.rate.GetScalar()));
+
+ const size_t count = m_EmissionData.burstCount;
+ for (size_t q = 0; q < count; ++q)
+ {
+ m_EmissionData.burstTime[q] = std::max<float> (0.0f, m_EmissionData.burstTime[q]);
+ }
+}
+
+template<class TransferFunction>
+void EmissionModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+
+ transfer.Transfer (m_EmissionData.type, "m_Type");
+ transfer.Transfer (m_EmissionData.rate, "rate");
+
+ const char* kCountNames [ParticleSystemEmissionData::kMaxNumBursts] = { "cnt0", "cnt1", "cnt2", "cnt3", };
+ const char* kTimeNames [ParticleSystemEmissionData::kMaxNumBursts] = { "time0", "time1", "time2", "time3", };
+ for(int i = 0; i < ParticleSystemEmissionData::kMaxNumBursts; i++)
+ transfer.Transfer (m_EmissionData.burstParticleCount[i], kCountNames[i]);
+
+ for(int i = 0; i < ParticleSystemEmissionData::kMaxNumBursts; i++)
+ transfer.Transfer (m_EmissionData.burstTime[i], kTimeNames[i]);
+
+ transfer.Transfer (m_EmissionData.burstCount, "m_BurstCount"); transfer.Align();
+}
+
+INSTANTIATE_TEMPLATE_TRANSFER(EmissionModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h
new file mode 100644
index 0000000..75ad977
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h
@@ -0,0 +1,34 @@
+#ifndef SHURIKENMODULEEMISSION_H
+#define SHURIKENMODULEEMISSION_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class Vector2f;
+
+class EmissionModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (EmissionModule)
+ EmissionModule ();
+
+ enum { kEmissionTypeTime, kEmissionTypeDistance };
+
+ static void Emit (ParticleSystemEmissionState& emissionState, size_t& amountOfParticlesToEmit, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length);
+ void CheckConsistency ();
+
+ int CalculateMaximumEmitCountEstimate(float deltaTime) const;
+
+ const ParticleSystemEmissionData& GetEmissionDataRef() { return m_EmissionData; }
+ const ParticleSystemEmissionData& GetEmissionDataRef() const { return m_EmissionData; }
+ void GetEmissionDataCopy(ParticleSystemEmissionData* emissionData) { *emissionData = m_EmissionData; };
+ ParticleSystemEmissionData& GetEmissionData() { return m_EmissionData; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ ParticleSystemEmissionData m_EmissionData;
+};
+
+#endif // SHURIKENMODULEEMISSION_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp
new file mode 100644
index 0000000..0b86e79
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp
@@ -0,0 +1,118 @@
+#include "UnityPrefix.h"
+#include "ExternalForcesModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Geometry/Sphere.h"
+#include "Runtime/Terrain/Wind.h"
+#include "Runtime/Input/TimeManager.h"
+
+
+void ApplyRadialForce(ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, const Vector3f position, const float radius, const float force, const float dt)
+{
+ for(int q = fromIndex; q < toIndex; q++)
+ {
+ Vector3f toForce = position - ps.position[q];
+ float distanceToForce = Magnitude(toForce);
+ toForce = NormalizeSafe(toForce);
+ float forceFactor = clamp(distanceToForce/radius, 0.0f, 1.0f);
+ forceFactor = 1.0f - forceFactor * forceFactor;
+ Vector3f delta = toForce * force * forceFactor * dt;
+ ps.velocity[q] += delta;
+ }
+}
+
+void ApplyDirectionalForce(ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, const Vector3f position, const Vector3f direction, const float radius, const float force, const float dt)
+{
+ for(int q = fromIndex; q < toIndex; q++)
+ {
+ Vector3f delta = direction * force * dt;
+ ps.velocity[q] += delta;
+ }
+}
+
+ExternalForcesModule::ExternalForcesModule () : ParticleSystemModule(false)
+, m_Multiplier (1.0f)
+{
+}
+
+void ExternalForcesModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt)
+{
+ Matrix4x4f matrix = Matrix4x4f::identity;
+ if(roState.useLocalSpace)
+ Matrix4x4f::Invert_General3D(state.localToWorld, matrix);
+
+ AABB aabb = state.minMaxAABB;
+ for(int i = 0; i < state.numCachedForces; i++)
+ {
+ const ParticleSystemExternalCachedForce& cachedForce = state.cachedForces[i];
+ const Vector3f position = matrix.MultiplyPoint3(cachedForce.position);
+ const Vector3f direction = matrix.MultiplyVector3(cachedForce.direction);
+ const float radius = cachedForce.radius;
+ const float force = cachedForce.forceMain * m_Multiplier;
+
+ const WindZone::WindZoneMode forceType = (WindZone::WindZoneMode)cachedForce.forceType;
+ if(WindZone::Spherical == forceType)
+ {
+ Sphere sphere (position, radius);
+ if(!IntersectAABBSphere (aabb, sphere))
+ continue;
+ ApplyRadialForce(ps, fromIndex, toIndex, position, radius, force, dt);
+ }
+ else if(WindZone::Directional == forceType)
+ ApplyDirectionalForce(ps, fromIndex, toIndex, position, direction, radius, force, dt);
+ }
+}
+
+// TODO: Perform culling here, instead of caching all of them
+void ExternalForcesModule::AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state)
+{
+ float time = GetTimeManager ().GetTimeSinceLevelLoad ();
+ WindManager::WindZoneList& windZones = WindManager::GetInstance().GetList();
+
+ state.numCachedForces = 0;
+ for (WindManager::WindZoneList::iterator it = windZones.begin (); it != windZones.end (); ++it)
+ state.numCachedForces++;
+
+ // Allocate
+ state.cachedForces = ALLOC_TEMP_MANUAL(ParticleSystemExternalCachedForce, state.numCachedForces);
+
+ // Cache
+ int i = 0;
+ for (WindManager::WindZoneList::iterator it = windZones.begin (); it != windZones.end (); ++it)
+ {
+ const WindZone& zone = **it;
+ ParticleSystemExternalCachedForce& cachedForce = state.cachedForces[i++];
+ cachedForce.position = zone.GetComponent (Transform).GetPosition();
+ cachedForce.direction = zone.GetComponent (Transform).GetLocalToWorldMatrix().GetAxisZ();
+ cachedForce.forceType = zone.GetMode();
+ cachedForce.radius = zone.GetRadius();
+
+ float phase = time * kPI * zone.GetWindPulseFrequency();
+ float pulse = (cos (phase) + cos (phase * 0.375f) + cos (phase * 0.05f)) * 0.333f;
+ pulse = 1.0f + (pulse * zone.GetWindPulseMagnitude());
+ cachedForce.forceMain = zone.GetWindMain() * pulse;
+ // Maybe implement using turbulence and time based phasing?
+ // ForceTurbulenceMultiply (maybe) // @TODO: Figure out what to do about turbulence. Do perlin field? Expensive but maybe cool! If use it: only do it when turbulence force is set to something
+
+ //cachedForce.force = 1.0f;
+ }
+}
+
+void ExternalForcesModule::FreeCache(ParticleSystemState& state)
+{
+ if(state.cachedForces)
+ FREE_TEMP_MANUAL(state.cachedForces);
+ state.cachedForces = NULL;
+ state.numCachedForces = 0;
+}
+
+template<class TransferFunction>
+void ExternalForcesModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Multiplier, "multiplier");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ExternalForcesModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h
new file mode 100644
index 0000000..2abc949
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h
@@ -0,0 +1,25 @@
+#ifndef SHURIKENMODULEEXTERNALFORCES_H
+#define SHURIKENMODULEEXTERNALFORCES_H
+
+#include "ParticleSystemModule.h"
+
+class ExternalForcesModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ExternalForcesModule)
+ ExternalForcesModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt);
+ void CheckConsistency() {};
+
+ static void AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state);
+ static void FreeCache(ParticleSystemState& state);
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ float m_Multiplier;
+};
+
+#endif // SHURIKENMODULEEXTERNALFORCES_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp
new file mode 100644
index 0000000..d72da93
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp
@@ -0,0 +1,164 @@
+#include "UnityPrefix.h"
+#include "ForceModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& x, const MinMaxCurve& y, const MinMaxCurve& z, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, bool transform, const Matrix4x4f& matrix, float dt)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemForceCurveId);
+ const float time = NormalizedTime(ps, q);
+ Vector3f f = Vector3f (Evaluate<mode> (x, time, random.x), Evaluate<mode> (y, time, random.y), Evaluate<mode> (z, time, random.z));
+ if(transform)
+ f = matrix.MultiplyVector3 (f);
+ ps.velocity[q] += f * dt;
+ }
+}
+
+template<bool isOptimized>
+void UpdateProceduralTpl(const DualMinMax3DPolyCurves& pos, const DualMinMax3DPolyCurves& vel, ParticleSystemParticles& ps, const Matrix4x4f& matrix, bool transform)
+{
+ const size_t count = ps.array_size();
+ for (int q=0; q<count; q++)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemForceCurveId);
+ float time = NormalizedTime(ps, q);
+ float range = ps.startLifetime[q];
+
+ Vector3f delta;
+ Vector3f velocity;
+ if(isOptimized)
+ {
+ delta = Vector3f (EvaluateDoubleIntegrated(pos.optX, time, random.x), EvaluateDoubleIntegrated(pos.optY, time, random.y), EvaluateDoubleIntegrated(pos.optZ, time, random.z));
+ velocity = Vector3f (EvaluateIntegrated(vel.optX, time, random.x), EvaluateIntegrated(vel.optY, time, random.y), EvaluateIntegrated(vel.optZ, time, random.z));
+ }
+ else
+ {
+ delta = Vector3f (EvaluateDoubleIntegrated(pos.x, time, random.x), EvaluateDoubleIntegrated(pos.y, time, random.y), EvaluateDoubleIntegrated(pos.z, time, random.z));
+ velocity = Vector3f (EvaluateIntegrated(vel.x, time, random.x), EvaluateIntegrated(vel.y, time, random.y), EvaluateIntegrated(vel.z, time, random.z));
+ }
+
+ // Sqr range
+ delta *= range * range;
+ velocity *= range;
+
+ if(transform)
+ {
+ delta = matrix.MultiplyVector3 (delta);
+ velocity = matrix.MultiplyVector3 (velocity);
+ }
+
+ ps.position[q] += delta;
+ ps.velocity[q] += velocity;
+ }
+}
+
+ForceModule::ForceModule () : ParticleSystemModule(false)
+, m_RandomizePerFrame (false)
+, m_InWorldSpace(false)
+{}
+
+void ForceModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt)
+{
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ if (m_RandomizePerFrame)
+ {
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = NormalizedTime (ps, q);
+ const float randomX = Random01 (m_Random);
+ const float randomY = Random01 (m_Random);
+ const float randomZ = Random01 (m_Random);
+ Vector3f f (Evaluate (m_X, t, randomX), Evaluate (m_Y, t, randomY), Evaluate (m_Z, t, randomZ));
+ if(transform)
+ f = matrix.MultiplyVector3 (f);
+ ps.velocity[q] += f * dt;
+ }
+ }
+ else
+ {
+ bool usesScalar = (m_X.minMaxState == kMMCScalar) && (m_Y.minMaxState == kMMCScalar) && (m_Z.minMaxState == kMMCScalar);
+ bool isOptimized = m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized();
+ bool usesMinMax = m_X.UsesMinMax() && m_Y.UsesMinMax() && m_Z.UsesMinMax();
+ if(usesScalar)
+ UpdateTpl<kEMScalar>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ else if(isOptimized && usesMinMax)
+ UpdateTpl<kEMOptimizedMinMax>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ else if(isOptimized)
+ UpdateTpl<kEMOptimized>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ else
+ UpdateTpl<kEMSlow>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ }
+}
+
+void ForceModule::UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ Assert(!m_RandomizePerFrame);
+
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ DualMinMax3DPolyCurves posCurves;
+ DualMinMax3DPolyCurves velCurves;
+ if(m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized())
+ {
+ posCurves.optX = m_X.polyCurves; posCurves.optX.DoubleIntegrate();
+ posCurves.optY = m_Y.polyCurves; posCurves.optY.DoubleIntegrate();
+ posCurves.optZ = m_Z.polyCurves; posCurves.optZ.DoubleIntegrate();
+ velCurves.optX = m_X.polyCurves; velCurves.optX.Integrate();
+ velCurves.optY = m_Y.polyCurves; velCurves.optY.Integrate();
+ velCurves.optZ = m_Z.polyCurves; velCurves.optZ.Integrate();
+ UpdateProceduralTpl<true>(posCurves, velCurves, ps, matrix, transform);
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (m_X.editorCurves, m_X.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Y.editorCurves, m_Y.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Z.editorCurves, m_Z.minMaxState));
+ BuildCurves(posCurves.x, m_X.editorCurves, m_X.GetScalar(), m_X.minMaxState); posCurves.x.DoubleIntegrate();
+ BuildCurves(posCurves.y, m_Y.editorCurves, m_Y.GetScalar(), m_Y.minMaxState); posCurves.y.DoubleIntegrate();
+ BuildCurves(posCurves.z, m_Z.editorCurves, m_Z.GetScalar(), m_Z.minMaxState); posCurves.z.DoubleIntegrate();
+ BuildCurves(velCurves.x, m_X.editorCurves, m_X.GetScalar(), m_X.minMaxState); velCurves.x.Integrate();
+ BuildCurves(velCurves.y, m_Y.editorCurves, m_Y.GetScalar(), m_Y.minMaxState); velCurves.y.Integrate();
+ BuildCurves(velCurves.z, m_Z.editorCurves, m_Z.GetScalar(), m_Z.minMaxState); velCurves.z.Integrate();
+ UpdateProceduralTpl<false>(posCurves, velCurves, ps, matrix, transform);
+ }
+}
+void ForceModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime)
+{
+ Vector2f xRange = m_X.FindMinMaxDoubleIntegrated();
+ Vector2f yRange = m_Y.FindMinMaxDoubleIntegrated();
+ Vector2f zRange = m_Z.FindMinMaxDoubleIntegrated();
+ bounds.m_Min = Vector3f(xRange.x, yRange.x, zRange.x) * maxLifeTime * maxLifeTime;
+ bounds.m_Max = Vector3f(xRange.y, yRange.y, zRange.y) * maxLifeTime * maxLifeTime;
+
+ if(m_InWorldSpace)
+ {
+ Matrix4x4f matrix;
+ Matrix4x4f::Invert_General3D(localToWorld, matrix);
+ matrix.SetPosition(Vector3f::zero);
+ AABB aabb = bounds;
+ TransformAABBSlow(aabb, matrix, aabb);
+ bounds = aabb;
+ }
+}
+
+template<class TransferFunction>
+void ForceModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_X, "x");
+ transfer.Transfer (m_Y, "y");
+ transfer.Transfer (m_Z, "z");
+ transfer.Transfer (m_InWorldSpace, "inWorldSpace");
+ transfer.Transfer (m_RandomizePerFrame, "randomizePerFrame"); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ForceModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ForceModule.h b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.h
new file mode 100644
index 0000000..9ad54ec
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.h
@@ -0,0 +1,36 @@
+#ifndef SHURIKENMODULEFORCE_H
+#define SHURIKENMODULEFORCE_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Random/rand.h"
+
+class ForceModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ForceModule)
+ ForceModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt);
+ void UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps);
+ void CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime);
+ void CheckConsistency() {};
+
+ inline MinMaxCurve& GetXCurve() { return m_X; }
+ inline MinMaxCurve& GetYCurve() { return m_Y; }
+ inline MinMaxCurve& GetZCurve() { return m_Z; }
+ inline bool GetRandomizePerFrame() { return m_RandomizePerFrame; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_X;
+ MinMaxCurve m_Y;
+ MinMaxCurve m_Z;
+ bool m_InWorldSpace;
+ bool m_RandomizePerFrame;
+ Rand m_Random;
+};
+
+#endif // SHURIKENMODULEFORCE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp
new file mode 100644
index 0000000..680d175
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp
@@ -0,0 +1,193 @@
+#include "UnityPrefix.h"
+#include "InitialModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Interfaces/IPhysics.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+
+InitialModule::InitialModule () : ParticleSystemModule(true)
+, m_GravityModifier(0.0f)
+, m_InheritVelocity(0.0f)
+, m_MaxNumParticles(1000)
+{
+}
+
+Vector3f InitialModule::GetGravity (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state) const
+{
+#if ENABLE_PHYSICS
+ IPhysics* physicsModule = GetIPhysics();
+ if (!physicsModule)
+ return Vector3f::zero;
+
+ Vector3f gravity = m_GravityModifier * physicsModule->GetGravity ();
+ if(roState.useLocalSpace)
+ {
+ Matrix4x4f worldToLocal;
+ Matrix4x4f::Invert_General3D(state.localToWorld, worldToLocal);
+ gravity = worldToLocal.MultiplyVector3(gravity);
+ }
+ return gravity;
+#else
+ return Vector3f::zero;
+#endif
+}
+
+void InitialModule::Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t)
+{
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ Rand& random = GetRandom();
+
+ Vector3f origin = matrix.GetPosition ();
+
+ const size_t count = ps.array_size ();
+ for (size_t q = fromIndex; q < count; ++q)
+ {
+ UInt32 randUInt32 = random.Get ();
+ float rand = Rand::GetFloatFromInt (randUInt32);
+ UInt32 randByte = Rand::GetByteFromInt (randUInt32);
+
+ const ColorRGBA32 col = Evaluate (m_Color, normalizedT, randByte);
+ float sz = std::max<float> (0.0f, Evaluate (m_Size, normalizedT, rand));
+ Vector3f vel = matrix.MultiplyVector3 (Vector3f::zAxis);
+ float ttl = std::max<float> (0.0f, Evaluate (m_Lifetime, normalizedT, rand));
+ float rot = Evaluate (m_Rotation, normalizedT, rand);
+
+ ps.position[q] = origin;
+ ps.velocity[q] = vel;
+ ps.animatedVelocity[q] = Vector3f::zero;
+ ps.lifetime[q] = ttl;
+ ps.startLifetime[q] = ttl;
+ ps.size[q] = sz;
+ ps.rotation[q] = rot;
+ if(ps.usesRotationalSpeed)
+ ps.rotationalSpeed[q] = 0.0f;
+ ps.color[q] = col;
+ ps.randomSeed[q] = random.Get(); // One more iteration to avoid visible patterns between random spawned parameters and those used in update
+ if(ps.usesAxisOfRotation)
+ ps.axisOfRotation[q] = Vector3f::zAxis;
+ for(int acc = 0; acc < ps.numEmitAccumulators; acc++)
+ ps.emitAccumulator[acc][q] = 0.0f;
+
+ }
+}
+
+void InitialModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt) const
+{
+ Vector3f gravityDelta = GetGravity(roState, state) * dt;
+ if(!CompareApproximately(gravityDelta, Vector3f::zero, 0.0001f))
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ ps.velocity[q] += gravityDelta;
+
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ ps.animatedVelocity[q] = Vector3f::zero;
+
+ if(ps.usesRotationalSpeed)
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ ps.rotationalSpeed[q] = 0.0f;
+}
+
+void InitialModule::GenerateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const ParticleSystemEmitReplay& emit)
+{
+ size_t count = emit.particlesToEmit;
+ float t = emit.t;
+ float alreadyPassedTime = emit.aliveTime;
+
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ Rand& random = GetRandom();
+
+ const Matrix4x4f localToWorld = !roState.useLocalSpace ? state.localToWorld : Matrix4x4f::identity;
+ Vector3f origin = localToWorld.GetPosition ();
+ for (size_t i = 0; i < count; ++i)
+ {
+ UInt32 randUInt32 = random.Get ();
+ float rand = Rand::GetFloatFromInt (randUInt32);
+ UInt32 randByte = Rand::GetByteFromInt (randUInt32);
+
+ float frameOffset = (float(i) + emit.emissionOffset) * emit.emissionGap * float(i < emit.numContinuous);
+
+ const ColorRGBA32 col = Evaluate (m_Color, normalizedT, randByte);
+ float sz = std::max<float> (0.0f, Evaluate (m_Size, normalizedT, rand));
+ Vector3f vel = localToWorld.MultiplyVector3 (Vector3f::zAxis);
+ float ttlStart = std::max<float> (0.0f, Evaluate (m_Lifetime, normalizedT, rand));
+ float ttl = ttlStart - alreadyPassedTime - frameOffset;
+ float rot = Evaluate (m_Rotation, normalizedT, rand);
+
+ if (ttl < 0.0F)
+ continue;
+
+ size_t q = ps.array_size();
+ ps.array_resize(ps.array_size() + 1);
+
+ ps.position[q] = origin;
+ ps.velocity[q] = vel;
+ ps.animatedVelocity[q] = Vector3f::zero;
+ ps.lifetime[q] = ttl;
+ ps.startLifetime[q] = ttlStart;
+ ps.size[q] = sz;
+ ps.rotation[q] = rot;
+ if(ps.usesRotationalSpeed)
+ ps.rotationalSpeed[q] = 0.0f;
+ ps.color[q] = col;
+ ps.randomSeed[q] = random.Get(); // One more iteration to avoid visible patterns between random spawned parameters and those used in update
+ if(ps.usesAxisOfRotation)
+ ps.axisOfRotation[q] = Vector3f::zAxis;
+ for(int acc = 0; acc < ps.numEmitAccumulators; acc++)
+ ps.emitAccumulator[acc][q] = 0.0f;
+ }
+}
+
+void InitialModule::CheckConsistency ()
+{
+ m_Lifetime.SetScalar(clamp<float> (m_Lifetime.GetScalar(), 0.05f, 100000.0f));
+ m_Size.SetScalar(std::max<float> (0.0f, m_Size.GetScalar()));
+ m_MaxNumParticles = std::max<int> (0, m_MaxNumParticles);
+}
+
+void InitialModule::AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState)
+{
+ ResetSeed(roState);
+}
+
+void InitialModule::ResetSeed(const ParticleSystemReadOnlyState& roState)
+{
+ if(roState.randomSeed == 0)
+ m_Random.SetSeed(GetGlobalRandomSeed ());
+ else
+ m_Random.SetSeed(roState.randomSeed);
+}
+
+Rand& InitialModule::GetRandom()
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ return m_EditorRandom;
+ else
+#endif
+ return m_Random;
+}
+
+template<class TransferFunction>
+void InitialModule::Transfer (TransferFunction& transfer)
+{
+ SetEnabled(true); // always enabled
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Lifetime, "startLifetime");
+ transfer.Transfer (m_Speed, "startSpeed");
+ transfer.Transfer (m_Color, "startColor");
+ transfer.Transfer (m_Size, "startSize");
+ transfer.Transfer (m_Rotation, "startRotation");
+ transfer.Transfer (m_GravityModifier, "gravityModifier");
+ transfer.Transfer (m_InheritVelocity, "inheritVelocity");
+ transfer.Transfer (m_MaxNumParticles, "maxNumParticles");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(InitialModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/InitialModule.h b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.h
new file mode 100644
index 0000000..cd602a9
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.h
@@ -0,0 +1,66 @@
+#ifndef SHURIKENMODULEINITIAL_H
+#define SHURIKENMODULEINITIAL_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h"
+#include "Runtime/Math/Random/rand.h"
+
+struct ParticleSystemState;
+
+class InitialModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (InitialModule)
+ InitialModule ();
+
+ void Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t);
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt) const;
+ void GenerateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const ParticleSystemEmitReplay& emit);
+ void CheckConsistency ();
+ void AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState);
+ void ResetSeed(const ParticleSystemReadOnlyState& roState);
+
+ inline MinMaxCurve& GetLifeTimeCurve() { return m_Lifetime; }
+ inline const MinMaxCurve& GetLifeTimeCurve() const { return m_Lifetime; }
+ inline MinMaxCurve& GetSpeedCurve() { return m_Speed; }
+ inline const MinMaxCurve& GetSpeedCurve() const { return m_Speed; }
+ inline MinMaxCurve& GetSizeCurve() { return m_Size; }
+ inline const MinMaxCurve& GetSizeCurve() const { return m_Size; }
+ inline MinMaxCurve& GetRotationCurve() { return m_Rotation; }
+ inline const MinMaxCurve& GetRotationCurve() const { return m_Rotation; }
+ inline MinMaxGradient& GetColor() { return m_Color; }
+ inline const MinMaxGradient& GetColor() const { return m_Color; }
+ inline void SetGravityModifier(float value) { m_GravityModifier = value; }
+ inline float GetGravityModifier() const { return m_GravityModifier; }
+
+ inline void SetMaxNumParticles(int value) { m_MaxNumParticles = value; }
+ inline int GetMaxNumParticles() const { return m_MaxNumParticles; }
+ inline float GetInheritVelocity() const { return m_InheritVelocity; }
+ Vector3f GetGravity (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state) const;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ Rand& GetRandom();
+
+ MinMaxCurve m_Lifetime;
+ MinMaxCurve m_Speed;
+ MinMaxGradient m_Color;
+ MinMaxCurve m_Size;
+ MinMaxCurve m_Rotation;
+ float m_GravityModifier;
+ float m_InheritVelocity;
+ int m_MaxNumParticles;
+
+ Rand m_Random;
+
+#if UNITY_EDITOR
+public:
+ Rand m_EditorRandom;
+#endif
+};
+
+
+#endif // SHURIKENMODULEINITIAL_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp
new file mode 100644
index 0000000..dee4872
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp
@@ -0,0 +1,141 @@
+#include "UnityPrefix.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "ParticleSystemModule.h"
+
+ParticleSystemReadOnlyState::ParticleSystemReadOnlyState()
+: lengthInSec (5.0f)
+, startDelay (0.0f)
+, speed (1.0f)
+, randomSeed (0)
+, looping (true)
+, prewarm (false)
+, playOnAwake (true)
+, useLocalSpace (true)
+{
+}
+
+void ParticleSystemReadOnlyState::CheckConsistency()
+{
+ lengthInSec = std::max(lengthInSec, 0.1f);
+ lengthInSec = std::min(lengthInSec, 100000.0f); // Very large values can lead to editor locking up due to numerical instability.
+ startDelay = std::max(startDelay, 0.0f);
+ speed = std::max(speed, 0.0f);
+}
+
+template<class TransferFunction>
+void ParticleSystemReadOnlyState::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (lengthInSec);
+ TRANSFER (startDelay);
+ TRANSFER (speed);
+ TRANSFER (randomSeed);
+ TRANSFER (looping);
+ TRANSFER (prewarm);
+ TRANSFER (playOnAwake);
+ transfer.Transfer (useLocalSpace, "moveWithTransform");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ParticleSystemReadOnlyState)
+
+ParticleSystemState::ParticleSystemState ()
+: playing (false)
+, needRestart (true)
+, stopEmitting (false)
+, accumulatedDt (0.0f)
+, delayT (0.0f)
+, t (0.0f)
+, maxSize (0.0f)
+, isSubEmitter (false)
+, recordSubEmits(false)
+, cullTime(0.0)
+, culled(false)
+, numLoops(0)
+, invalidateProcedural(false)
+, supportsProcedural(true)
+, cachedForces(0)
+, numCachedForces(0)
+, cachedSubDataBirth(0)
+, numCachedSubDataBirth(0)
+, cachedSubDataCollision(0)
+, numCachedSubDataCollision(0)
+, cachedSubDataDeath(0)
+, numCachedSubDataDeath(0)
+, cachedCollisionPlanes(0)
+, numCachedCollisionPlanes(0)
+, rayBudget(0)
+, nextParticleToTrace(0)
+{
+ ClearSubEmitterCommandBuffer();
+
+ localToWorld.SetIdentity ();
+ emitterVelocity = Vector3f::zero;
+ emitterScale = Vector3f::one;
+ minMaxAABB = MinMaxAABB (Vector3f::zero, Vector3f::zero);
+}
+
+void ParticleSystemState::Tick (const ParticleSystemReadOnlyState& constState, float dt)
+{
+ t += dt;
+
+ for(int i = 0; i < subEmitterCommandBuffer.commandCount; i++)
+ subEmitterCommandBuffer.commands[i].timeAlive += dt;
+
+ if (!constState.looping)
+ t = std::min<float> (t, constState.lengthInSec);
+ else
+ if(t > constState.lengthInSec)
+ {
+ t -= constState.lengthInSec;
+ numLoops++;
+ }
+}
+
+void ParticleSystemState::ClearSubEmitterCommandBuffer()
+{
+ if(cachedSubDataBirth)
+ {
+ for (int i = 0; i < numCachedSubDataBirth; ++i)
+ {
+ (cachedSubDataBirth+i)->~ParticleSystemSubEmitterData();
+ }
+ FREE_TEMP_MANUAL(cachedSubDataBirth);
+ }
+ if(cachedSubDataCollision)
+ {
+ for (int i = 0; i < numCachedSubDataCollision; ++i)
+ {
+ (cachedSubDataCollision+i)->~ParticleSystemSubEmitterData();
+ }
+ FREE_TEMP_MANUAL(cachedSubDataCollision);
+ }
+ if(cachedSubDataDeath)
+ {
+ for (int i = 0; i < numCachedSubDataDeath; ++i)
+ {
+ (cachedSubDataDeath+i)->~ParticleSystemSubEmitterData();
+ }
+ FREE_TEMP_MANUAL(cachedSubDataDeath);
+ }
+ if(subEmitterCommandBuffer.commands)
+ FREE_TEMP_MANUAL(subEmitterCommandBuffer.commands);
+
+ cachedSubDataBirth = cachedSubDataCollision = cachedSubDataDeath = 0;
+ numCachedSubDataBirth = numCachedSubDataCollision = numCachedSubDataDeath = 0;
+ subEmitterCommandBuffer.commands = 0;
+ subEmitterCommandBuffer.commandCount = subEmitterCommandBuffer.maxCommandCount = 0;
+}
+
+template<class TransferFunction>
+void ParticleSystemState::Transfer (TransferFunction& transfer)
+{
+ TRANSFER_DEBUG (t);
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ParticleSystemState)
+
+
+template<class TransferFunction>
+void ParticleSystemModule::Transfer (TransferFunction& transfer)
+{
+ transfer.Transfer (m_Enabled, "enabled"); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ParticleSystemModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h
new file mode 100644
index 0000000..b4746de
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h
@@ -0,0 +1,238 @@
+#ifndef SHURIKENMODULE_H
+#define SHURIKENMODULE_H
+
+#include "../ParticleSystemCommon.h"
+#include "../ParticleSystemCurves.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+#define ENABLE_MULTITHREADED_PARTICLES ENABLE_MULTITHREADED_CODE
+
+#define DECLARE_MODULE(name) const char* GetName () { return #name; } DEFINE_GET_TYPESTRING(name)
+
+class ParticleSystem;
+class Plane;
+
+struct ParticleSystemEmissionState
+{
+ ParticleSystemEmissionState() { Clear(); }
+ inline void Clear()
+ {
+ m_ToEmitAccumulator = 0.0f;
+ m_ParticleSpacing = 0.0f;
+ }
+ float m_ParticleSpacing;
+ float m_ToEmitAccumulator;
+};
+
+struct ParticleSystemEmissionData
+{
+ enum { kMaxNumBursts = 4 };
+
+ int type;
+ MinMaxCurve rate;
+ float burstTime[kMaxNumBursts];
+ UInt16 burstParticleCount[kMaxNumBursts];
+ UInt8 burstCount;
+};
+
+
+struct ParticleSystemSubEmitterData
+{
+ ParticleSystemSubEmitterData()
+ :maxLifetime(0.0f)
+ ,startDelayInSec(0.0f)
+ ,lengthInSec(0.0f)
+ {}
+
+ ParticleSystemEmissionData emissionData;
+ float maxLifetime;
+ float startDelayInSec;
+ float lengthInSec;
+ ParticleSystem* emitter;
+};
+
+// @TODO: Find "pretty" place for shared structs and enums?
+struct ParticleSystemEmitReplay
+{
+ float t;
+ float aliveTime;
+ float emissionOffset;
+ float emissionGap;
+ int particlesToEmit;
+ size_t numContinuous;
+ UInt32 randomSeed;
+
+ ParticleSystemEmitReplay (float inT, int inParticlesToEmit, float inEmissionOffset, float inEmissionGap, size_t inNumContinuous, UInt32 inRandomSeed)
+ : t (inT), particlesToEmit (inParticlesToEmit), aliveTime(0.0F), emissionOffset(inEmissionOffset), emissionGap(inEmissionGap), numContinuous(inNumContinuous), randomSeed(inRandomSeed)
+ {}
+};
+
+struct SubEmitterEmitCommand
+{
+ SubEmitterEmitCommand(ParticleSystemEmissionState inEmissionState, Vector3f inPosition, Vector3f inVelocity, ParticleSystemSubType inSubEmitterType, int inSubEmitterIndex, int inParticlesToEmit, int inParticlesToEmitContinuous, float inParentT, float inDeltaTime)
+ :emissionState(inEmissionState)
+ ,position(inPosition)
+ ,velocity(inVelocity)
+ ,subEmitterType(inSubEmitterType)
+ ,subEmitterIndex(inSubEmitterIndex)
+ ,particlesToEmit(inParticlesToEmit)
+ ,particlesToEmitContinuous(inParticlesToEmitContinuous)
+ ,deltaTime(inDeltaTime)
+ ,parentT(inParentT)
+ ,timeAlive(0.0f)
+ {
+ }
+
+ ParticleSystemEmissionState emissionState;
+ Vector3f position;
+ Vector3f velocity;
+ ParticleSystemSubType subEmitterType;
+ int subEmitterIndex;
+ int particlesToEmit;
+ int particlesToEmitContinuous;
+ float deltaTime;
+ float parentT; // Used for StartModules
+ float timeAlive;
+};
+
+struct ParticleSystemSubEmitCmdBuffer
+{
+ ParticleSystemSubEmitCmdBuffer()
+ :commands(0)
+ ,commandCount(0)
+ ,maxCommandCount(0)
+ {}
+
+ inline void AddCommand(const ParticleSystemEmissionState& emissionState, const Vector3f& initialPosition, const Vector3f& initialVelocity, const ParticleSystemSubType type, const int index, const int particlesToEmit, const int particlesToEmitContinuous, const float parentT, const float dt)
+ {
+ commands[commandCount++] = SubEmitterEmitCommand(emissionState, initialPosition, initialVelocity, type, index, particlesToEmit, particlesToEmitContinuous, parentT, dt);
+ }
+ inline bool IsFull() { return commandCount >= maxCommandCount; }
+
+ SubEmitterEmitCommand* commands;
+ int commandCount;
+ int maxCommandCount; // Mostly to assert/test against trashing memory
+};
+
+struct ParticleSystemExternalCachedForce
+{
+ Vector3f position;
+ Vector3f direction;
+ int forceType;
+ float radius;
+ float forceMain;
+ float forceTurbulence; // not yet implemented
+};
+
+// @TODO: Maybe there's a better name for this? ParticleSystemSerializedState? Some shit like that :)
+struct ParticleSystemReadOnlyState
+{
+ ParticleSystemReadOnlyState();
+
+ void CheckConsistency();
+
+ float lengthInSec;
+ float startDelay;
+ float speed;
+ UInt32 randomSeed;
+ bool looping;
+ bool prewarm;
+ bool playOnAwake;
+ bool useLocalSpace;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+};
+
+// Some of these aren't actually state, but more like context. Separate it?
+struct ParticleSystemState
+{
+ // state
+ float accumulatedDt;
+ float delayT;
+ bool playing;
+ bool needRestart;
+ bool stopEmitting;
+ size_t rayBudget;
+ size_t nextParticleToTrace;
+
+ bool GetIsSubEmitter() const { return isSubEmitter; }
+private:
+ // When setting this we need to ensure some other things happen as well
+ bool isSubEmitter;
+
+public:
+
+ bool recordSubEmits;
+
+ // Procedural mode / culling
+ bool supportsProcedural; // With the current parameter set, does the emitter support procedural mode?
+ bool invalidateProcedural; // This is set if anything changes from script at some point when running a system
+ bool culled; // Is particle system currently culled?
+ double cullTime; // Absolute time, so we need as double in case it runs for ages
+ int numLoops; // Number of loops executed
+
+ // per-frame
+ Matrix4x4f localToWorld;
+ Matrix4x4f worldToLocal;
+ Vector3f emitterVelocity;
+ Vector3f emitterScale;
+
+ MinMaxAABB minMaxAABB;
+ float maxSize; // Maximum size of particles due to setting from script
+ float t;
+
+ // Temp alloc stuff
+ ParticleSystemSubEmitterData* cachedSubDataBirth;
+ size_t numCachedSubDataBirth;
+ ParticleSystemSubEmitterData* cachedSubDataCollision;
+ size_t numCachedSubDataCollision;
+ ParticleSystemSubEmitterData* cachedSubDataDeath;
+ size_t numCachedSubDataDeath;
+ ParticleSystemExternalCachedForce* cachedForces;
+ size_t numCachedForces;
+ Plane* cachedCollisionPlanes;
+ size_t numCachedCollisionPlanes;
+ ParticleSystemSubEmitCmdBuffer subEmitterCommandBuffer;
+
+ dynamic_array<ParticleSystemEmitReplay> emitReplay;
+ ParticleSystemEmissionState emissionState;
+
+ ParticleSystemState ();
+
+ void Tick (const ParticleSystemReadOnlyState& constState, float dt);
+ void ClearSubEmitterCommandBuffer();
+
+ void SetIsSubEmitter(bool inIsSubEmitter)
+ {
+ if(inIsSubEmitter)
+ {
+ stopEmitting = true;
+ invalidateProcedural = true;
+ }
+ isSubEmitter = inIsSubEmitter;
+ }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+};
+
+
+class ParticleSystemModule
+{
+public:
+ DECLARE_SERIALIZE (ParticleSystemModule)
+ ParticleSystemModule (bool enabled) : m_Enabled (enabled) {}
+ virtual ~ParticleSystemModule () {}
+
+ inline bool GetEnabled() const { return m_Enabled; }
+ inline void SetEnabled(bool enabled) { m_Enabled = enabled; }
+
+private:
+ // shared data
+ bool m_Enabled;
+};
+
+#endif // SHURIKENMODULE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp
new file mode 100644
index 0000000..c194808
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp
@@ -0,0 +1,51 @@
+#include "UnityPrefix.h"
+#include "RotationByVelocityModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+
+RotationBySpeedModule::RotationBySpeedModule () : ParticleSystemModule(false)
+, m_Range (0.0f, 1.0f)
+{}
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, const Vector2f offsetScale)
+{
+ if (!ps.usesRotationalSpeed) return;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ const float t = InverseLerpFast01 (offsetScale, Magnitude(vel));
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemRotationBySpeedCurveId);
+ ps.rotationalSpeed[q] += Evaluate<mode> (curve, t, random);
+ }
+}
+
+void RotationBySpeedModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ Vector2f offsetScale = CalculateInverseLerpOffsetScale (m_Range);
+ if (m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+ else if(m_Curve.IsOptimized() && m_Curve.UsesMinMax())
+ UpdateTpl<kEMOptimizedMinMax>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+ else
+ UpdateTpl<kEMSlow>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+}
+
+void RotationBySpeedModule::CheckConsistency ()
+{
+ const float MyEpsilon = 0.001f;
+ m_Range.y = std::max (m_Range.x + MyEpsilon, m_Range.y);
+}
+
+template<class TransferFunction>
+void RotationBySpeedModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+ transfer.Transfer (m_Range, "range");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(RotationBySpeedModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h
new file mode 100644
index 0000000..548f9c3
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULEROTATIONBYVELOCITY_H
+#define SHURIKENMODULEROTATIONBYVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Vector2.h"
+
+class RotationBySpeedModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (RotationBySpeedModule)
+ RotationBySpeedModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+ Vector2f m_Range;
+};
+
+#endif // SHURIKENMODULEROTATIONBYVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp
new file mode 100644
index 0000000..7f331d6
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp
@@ -0,0 +1,83 @@
+#include "UnityPrefix.h"
+#include "RotationModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Math/Vector2.h"
+
+struct DualMinMaxPolyCurves
+{
+ MinMaxOptimizedPolyCurves optRot;
+ MinMaxPolyCurves rot;
+};
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ if ( !ps.usesRotationalSpeed ) return;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float time = NormalizedTime(ps, q);
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemRotationCurveId);
+ ps.rotationalSpeed[q] += Evaluate<mode> (curve, time, random);
+ }
+}
+
+template<bool isOptimized>
+void UpdateProceduralTpl(const DualMinMaxPolyCurves& curves, ParticleSystemParticles& ps)
+{
+ const size_t count = ps.array_size ();
+ for (int q=0; q<count; q++)
+ {
+ float time = NormalizedTime(ps, q);
+ float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemRotationCurveId);
+ float range = ps.startLifetime[q];
+ float value;
+ if(isOptimized)
+ value = EvaluateIntegrated (curves.optRot, time, random);
+ else
+ value = EvaluateIntegrated (curves.rot, time, random);
+ ps.rotation[q] += value * range;
+ }
+}
+
+RotationModule::RotationModule() : ParticleSystemModule(false)
+{}
+
+void RotationModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ if (m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar>(m_Curve, ps, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized() && m_Curve.UsesMinMax())
+ UpdateTpl<kEMOptimizedMinMax>(m_Curve, ps, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized>(m_Curve, ps, fromIndex, toIndex);
+ else
+ UpdateTpl<kEMSlow>(m_Curve, ps, fromIndex, toIndex);
+
+}
+
+void RotationModule::UpdateProcedural (const ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ DualMinMaxPolyCurves curves;
+ if(m_Curve.IsOptimized())
+ {
+ curves.optRot = m_Curve.polyCurves; curves.optRot.Integrate();
+ UpdateProceduralTpl<true>(curves, ps);
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (m_Curve.editorCurves, m_Curve.minMaxState));
+ BuildCurves(curves.rot, m_Curve.editorCurves, m_Curve.GetScalar(), m_Curve.minMaxState); curves.rot.Integrate();
+ UpdateProceduralTpl<false>(curves, ps);
+ }
+}
+
+template<class TransferFunction>
+void RotationModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(RotationModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationModule.h b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.h
new file mode 100644
index 0000000..494414e
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULEROTATION_H
+#define SHURIKENMODULEROTATION_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class RotationModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (RotationModule)
+
+ RotationModule();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void UpdateProcedural (const ParticleSystemState& state, ParticleSystemParticles& ps);
+ void CheckConsistency() {};
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+};
+
+#endif // SHURIKENMODULEROTATION_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp
new file mode 100644
index 0000000..3cdb007
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp
@@ -0,0 +1,650 @@
+#include "UnityPrefix.h"
+#include "ShapeModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystem.h"
+#include "Runtime/Graphics/TriStripper.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Geometry/ComputionalGeometry.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Utilities/StrideIterator.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+enum MeshDistributionMode
+{
+ kDistributionVertex,
+ kDistributionTriangle,
+};
+
+
+/// This gives a random barycentric coord (on the edge of triangle)
+// @TODO: Stupid: Make this in a faster way
+inline Vector3f RandomBarycentricCoordEdge (Rand& rand)
+{
+ float u = rand.GetFloat ();
+ float v = rand.GetFloat ();
+ if (u + v > 1.0F)
+ {
+ u = 1.0F - u;
+ v = 1.0F - v;
+ }
+ float w = 1.0F - u - v;
+
+ int edge = RangedRandom(rand, 0, 2);
+ if(0 == edge)
+ {
+ v += 0.5f * u;
+ w += 0.5f * u;
+ u = 0.0f;
+ }
+ else if(1 == edge)
+ {
+ u += 0.5f * v;
+ w += 0.5f * v;
+ v = 0.0f;
+ }
+ else
+ {
+ u += 0.5f * w;
+ v += 0.5f * w;
+ w = 0.0f;
+ }
+
+ return Vector3f (u, v, w);
+}
+
+
+// TODO: It could make sense to initialize in separated loops. i.e. separate position and velcoity vectors
+inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, Rand& random, bool randomDirection)
+{
+ if(randomDirection)
+ n = RandomUnitVector (random);
+
+ n = NormalizeSafe(n);
+
+ pos = Scale(pos, scale);
+
+ Vector3f vel = Magnitude (ps.velocity[q]) * n;
+ vel = localToWorld.MultiplyVector3 (vel);
+
+ // @TODO: Sooo... why multiply point and then undo the result of it? Why not just MultiplyVector?
+ pos = localToWorld.MultiplyPoint3 (pos) - localToWorld.GetPosition();
+ ps.position[q] += pos;
+ ps.velocity[q] = vel;
+
+#if 0 // WIP code for converting to spherical
+ Vector3f sp = ps.position[q];
+ ps.position[q].x = Sqrt(sp.x*sp.x + sp.y*sp.y + sp.z*sp.z);
+ ps.position[q].y = acosf(sp.z/ps.position[q].x);
+ ps.position[q].z = acosf(sp.y/ps.position[q].x);
+#endif
+
+ if(ps.usesAxisOfRotation)
+ {
+ Vector3f tan = Cross (-n, Vector3f::zAxis);
+ if (SqrMagnitude (tan) <= 0.01)
+ tan = Cross (-pos, Vector3f::zAxis);
+ if (SqrMagnitude (tan) <= 0.01)
+ tan = Vector3f::yAxis;
+ ps.axisOfRotation[q] = Normalize (tan);
+ }
+}
+
+inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, ColorRGBA32& color, Rand& random, bool randomDirection)
+{
+ EmitterStoreData(localToWorld, scale, ps, q, pos, n, random, randomDirection);
+ ps.color[q] *= color;
+}
+
+
+template<MeshDistributionMode distributionMode>
+void GetPositionMesh (Vector3f& pos,
+ Vector3f& n,
+ ColorRGBA32& color,
+ const ParticleSystemEmitterMeshVertex* vertexData,
+ const int vertexCount,
+ const MeshTriangleData* triangleData,
+ const UInt32 numPrimitives,
+ float totalTriangleArea,
+ Rand& random,
+ bool edge)
+{
+ // position/normal of particle is vertex/vertex normal from mesh
+ if(kDistributionVertex == distributionMode)
+ {
+ int vertexIndex = RangedRandom (random, 0, vertexCount);
+ pos = vertexData[vertexIndex].position;
+ n = vertexData[vertexIndex].normal;
+ color = vertexData[vertexIndex].color;
+ }
+ else if(kDistributionTriangle == distributionMode)
+ {
+ float randomArea = RangedRandom(random, 0.0f, totalTriangleArea);
+ float accArea = 0.0f;
+ UInt32 triangleIndex = 0;
+
+ for(UInt32 i = 0; i < numPrimitives; i++)
+ {
+ const MeshTriangleData& data = triangleData[i];
+ accArea += data.area;
+ if(accArea >= randomArea)
+ {
+ triangleIndex = i;
+ break;
+ }
+ }
+
+ const MeshTriangleData& data = triangleData[triangleIndex];
+ UInt16 a = data.indices[0];
+ UInt16 b = data.indices[1];
+ UInt16 c = data.indices[2];
+
+ Vector3f barycenter;
+ if(edge)
+ barycenter = RandomBarycentricCoordEdge (random);
+ else
+ barycenter = RandomBarycentricCoord (random);
+
+ // Interpolate vertex with barycentric coordinate
+ pos = barycenter.x * vertexData[a].position + barycenter.y * vertexData[b].position + barycenter.z * vertexData[c].position;
+ n = barycenter.x * vertexData[a].normal + barycenter.y * vertexData[b].normal + barycenter.z * vertexData[c].normal;
+
+ // TODO: Don't convert to floats!!!
+ ColorRGBAf color1 = vertexData[a].color;
+ ColorRGBAf color2 = vertexData[b].color;
+ ColorRGBAf color3 = vertexData[c].color;
+ color = barycenter.x * color1 + barycenter.y * color2 + barycenter.z * color3;
+ }
+}
+
+static bool CompareMeshTriangleData (const MeshTriangleData& a, const MeshTriangleData& b)
+{
+ return (a.area > b.area);
+}
+
+static float BuildMeshAreaTable(MeshTriangleData* triData, const StrideIterator<Vector3f> vertices, const UInt16* indices, int numTriangles)
+{
+ float result = 0.0f;
+ for(int i = 0; i < numTriangles; i++)
+ {
+ const UInt16 a = indices[i * 3 + 0];
+ const UInt16 b = indices[i * 3 + 1];
+ const UInt16 c = indices[i * 3 + 2];
+ float area = TriangleArea3D (vertices[a], vertices[b], vertices[c]);
+ result += area;
+
+ triData[i].indices[0] = a;
+ triData[i].indices[1] = b;
+ triData[i].indices[2] = c;
+ triData[i].area = area;
+ }
+
+ return result;
+}
+
+// ------------------------------------------------------------------------------------------
+
+ShapeModule::ShapeModule () : ParticleSystemModule(true)
+, m_Type (kCone)
+, m_RandomDirection (false)
+, m_Angle(25.0f)
+, m_Radius(1.0f)
+, m_Length(5.0f)
+, m_BoxX(1.0f)
+, m_BoxY(1.0f)
+, m_BoxZ(1.0f)
+, m_PlacementMode(kVertex)
+, m_CachedMesh(NULL)
+, m_MeshNode (NULL)
+{
+}
+
+void ShapeModule::Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t)
+{
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ Rand& random = GetRandom();
+
+ if (m_Type == kMesh)
+ {
+ if(!m_CachedMesh)
+ return;
+
+ if(!m_CachedVertexData.size())
+ return;
+
+ if(!m_CachedTriangleData.size())
+ return;
+
+ const ParticleSystemEmitterMeshVertex* vertexData = &m_CachedVertexData[0];
+ const int vertexCount = m_CachedVertexData.size();
+ size_t count = ps.array_size ();
+ switch(m_PlacementMode)
+ {
+ case kVertex:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos;
+ Vector3f n;
+ ColorRGBA32 color;
+ GetPositionMesh<kDistributionVertex>(pos, n, color, vertexData, vertexCount, NULL, 0, m_CachedTotalTriangleArea, random, false);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kEdge:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos;
+ Vector3f n;
+ ColorRGBA32 color;
+ GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, true);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kTriangle:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos;
+ Vector3f n;
+ ColorRGBA32 color;
+ GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, false);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
+ }
+ break;
+ }
+ default:
+ {
+ DebugAssert(0 && "PlacementMode Not Supported");
+ }
+ }
+ }
+ else
+ {
+ const float r = m_Radius;
+
+ float a = Deg2Rad (m_Angle);
+ float sinA = Sin (a);
+ float cosA = Cos (a);
+ float length = m_Length;
+
+ const size_t count = ps.array_size ();
+ switch(m_Type)
+ {
+ case kSphere:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomPointInsideUnitSphere (random) * r;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kSphereShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomUnitVector(random) * r;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kHemiSphere:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomPointInsideUnitSphere (random) * r;
+ if (pos.z < 0.0f)
+ pos.z *= -1.0f;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kHemiSphereShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomUnitVector (random) * r;
+ if (pos.z < 0.0f)
+ pos.z *= -1.0f;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kCone:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = RandomPointInsideUnitCircle (random);
+ Vector2f nXY;
+ if(m_RandomDirection)
+ nXY = RandomPointInsideUnitCircle (random) * sinA;
+ else
+ nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false);
+ }
+ break;
+ }
+ case kConeShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random));
+
+ Vector2f nXY;
+ if(m_RandomDirection)
+ nXY = RandomPointInsideUnitCircle (random) * sinA;
+ else
+ nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false);
+ }
+ break;
+ }
+ case kConeVolume:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = RandomPointInsideUnitCircle (random);
+ Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
+ pos += length * Random01(random) * NormalizeSafe(n);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kConeVolumeShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random));
+ Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos = Vector3f(posXY.x * r, posXY.y * r, 0.0f);
+ pos += length * Random01(random) * NormalizeSafe(n);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kBox:
+ {
+ const Vector3f extents (0.5f * m_BoxX, 0.5f * m_BoxY, 0.5f * m_BoxZ);
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomPointInsideCube (random, extents);
+ Vector3f n = Vector3f::zAxis;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ }
+ break;
+ default:
+ {
+ DebugAssert(0 && "Shape not supported");
+ }
+ }
+ }
+}
+
+void ShapeModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Vector3f& emitterScale, Vector2f minMaxBounds) const
+{
+ DebugAssert(minMaxBounds.x <= minMaxBounds.y);
+
+ switch(m_Type)
+ {
+ case kSphere:
+ case kSphereShell:
+ bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius);
+ bounds.m_Min = -bounds.m_Max;
+ break;
+ case kHemiSphere:
+ case kHemiSphereShell:
+ bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius);
+ bounds.m_Min = Vector3f(-m_Radius, -m_Radius, 0.0f);
+ break;
+ case kCone:
+ case kConeShell:
+ bounds.m_Max = Vector3f(m_Radius, m_Radius, 0.0f);
+ bounds.m_Min = -bounds.m_Max;
+ break;
+ case kConeVolume:
+ case kConeVolumeShell:
+ {
+ const float a = Deg2Rad (m_Angle);
+ const float coneRadius2 = m_Radius + m_Length * Sin (a);
+ const float coneLength = m_Length * Cos (a);
+ bounds.m_Max = Vector3f(coneRadius2, coneRadius2, coneLength);
+ bounds.m_Min = -Vector3f(coneRadius2, coneRadius2, 0.0f);
+ break;
+ }
+ case kBox:
+ bounds.m_Max = Vector3f(m_BoxX, m_BoxY, m_BoxZ) * 0.5f;
+ bounds.m_Min = -bounds.m_Max;
+ break;
+ case kMesh:
+ {
+ if(m_CachedMesh)
+ bounds = m_CachedMesh->GetBounds(0);
+ else
+ bounds = MinMaxAABB(Vector3f::zero, Vector3f::zero);
+ break;
+ }
+ default:
+ {
+ AssertBreak(!"Shape not implemented.");
+ }
+ }
+
+ bounds.m_Min = Scale(bounds.m_Min, emitterScale);
+ bounds.m_Max = Scale(bounds.m_Max, emitterScale);
+
+ MinMaxAABB speedBounds;
+
+ // Cone and cone shell random direction only deviate inside the bound
+ if(m_RandomDirection && (m_Type != kCone) && (m_Type != kConeShell))
+ {
+ speedBounds.m_Max = Vector3f::one;
+ speedBounds.m_Min = -Vector3f::one;
+ minMaxBounds = Abs(minMaxBounds);
+ }
+ else
+ {
+ switch(m_Type)
+ {
+ case kSphere:
+ case kSphereShell:
+ case kMesh:
+ speedBounds.m_Max = Vector3f::one;
+ speedBounds.m_Min = -Vector3f::one;
+ break;
+ case kHemiSphere:
+ case kHemiSphereShell:
+ speedBounds.m_Max = Vector3f::one;
+ speedBounds.m_Min = Vector3f(-1.0f, -1.0f, 0.0f);
+ break;
+ case kCone:
+ case kConeShell:
+ case kConeVolume:
+ case kConeVolumeShell:
+ {
+ const float a = Deg2Rad (m_Angle);
+ const float sinA = Sin (a);
+ speedBounds.m_Max = Vector3f(sinA, sinA, 1.0f);
+ speedBounds.m_Min = Vector3f(-sinA, -sinA, 0.0f);
+ break;
+ }
+ case kBox:
+ speedBounds.m_Max = Vector3f::zAxis;
+ speedBounds.m_Min = Vector3f::zero;
+ break;
+ default:
+ {
+ AssertBreak(!"Shape not implemented.");
+ }
+ }
+ }
+
+ MinMaxAABB speedBound;
+ speedBound.m_Min = bounds.m_Min + speedBounds.m_Min * minMaxBounds.y;
+ speedBound.m_Max = bounds.m_Max + speedBounds.m_Max * minMaxBounds.y;
+ bounds.Encapsulate(speedBound);
+
+ MinMaxAABB negSpeedBound;
+ negSpeedBound.m_Min = speedBounds.m_Min * minMaxBounds.x;
+ negSpeedBound.m_Max = speedBounds.m_Max * minMaxBounds.x;
+ speedBound.m_Min = min(negSpeedBound.m_Min, negSpeedBound.m_Max);
+ speedBound.m_Max = max(negSpeedBound.m_Min, negSpeedBound.m_Max);
+ bounds.Encapsulate(speedBound);
+}
+
+void ShapeModule::CheckConsistency ()
+{
+ m_Type = clamp<int> (m_Type, kSphere, kMax-1);
+ m_PlacementMode = clamp<int> (m_PlacementMode, kVertex, kModeMax-1);
+
+ m_Angle = clamp(m_Angle, 0.0f, 90.0f);
+ m_Radius = max(0.01f, m_Radius);
+ m_Length = max(0.0f, m_Length);
+ m_BoxX = max(0.0f, m_BoxX);
+ m_BoxY = max(0.0f, m_BoxY);
+ m_BoxZ = max(0.0f, m_BoxZ);
+}
+
+void ShapeModule::AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState)
+{
+ m_MeshNode.RemoveFromList();
+ m_MeshNode.SetData(system);
+ m_CachedMesh = m_Mesh;
+ if (m_CachedMesh != NULL)
+ m_CachedMesh->AddObjectUser( m_MeshNode );
+ DidModifyMeshData();
+
+ ResetSeed(roState);
+}
+
+void ShapeModule::ResetSeed(const ParticleSystemReadOnlyState& roState)
+{
+ if(roState.randomSeed == 0)
+ m_Random.SetSeed(GetGlobalRandomSeed ());
+ else
+ m_Random.SetSeed(roState.randomSeed);
+}
+
+void ShapeModule::DidDeleteMesh (ParticleSystem* system)
+{
+ m_CachedMesh = NULL;
+}
+
+void ShapeModule::DidModifyMeshData ()
+{
+ if (m_CachedMesh == NULL)
+ {
+ m_CachedTriangleData.resize_uninitialized(0);
+ m_CachedVertexData.resize_uninitialized(0);
+ m_CachedTotalTriangleArea = 0;
+ return;
+ }
+
+
+ const StrideIterator<Vector3f> vertexBuffer = m_CachedMesh->GetVertexBegin();
+ const UInt16* indexBuffer = m_CachedMesh->GetSubMeshBuffer16(0);
+ const SubMesh& submesh = m_CachedMesh->GetSubMeshFast (0);
+ if (submesh.topology == kPrimitiveTriangleStripDeprecated)
+ {
+ const int numTriangles = CountTrianglesInStrip(indexBuffer, submesh.indexCount);
+ const int capacity = numTriangles * 3;
+ UNITY_TEMP_VECTOR(UInt16) tempIndices(capacity);
+ Destripify(indexBuffer, submesh.indexCount, &tempIndices[0], capacity);
+ m_CachedTriangleData.resize_uninitialized(numTriangles);
+ m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, &tempIndices[0], numTriangles);
+ }
+ else if (submesh.topology == kPrimitiveTriangles)
+ {
+ const int numTriangles = submesh.indexCount/3;
+ m_CachedTriangleData.resize_uninitialized(numTriangles);
+ m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, indexBuffer, numTriangles);
+ }
+ else
+ {
+ m_CachedMesh = NULL;
+ }
+
+ // Optimization: This sorts so big triangles comes before small, which means finding the right triangle is faster
+ std::sort(m_CachedTriangleData.begin(), m_CachedTriangleData.begin() + m_CachedTriangleData.size(), CompareMeshTriangleData);
+
+ // Cache vertices
+ const int vertexCount = m_CachedMesh->GetVertexCount();
+ const StrideIterator<Vector3f> vertices = m_CachedMesh->GetVertexBegin();
+ const StrideIterator<Vector3f> normals = m_CachedMesh->GetNormalBegin();
+ const StrideIterator<ColorRGBA32> colors = m_CachedMesh->GetColorBegin();
+ m_CachedVertexData.resize_uninitialized(vertexCount);
+ for(int i = 0; i < vertexCount; i++)
+ {
+ m_CachedVertexData[i].position = vertices[i];
+
+ if(!normals.IsNull())
+ m_CachedVertexData[i].normal = normals[i];
+ else
+ m_CachedVertexData[i].normal = Vector3f::zero;
+
+ if(!colors.IsNull())
+ m_CachedVertexData[i].color = colors[i];
+ else
+ m_CachedVertexData[i].color = ColorRGBA32(0xffffffff);
+ }
+}
+
+Rand& ShapeModule::GetRandom()
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ return m_EditorRandom;
+ else
+#endif
+ return m_Random;
+}
+
+template<class TransferFunction>
+void ShapeModule::Transfer (TransferFunction& transfer)
+{
+ transfer.SetVersion(2);
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Type, "type");
+
+ // Primitive
+ transfer.Transfer(m_Radius, "radius");
+ transfer.Transfer(m_Angle, "angle");
+ transfer.Transfer(m_Length, "length");
+ transfer.Transfer(m_BoxX, "boxX");
+ transfer.Transfer(m_BoxY, "boxY");
+ transfer.Transfer(m_BoxZ, "boxZ");
+
+ // Mesh
+ transfer.Transfer (m_PlacementMode, "placementMode");
+ TRANSFER (m_Mesh);
+
+ transfer.Transfer (m_RandomDirection, "randomDirection"); transfer.Align();
+
+ // In Unity 3.5 all cone emitters had random direction set to false, but behaved as if it was true
+ if(transfer.IsOldVersion(1))
+ if(kCone == m_Type)
+ m_RandomDirection = true;
+}
+
+INSTANTIATE_TEMPLATE_TRANSFER(ShapeModule)
+
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h
new file mode 100644
index 0000000..1f95d33
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h
@@ -0,0 +1,80 @@
+#ifndef SHURIKENMODULESHAPE_H
+#define SHURIKENMODULESHAPE_H
+
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "ParticleSystemModule.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Utilities/LinkedList.h"
+
+struct MeshTriangleData
+{
+ float area;
+ UInt16 indices[3];
+};
+
+struct ParticleSystemEmitterMeshVertex
+{
+ Vector3f position;
+ Vector3f normal;
+ ColorRGBA32 color;
+};
+
+class Mesh;
+
+class ShapeModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ShapeModule)
+ ShapeModule ();
+
+ enum MeshPlacementMode { kVertex, kEdge, kTriangle, kModeMax };
+ enum { kSphere, kSphereShell, kHemiSphere, kHemiSphereShell, kCone, kBox, kMesh, kConeShell, kConeVolume, kConeVolumeShell, kMax };
+
+ void AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState);
+ void ResetSeed(const ParticleSystemReadOnlyState& roState);
+ void DidModifyMeshData ();
+ void DidDeleteMesh (ParticleSystem* system);
+
+ PPtr<Mesh> GetMeshEmitterShape () { return m_Mesh; }
+
+ void Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t);
+ void CalculateProceduralBounds(MinMaxAABB& bounds, const Vector3f& emitterScale, Vector2f minMaxBounds) const;
+ void CheckConsistency ();
+
+ inline void SetShapeType(int type) { m_Type = type; };
+ inline void SetRadius(float radius) { m_Radius = radius; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ Rand& GetRandom();
+
+ int m_Type;
+
+ // Primitive stuff
+ float m_Radius;
+ float m_Angle;
+ float m_Length;
+ float m_BoxX;
+ float m_BoxY;
+ float m_BoxZ;
+
+ // Mesh stuff
+ int m_PlacementMode;
+ PPtr<Mesh> m_Mesh;
+ Mesh* m_CachedMesh;
+ dynamic_array<ParticleSystemEmitterMeshVertex> m_CachedVertexData;
+ dynamic_array<MeshTriangleData> m_CachedTriangleData;
+ float m_CachedTotalTriangleArea;
+ ListNode<Object> m_MeshNode;
+
+ bool m_RandomDirection;
+ Rand m_Random;
+#if UNITY_EDITOR
+public:
+ Rand m_EditorRandom;
+#endif
+};
+
+#endif // SHURIKENMODULESHAPE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp
new file mode 100644
index 0000000..21cd5df
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp
@@ -0,0 +1,50 @@
+#include "UnityPrefix.h"
+#include "SizeByVelocityModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+SizeBySpeedModule::SizeBySpeedModule () : ParticleSystemModule(false)
+, m_Range (0.0f, 1.0f)
+{}
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex, const Vector2f offsetScale)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ const float t = InverseLerpFast01 (offsetScale, Magnitude (vel));
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemSizeBySpeedCurveId);
+ tempSize[q] *= max<float> (0.0f, Evaluate<mode> (curve, t, random));
+ }
+}
+
+void SizeBySpeedModule::Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size ());
+ Vector2f offsetScale = CalculateInverseLerpOffsetScale(m_Range);
+ if (m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+ else if (m_Curve.IsOptimized() && m_Curve.UsesMinMax())
+ UpdateTpl<kEMOptimizedMinMax> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+ else
+ UpdateTpl<kEMSlow> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+}
+
+void SizeBySpeedModule::CheckConsistency ()
+{
+ const float MyEpsilon = 0.001f;
+ m_Range.x = std::min (m_Range.x, m_Range.y - MyEpsilon);
+}
+
+template<class TransferFunction>
+void SizeBySpeedModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+ transfer.Transfer (m_Range, "range");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(SizeBySpeedModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h
new file mode 100644
index 0000000..c8ad729
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULESIZEBYVELOCITY_H
+#define SHURIKENMODULESIZEBYVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Vector2.h"
+
+class SizeBySpeedModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (SizeBySpeedModule)
+ SizeBySpeedModule ();
+
+ void Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+ Vector2f m_Range;
+};
+
+#endif // SHURIKENMODULESIZEBYVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp
new file mode 100644
index 0000000..ad08a4a
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp
@@ -0,0 +1,41 @@
+#include "UnityPrefix.h"
+#include "SizeModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float time = NormalizedTime(ps, q);
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemSizeCurveId);
+ tempSize[q] *= max<float>(0.0f, Evaluate<mode> (curve, time, random));
+ }
+}
+
+SizeModule::SizeModule() : ParticleSystemModule(false)
+{}
+
+void SizeModule::Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size ());
+ if(m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar>(m_Curve, ps, tempSize, fromIndex, toIndex);
+ else if (m_Curve.IsOptimized() && m_Curve.UsesMinMax ())
+ UpdateTpl<kEMOptimizedMinMax>(m_Curve, ps, tempSize, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized>(m_Curve, ps, tempSize, fromIndex, toIndex);
+ else
+ UpdateTpl<kEMSlow>(m_Curve, ps, tempSize, fromIndex, toIndex);
+}
+
+template<class TransferFunction>
+void SizeModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(SizeModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeModule.h b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.h
new file mode 100644
index 0000000..3c92634
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULESIZE_H
+#define SHURIKENMODULESIZE_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class SizeModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (SizeModule)
+
+ SizeModule();
+
+ void Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex);
+
+ void CheckConsistency () {};
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+};
+
+#endif // SHURIKENMODULESIZE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp
new file mode 100644
index 0000000..895c444
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp
@@ -0,0 +1,148 @@
+#include "UnityPrefix.h"
+#include "SubModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystem.h" // Only because of PPtr comparison
+
+bool IsUsingSubEmitter(ParticleSystem* emitter)
+{
+ const void* shurikenSelf = NULL;
+ return emitter != shurikenSelf;
+}
+
+SubModule::SubModule () : ParticleSystemModule(false)
+{
+}
+
+void SubModule::Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, float dt) const
+{
+ Assert(state.numCachedSubDataBirth <= kParticleSystemMaxNumEmitAccumulators);
+ for(int i = 0; i < state.numCachedSubDataBirth; i++)
+ {
+ const ParticleSystemSubEmitterData& data = state.cachedSubDataBirth[i];
+ //const bool culled = (data.maxLifetime < state.accumulatedDt);
+ const float startDelay = data.startDelayInSec;
+ const float length = data.lengthInSec;
+ for (int q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = std::max(0.0f, ps.startLifetime[q] - ps.lifetime[q] - dt) - startDelay;
+ const bool started = (t >= 0.0f);
+ const bool ended = (t >= length);
+ if(started && !ended)
+ {
+ ParticleSystemEmissionState emissionState;
+ emissionState.m_ToEmitAccumulator = ps.emitAccumulator[i][q];
+ RecordEmit(emissionState, data, roState, state, ps, kParticleSystemSubTypeBirth, i, q, t, dt, length);
+ ps.emitAccumulator[i][q] = emissionState.m_ToEmitAccumulator;
+ }
+ }
+ }
+}
+
+void SubModule::RemoveDuplicatePtrs (ParticleSystem** shurikens)
+{
+ for(int i = 0; i < kParticleSystemMaxSubTotal-1; i++)
+ for(int j = i+1; j < kParticleSystemMaxSubTotal; j++)
+ if(shurikens[i] && (shurikens[i] == shurikens[j]))
+ shurikens[i] = NULL;
+}
+
+// This can not be cached between frames since the referenced particle systems might be deleted at any point.
+int SubModule::GetSubEmitterPtrs (ParticleSystem** subEmitters) const
+{
+ for(int i = 0; i < kParticleSystemMaxSubTotal; i++)
+ subEmitters[i] = NULL;
+
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubBirth; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersBirth[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ for(int i = 0; i < kParticleSystemMaxSubCollision; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersCollision[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ for(int i = 0; i < kParticleSystemMaxSubDeath; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersDeath[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterPtrsBirth (ParticleSystem** subEmitters) const
+{
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubBirth; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersBirth[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterPtrsCollision (ParticleSystem** subEmitters) const
+{
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubCollision; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersCollision[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterPtrsDeath(ParticleSystem** subEmitters) const
+{
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubDeath; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersDeath[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterTypeCount(ParticleSystemSubType type) const
+{
+ int count = 0;
+ const void* shurikenSelf = NULL;
+ if(type == kParticleSystemSubTypeBirth) {
+ for(int i = 0; i < kParticleSystemMaxSubBirth; i++)
+ if(m_SubEmittersBirth[i] != shurikenSelf)
+ count++;
+ } else if(type == kParticleSystemSubTypeCollision) {
+ for(int i = 0; i < kParticleSystemMaxSubCollision; i++)
+ if(m_SubEmittersCollision[i] != shurikenSelf)
+ count++;
+ } else if(type == kParticleSystemSubTypeDeath) {
+ for(int i = 0; i < kParticleSystemMaxSubDeath; i++)
+ if(m_SubEmittersDeath[i] != shurikenSelf)
+ count++;
+ } else {
+ Assert(!"Sub emitter type not implemented.");
+ }
+ return count;
+}
+
+template<class TransferFunction>
+void SubModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_SubEmittersBirth[0], "subEmitterBirth");
+ transfer.Transfer (m_SubEmittersBirth[1], "subEmitterBirth1");
+ transfer.Transfer (m_SubEmittersCollision[0], "subEmitterCollision");
+ transfer.Transfer (m_SubEmittersCollision[1], "subEmitterCollision1");
+ transfer.Transfer (m_SubEmittersDeath[0], "subEmitterDeath");
+ transfer.Transfer (m_SubEmittersDeath[1], "subEmitterDeath1");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(SubModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SubModule.h b/Runtime/Graphics/ParticleSystem/Modules/SubModule.h
new file mode 100644
index 0000000..093c8ba
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SubModule.h
@@ -0,0 +1,38 @@
+#ifndef SHURIKENMODULESUB_H
+#define SHURIKENMODULESUB_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+
+class ParticleSystem;
+struct ParticleSystemParticles;
+
+
+class SubModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (SubModule)
+ SubModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, float dt) const;
+ void CheckConsistency() {};
+
+ int GetSubEmitterTypeCount(ParticleSystemSubType type) const;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+ static void RemoveDuplicatePtrs (ParticleSystem** shurikens);
+ int GetSubEmitterPtrs (ParticleSystem** shurikens) const;
+ int GetSubEmitterPtrsBirth (ParticleSystem** shurikens) const;
+ int GetSubEmitterPtrsCollision (ParticleSystem** shurikens) const;
+ int GetSubEmitterPtrsDeath(ParticleSystem** shurikens) const;
+
+
+private:
+ PPtr<ParticleSystem> m_SubEmittersBirth[kParticleSystemMaxSubBirth];
+ PPtr<ParticleSystem> m_SubEmittersCollision[kParticleSystemMaxSubCollision];
+ PPtr<ParticleSystem> m_SubEmittersDeath[kParticleSystemMaxSubDeath];
+};
+
+#endif // SHURIKENMODULESUB_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp
new file mode 100644
index 0000000..67d49b3
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp
@@ -0,0 +1,105 @@
+#include "UnityPrefix.h"
+#include "UVModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateWholeSheetTpl(float cycles, const MinMaxCurve& curve, const ParticleSystemParticles& ps, float* tempSheetIndex, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ tempSheetIndex[q] = Repeat (cycles * Evaluate(curve, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemUVCurveId)), 1.0f);
+}
+
+UVModule::UVModule () : ParticleSystemModule(false)
+, m_TilesX (1), m_TilesY (1)
+, m_AnimationType (kWholeSheet)
+, m_RowIndex (0)
+, m_Cycles (1.0f)
+, m_RandomRow (true)
+{}
+
+void UVModule::Update (const ParticleSystemParticles& ps, float* tempSheetIndex, size_t fromIndex, size_t toIndex)
+{
+ const float cycles = m_Cycles;
+
+ DebugAssert(toIndex <= ps.array_size ());
+ if (m_AnimationType == kSingleRow) // row
+ {
+ int rows = m_TilesY;
+ float animRange = (1.0f / (m_TilesX * rows)) * m_TilesX;
+ if(m_RandomRow)
+ {
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = cycles * Evaluate(m_Curve, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemUVCurveId));
+ const float x = Repeat (t, 1.0f);
+ const float randomValue = GenerateRandom(ps.randomSeed[q] + kParticleSystemUVRowSelectionId);
+ const float startRow = Floorf (randomValue * rows);
+ float from = startRow * animRange;
+ float to = from + animRange;
+ tempSheetIndex[q] = Lerp (from, to, x);
+ }
+ }
+ else
+ {
+ const float startRow = Floorf(m_RowIndex * animRange * rows);
+ float from = startRow * animRange;
+ float to = from + animRange;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = cycles * Evaluate(m_Curve, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemUVCurveId));
+ const float x = Repeat (t, 1.0f);
+ tempSheetIndex[q] = Lerp (from, to, x);
+ }
+ }
+ }
+ else if (m_AnimationType == kWholeSheet) // grid || row
+ {
+ if(m_Curve.minMaxState == kMMCScalar)
+ UpdateWholeSheetTpl<kEMScalar>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ else if (m_Curve.IsOptimized() && m_Curve.UsesMinMax ())
+ UpdateWholeSheetTpl<kEMOptimizedMinMax>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized())
+ UpdateWholeSheetTpl<kEMOptimized>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ else
+ UpdateWholeSheetTpl<kEMSlow>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ }
+ else
+ {
+ Assert(!"Animation mode not implemented!");
+ }
+}
+
+void UVModule::CheckConsistency ()
+{
+ m_AnimationType = clamp<int> (m_AnimationType, 0, kNumAnimationTypes-1);
+ m_TilesX = std::max<int> (1, m_TilesX);
+ m_TilesY = std::max<int> (1, m_TilesY);
+ m_Cycles = std::max<int> (1, (int)m_Cycles);
+ m_RowIndex = clamp<int> (m_RowIndex, 0, m_TilesY-1);
+ m_Curve.SetScalar(clamp<float> (m_Curve.GetScalar(), 0.0f, 1.0f));
+}
+
+void UVModule::GetNumTiles(int& uvTilesX, int& uvTilesY) const
+{
+ uvTilesX = m_TilesX;
+ uvTilesY = m_TilesY;
+}
+
+template<class TransferFunction>
+void UVModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "frameOverTime");
+ transfer.Transfer (m_TilesX, "tilesX");
+ transfer.Transfer (m_TilesY, "tilesY");
+ transfer.Transfer (m_AnimationType, "animationType");
+ transfer.Transfer (m_RowIndex, "rowIndex");
+ transfer.Transfer (m_Cycles, "cycles");
+ transfer.Transfer (m_RandomRow, "randomRow"); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(UVModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/UVModule.h b/Runtime/Graphics/ParticleSystem/Modules/UVModule.h
new file mode 100644
index 0000000..5ed9e7f
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/UVModule.h
@@ -0,0 +1,36 @@
+#ifndef SHURIKENMODULEUV_H
+#define SHURIKENMODULEUV_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/PolynomialCurve.h"
+
+struct ParticleSystemParticles;
+
+class UVModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (UVModule)
+ UVModule ();
+
+ void Update (const ParticleSystemParticles& ps, float* tempSheetIndex, size_t fromIndex, size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ void GetNumTiles(int& uvTilesX, int& uvTilesY) const;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ enum { kWholeSheet, kSingleRow, kNumAnimationTypes };
+
+ MinMaxCurve m_Curve;
+ int m_TilesX, m_TilesY;
+ int m_AnimationType;
+ int m_RowIndex;
+ float m_Cycles;
+ bool m_RandomRow;
+};
+
+#endif // SHURIKENMODULEUV_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp
new file mode 100644
index 0000000..edb7ed9
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp
@@ -0,0 +1,127 @@
+#include "UnityPrefix.h"
+#include "VelocityModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Math/Matrix4x4.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& x, const MinMaxCurve& y, const MinMaxCurve& z, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, bool transform, const Matrix4x4f& matrix)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemVelocityCurveId);
+
+ const float normalizedTime = NormalizedTime (ps, q);
+ Vector3f vel = Vector3f (Evaluate<mode> (x, normalizedTime, random.x), Evaluate<mode> (y, normalizedTime, random.y), Evaluate<mode> (z, normalizedTime, random.z));
+ if(transform)
+ vel = matrix.MultiplyVector3 (vel);
+ ps.animatedVelocity[q] += vel;
+ }
+}
+
+template<bool isOptimized>
+void UpdateProceduralTpl(const DualMinMax3DPolyCurves& curves, const MinMaxCurve& x, const MinMaxCurve& y, const MinMaxCurve& z, ParticleSystemParticles& ps, const Matrix4x4f& matrix, bool transform)
+{
+ const size_t count = ps.array_size ();
+ for (int q=0;q<count;q++)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemVelocityCurveId);
+ const float time = NormalizedTime(ps, q);
+
+ Vector3f delta;
+ if(isOptimized)
+ delta = Vector3f(EvaluateIntegrated(curves.optX, time, random.x), EvaluateIntegrated(curves.optY, time, random.y), EvaluateIntegrated(curves.optZ, time, random.z)) * ps.startLifetime[q];
+ else
+ delta = Vector3f(EvaluateIntegrated(curves.x, time, random.x), EvaluateIntegrated(curves.y, time, random.y), EvaluateIntegrated(curves.z, time, random.z)) * ps.startLifetime[q];
+
+ Vector3f velocity (Evaluate(x, time, random.x), Evaluate(y, time, random.y), Evaluate(z, time, random.z));
+ if(transform)
+ {
+ delta = matrix.MultiplyVector3 (delta);
+ velocity = matrix.MultiplyVector3 (velocity);
+ }
+ ps.position[q] += delta;
+ ps.animatedVelocity[q] += velocity;
+ }
+}
+
+VelocityModule::VelocityModule () : ParticleSystemModule(false)
+, m_InWorldSpace (false)
+{}
+
+void VelocityModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ bool usesScalar = (m_X.minMaxState == kMMCScalar) && (m_Y.minMaxState == kMMCScalar) && (m_Z.minMaxState == kMMCScalar);
+ bool isOptimized = m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized();
+ bool usesMinMax = m_X.UsesMinMax() && m_Y.UsesMinMax() && m_Z.UsesMinMax();
+ if(usesScalar)
+ UpdateTpl<kEMScalar>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+ else if(isOptimized && usesMinMax)
+ UpdateTpl<kEMOptimizedMinMax>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+ else if(isOptimized)
+ UpdateTpl<kEMOptimized>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+ else
+ UpdateTpl<kEMSlow>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+
+}
+
+void VelocityModule::UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ DualMinMax3DPolyCurves curves;
+ if(m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized())
+ {
+ curves.optX = m_X.polyCurves; curves.optX.Integrate();
+ curves.optY = m_Y.polyCurves; curves.optY.Integrate();
+ curves.optZ = m_Z.polyCurves; curves.optZ.Integrate();
+ UpdateProceduralTpl<true>(curves, m_X, m_Y, m_Z, ps, matrix, transform);
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (m_X.editorCurves, m_X.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Y.editorCurves, m_Y.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Z.editorCurves, m_Z.minMaxState));
+ BuildCurves(curves.x, m_X.editorCurves, m_X.GetScalar(), m_X.minMaxState); curves.x.Integrate();
+ BuildCurves(curves.y, m_Y.editorCurves, m_Y.GetScalar(), m_Y.minMaxState); curves.y.Integrate();
+ BuildCurves(curves.z, m_Z.editorCurves, m_Z.GetScalar(), m_Z.minMaxState); curves.z.Integrate();
+ UpdateProceduralTpl<false>(curves, m_X, m_Y, m_Z, ps, matrix, transform);
+ }
+}
+
+void VelocityModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime)
+{
+ Vector2f xRange = m_X.FindMinMaxIntegrated();
+ Vector2f yRange = m_Y.FindMinMaxIntegrated();
+ Vector2f zRange = m_Z.FindMinMaxIntegrated();
+ bounds.m_Min = Vector3f(xRange.x, yRange.x, zRange.x) * maxLifeTime;
+ bounds.m_Max = Vector3f(xRange.y, yRange.y, zRange.y) * maxLifeTime;
+ if(m_InWorldSpace)
+ {
+ Matrix4x4f matrix;
+ Matrix4x4f::Invert_General3D(localToWorld, matrix);
+ matrix.SetPosition(Vector3f::zero);
+ AABB aabb = bounds;
+ TransformAABBSlow(aabb, matrix, aabb);
+ bounds = aabb;
+ }
+}
+
+template<class TransferFunction>
+void VelocityModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_X, "x");
+ transfer.Transfer (m_Y, "y");
+ transfer.Transfer (m_Z, "z");
+ transfer.Transfer (m_InWorldSpace, "inWorldSpace"); transfer.Align();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(VelocityModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h
new file mode 100644
index 0000000..bf76f28
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h
@@ -0,0 +1,36 @@
+#ifndef SHURIKENMODULEVELOCITY_H
+#define SHURIKENMODULEVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Random/rand.h"
+
+class VelocityModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (VelocityModule)
+ VelocityModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps);
+ void CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime);
+ void CheckConsistency() {};
+
+ inline MinMaxCurve& GetXCurve() { return m_X; };
+ inline MinMaxCurve& GetYCurve() { return m_Y; };
+ inline MinMaxCurve& GetZCurve() { return m_Z; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_X;
+ MinMaxCurve m_Y;
+ MinMaxCurve m_Z;
+ bool m_InWorldSpace;
+
+ Rand m_Random;
+};
+
+
+#endif // SHURIKENMODULEVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp
new file mode 100644
index 0000000..dfefc6a
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp
@@ -0,0 +1,122 @@
+#include "UnityPrefix.h"
+#include "ParticleCollisionEvents.h"
+
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Threads/Thread.h"
+
+CollisionEvents::CollisionEvents (): currentCollisionEventThreadArray(0)
+{
+}
+
+void CollisionEvents::Clear ()
+{
+ collisionEvents[0].clear ();
+ collisionEvents[1].clear ();
+}
+
+bool CollisionEvents::AddEvent (const ParticleCollisionEvent& event)
+{
+ GetCollisionEventThreadArray ().push_back (event);
+ return true;
+}
+
+int CollisionEvents::GetCollisionEventCount () const
+{
+ return GetCollisionEventScriptArray ().size ();
+}
+
+void CollisionEvents::SwapCollisionEventArrays ()
+{
+ Assert (Thread::CurrentThreadIsMainThread ());
+ currentCollisionEventThreadArray = (currentCollisionEventThreadArray+1)%2;
+ collisionEvents[currentCollisionEventThreadArray].clear ();
+}
+
+dynamic_array<ParticleCollisionEvent>& CollisionEvents::GetCollisionEventThreadArray ()
+{
+ return collisionEvents[currentCollisionEventThreadArray];
+}
+
+const dynamic_array<ParticleCollisionEvent>& CollisionEvents::GetCollisionEventScriptArray () const
+{
+ const int currentCollisionEventScriptArray = (currentCollisionEventThreadArray+1)%2;
+ return collisionEvents[currentCollisionEventScriptArray];
+}
+
+struct SortCollisionEventsByGameObject {
+ bool operator()( const ParticleCollisionEvent& ra, const ParticleCollisionEvent& rb ) const;
+};
+
+bool SortCollisionEventsByGameObject::operator()( const ParticleCollisionEvent& ra, const ParticleCollisionEvent& rb ) const
+{
+ return ra.m_RigidBodyOrColliderInstanceID < rb.m_RigidBodyOrColliderInstanceID;
+}
+
+void CollisionEvents::SortCollisionEventThreadArray ()
+{
+ std::sort (GetCollisionEventThreadArray ().begin (), GetCollisionEventThreadArray ().end (), SortCollisionEventsByGameObject ());
+}
+
+static GameObject* GetGameObjectFromInstanceID (int instanceId)
+{
+ Object* temp = Object::IDToPointer (instanceId);
+ if ( temp )
+ {
+ return (reinterpret_cast<Unity::Component*> (temp))->GetGameObjectPtr ();
+ }
+ return NULL;
+}
+
+static int GetGameObjectIDFromInstanceID (int instanceId)
+{
+ Object* temp = Object::IDToPointer (instanceId);
+ if ( temp )
+ {
+ return (reinterpret_cast<Unity::Component*> (temp))->GetGameObjectInstanceID ();
+ }
+ return 0;
+}
+
+void CollisionEvents::SendCollisionEvents (Unity::Component& particleSystem) const
+{
+ const dynamic_array<ParticleCollisionEvent>& scriptEventArray = GetCollisionEventScriptArray ();
+ GameObject* pParticleSystem = &particleSystem.GetGameObject ();
+ GameObject* collideeGO = NULL;
+ int currentId = -1;
+ for (int e = 0; e < scriptEventArray.size (); ++e)
+ {
+ if (currentId != scriptEventArray[e].m_RigidBodyOrColliderInstanceID)
+ {
+ collideeGO = GetGameObjectFromInstanceID (scriptEventArray[e].m_RigidBodyOrColliderInstanceID);
+ if (!collideeGO)
+ continue;
+ currentId = scriptEventArray[e].m_RigidBodyOrColliderInstanceID;
+ pParticleSystem->SendMessage (kParticleCollisionEvent, collideeGO, ClassID (GameObject)); // send message to particle system
+ collideeGO->SendMessage (kParticleCollisionEvent, pParticleSystem, ClassID (GameObject)); // send message to object collided with
+ }
+ }
+}
+
+int CollisionEvents::GetCollisionEvents (int instanceId, MonoParticleCollisionEvent* collisionEvents, int size) const
+{
+ const dynamic_array<ParticleCollisionEvent>& scriptEventArray = GetCollisionEventScriptArray ();
+ dynamic_array<ParticleCollisionEvent>::const_iterator iter = scriptEventArray.begin ();
+ for (; iter != scriptEventArray.end (); ++iter)
+ {
+ if (instanceId == GetGameObjectIDFromInstanceID (iter->m_RigidBodyOrColliderInstanceID))
+ {
+ int count = 0;
+ while (iter != scriptEventArray.end () && GetGameObjectIDFromInstanceID (iter->m_RigidBodyOrColliderInstanceID) == instanceId && count < size)
+ {
+ collisionEvents[count].m_Intersection = iter->m_Intersection;
+ collisionEvents[count].m_Normal = iter->m_Normal;
+ collisionEvents[count].m_Velocity = iter->m_Velocity;
+ collisionEvents[count].m_ColliderInstanceID = iter->m_ColliderInstanceID;
+ count++;
+ iter++;
+ }
+ return count;
+ }
+ }
+ return 0;
+}
diff --git a/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h
new file mode 100644
index 0000000..98260ee
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+
+struct ParticleCollisionEvent
+{
+ ParticleCollisionEvent (const Vector3f& intersection, const Vector3f& normal, const Vector3f& velocity, int colliderInstanceID, int rigidBodyOrColliderInstanceID);
+ Vector3f m_Intersection;
+ Vector3f m_Normal;
+ Vector3f m_Velocity;
+ int m_ColliderInstanceID;
+ int m_RigidBodyOrColliderInstanceID; // This can be a Collider or a RigidBody component
+};
+
+struct MonoParticleCollisionEvent
+{
+ Vector3f m_Intersection;
+ Vector3f m_Normal;
+ Vector3f m_Velocity;
+ int m_ColliderInstanceID;
+};
+
+struct CollisionEvents
+{
+ CollisionEvents ();
+ dynamic_array<ParticleCollisionEvent> collisionEvents[2];
+ int currentCollisionEventThreadArray;
+
+ void Clear ();
+ bool AddEvent (const ParticleCollisionEvent& event);
+ int GetCollisionEventCount () const;
+ void SwapCollisionEventArrays ();
+ void SortCollisionEventThreadArray ();
+ void SendCollisionEvents (Unity::Component& particleSystem) const;
+ int GetCollisionEvents (int instanceId, MonoParticleCollisionEvent* collisionEvents, int size) const;
+ dynamic_array<ParticleCollisionEvent>& GetCollisionEventThreadArray ();
+ const dynamic_array<ParticleCollisionEvent>& GetCollisionEventScriptArray () const;
+};
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystem.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystem.cpp
new file mode 100644
index 0000000..47e952f
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystem.cpp
@@ -0,0 +1,2110 @@
+#include "UnityPrefix.h"
+#include "ParticleSystem.h"
+#include "ParticleSystemRenderer.h"
+#include "ParticleSystemCurves.h"
+#include "ParticleSystemUtils.h"
+#include "Modules/ParticleSystemModule.h"
+#include "Modules/InitialModule.h"
+#include "Modules/ShapeModule.h"
+#include "Modules/EmissionModule.h"
+#include "Modules/SizeModule.h"
+#include "Modules/RotationModule.h"
+#include "Modules/ColorModule.h"
+#include "Modules/UVModule.h"
+#include "Modules/VelocityModule.h"
+#include "Modules/ForceModule.h"
+#include "Modules/ExternalForcesModule.h"
+#include "Modules/ClampVelocityModule.h"
+#include "Modules/SizeByVelocityModule.h"
+#include "Modules/RotationByVelocityModule.h"
+#include "Modules/ColorByVelocityModule.h"
+#include "Modules/CollisionModule.h"
+#include "Modules/SubModule.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/FloatConversion.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Math/AnimationCurve.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Core/Callbacks/GlobalCallbacks.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/ParticleSystem/ParticleSystemEditor.h"
+#include "Runtime/Mono/MonoManager.h"
+#endif
+
+// P1:
+// . Calling ps.Play() in Start(). Does it actually stsrt playing when game starts? No.
+
+// P2:
+// Automatic Culling:
+// . Improve procedural AABB
+// . Gravity: make it work with transforming into local space the same way velocity and force modules work
+// . Get tighter bounds by forces counteracting eachother instead of always expanding
+// . For procedural systems, no gain in calculating AABB every frame. Just do it when something changes.
+// . Solving for roots analytically
+// . http://en.wikipedia.org/wiki/Cubic_function
+// . http://en.wikipedia.org/wiki/Quartic_function
+// . http://en.wikipedia.org/wiki/Quintic_function
+
+// External Forces:
+// . Currently not using turbulence. Start using that?
+
+// Other:
+// . Batching: Make it work with meshes as well
+// . !!! Add runtime tests for playback code. Playing, stopping, simulating etc. Make sure it's 100%.
+
+struct ParticleSystemManager
+{
+ ParticleSystemManager()
+ :needSync(false)
+ {
+ activeEmitters.reserve(32);
+ }
+
+ dynamic_array<ParticleSystem*> activeEmitters;
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ JobScheduler::JobGroupID worldCollisionJobGroup; // group for particle systems performing world collisions, PhysX is currently not threadsafe so deleting objects in LateUpdate could cause crashes as that could overlap with collision testing
+ JobScheduler::JobGroupID jobGroup; // for any other collisions
+#endif
+ bool needSync;
+};
+
+Material* GetDefaultParticleMaterial ()
+{
+ #if WEBPLUG
+ if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion_OldWebResourcesAdded))
+ return GetBuiltinOldWebResource<Material> ("Default-Particle.mat");
+ #endif
+
+ #if UNITY_EDITOR
+ return GetBuiltinExtraResource<Material> ("Default-Particle.mat");
+ #endif
+
+ // If someone happens to create a new particle system component at runtime,
+ // just don't assign any material. The default one might not even be included
+ // into the build.
+ return NULL;
+}
+
+ParticleSystemManager gParticleSystemManager;
+
+PROFILER_INFORMATION(gParticleSystemProfile, "ParticleSystem.Update", kProfilerParticles)
+PROFILER_INFORMATION(gParticleSystemPrewarm, "ParticleSystem.Prewarm", kProfilerParticles)
+PROFILER_INFORMATION(gParticleSystemWait, "ParticleSystem.WaitForUpdateThreads", kProfilerParticles)
+PROFILER_INFORMATION(gParticleSystemJobProfile, "ParticleSystem.UpdateJob", kProfilerParticles)
+
+PROFILER_INFORMATION(gParticleSystemUpdateCollisions, "ParticleSystem.UpdateCollisions", kProfilerParticles)
+
+
+#define MAX_TIME_STEP (0.03f)
+static float GetTimeStep(float dt, bool fixedTimeStep)
+{
+ if(fixedTimeStep)
+ return GetTimeManager().GetFixedDeltaTime();
+ else if(dt > MAX_TIME_STEP)
+ return dt / Ceilf(dt / MAX_TIME_STEP);
+ else
+ return dt;
+}
+
+static void ApplyStartDelay (float& delayT, float& accumulatedDt)
+{
+ if(delayT > 0.0f)
+ {
+ delayT -= accumulatedDt;
+ accumulatedDt = max(-delayT, 0.0f);
+ delayT = max(delayT, 0.0f);
+ }
+ DebugAssert(delayT >= 0.0f);
+}
+
+static inline void CheckParticleConsistency(ParticleSystemState& state, ParticleSystemParticle& particle)
+{
+ particle.lifetime = min(particle.lifetime, particle.startLifetime);
+ state.maxSize = max(state.maxSize, particle.size);
+}
+
+ParticleSystem::ParticleSystem (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_RayBudget(0)
+, m_EmittersIndex (-1)
+#if UNITY_EDITOR
+, m_EditorListNode ( this )
+#endif
+{
+ m_State = new ParticleSystemState ();
+ m_ReadOnlyState = new ParticleSystemReadOnlyState ();
+
+ m_SizeModule = new SizeModule ();
+ m_RotationModule = new RotationModule ();
+ m_ColorModule = new ColorModule ();
+ m_UVModule = new UVModule ();
+ m_VelocityModule = new VelocityModule ();
+ m_ForceModule = new ForceModule ();
+ m_ExternalForcesModule = new ExternalForcesModule ();
+ m_ClampVelocityModule = new ClampVelocityModule ();
+ m_SizeBySpeedModule = new SizeBySpeedModule ();
+ m_RotationBySpeedModule = new RotationBySpeedModule ();
+ m_ColorBySpeedModule = new ColorBySpeedModule ();
+ m_CollisionModule = new CollisionModule ();
+ m_SubModule = new SubModule ();
+
+#if UNITY_EDITOR
+ ParticleSystemEditor::SetupDefaultParticleSystem(*this);
+ m_EditorRandomSeedIndex = 0;
+#endif
+}
+
+ParticleSystem::~ParticleSystem ()
+{
+ delete m_State;
+ delete m_ReadOnlyState;
+
+ delete m_SizeModule;
+ delete m_RotationModule;
+ delete m_ColorModule;
+ delete m_UVModule;
+ delete m_VelocityModule;
+ delete m_ForceModule;
+ delete m_ExternalForcesModule;
+ delete m_ClampVelocityModule;
+ delete m_SizeBySpeedModule;
+ delete m_RotationBySpeedModule;
+ delete m_ColorBySpeedModule;
+ delete m_CollisionModule;
+ delete m_SubModule;
+}
+
+void ParticleSystem::InitializeClass ()
+{
+#if UNITY_EDITOR
+ // This is only necessary to avoid pain during development (Pre 3.5) - can be removed later...
+ RegisterAllowTypeNameConversion ("MinMaxColorCurve", "MinMaxColor");
+ // Only needed due to 3.5 beta content
+ RegisterAllowNameConversion ("MinMaxCurve", "maxScalar", "scalar");
+ RegisterAllowNameConversion ("InitialModule", "lifetime", "startLifetime");
+ RegisterAllowNameConversion ("InitialModule", "speed", "startSpeed");
+ RegisterAllowNameConversion ("InitialModule", "color", "startColor");
+ RegisterAllowNameConversion ("InitialModule", "size", "startSize");
+ RegisterAllowNameConversion ("InitialModule", "rotation", "startRotation");
+ RegisterAllowNameConversion ("ShapeModule", "m_Radius", "radius");
+ RegisterAllowNameConversion ("ShapeModule", "m_Angle", "angle");
+ RegisterAllowNameConversion ("ShapeModule", "m_BoxX", "boxX");
+ RegisterAllowNameConversion ("ShapeModule", "m_BoxY", "boxY");
+ RegisterAllowNameConversion ("ShapeModule", "m_BoxZ", "boxZ");
+
+ REGISTER_MESSAGE_VOID (ParticleSystem, kTransformChanged, TransformChanged);
+#endif
+
+ REGISTER_MESSAGE_VOID(ParticleSystem, kDidDeleteMesh, DidDeleteMesh);
+ REGISTER_MESSAGE_VOID(ParticleSystem, kDidModifyMesh, DidModifyMesh);
+}
+
+void ParticleSystem::SetRayBudget (int rayBudget)
+{
+ m_RayBudget = rayBudget;
+};
+
+int ParticleSystem::GetRayBudget() const
+{
+ return m_RayBudget;
+};
+
+void ParticleSystem::DidModifyMesh ()
+{
+ m_ShapeModule.DidModifyMeshData();
+}
+
+void ParticleSystem::DidDeleteMesh ()
+{
+ m_ShapeModule.DidDeleteMesh(this);
+}
+
+void ParticleSystem::Cull()
+{
+ if(!IsWorldPlaying())
+ return;
+
+ m_State->culled = true;
+
+ Clear(false);
+ m_State->cullTime = GetCurTime ();
+ RemoveFromManager();
+}
+
+void ParticleSystem::RendererBecameVisible()
+{
+ if(m_State->culled)
+ {
+ m_State->culled = false;
+ if(!m_State->stopEmitting && IsPlaying() && IsWorldPlaying() && CheckSupportsProcedural (*this))
+ {
+ double timeDiff = GetCurTime() - m_State->cullTime;
+ Simulate(m_State->numLoops * m_ReadOnlyState->lengthInSec + m_State->t + timeDiff, true);
+ Play();
+ }
+ }
+}
+
+void ParticleSystem::RendererBecameInvisible()
+{
+ if(!m_State->culled && CheckSupportsProcedural(*this))
+ Cull();
+}
+
+void ParticleSystem::AddToManager()
+{
+ if(m_EmittersIndex >= 0)
+ return;
+ size_t index = gParticleSystemManager.activeEmitters.size();
+ gParticleSystemManager.activeEmitters.push_back(this);
+ m_EmittersIndex = index;
+}
+
+void ParticleSystem::RemoveFromManager()
+{
+ if(m_EmittersIndex < 0)
+ return;
+ const int index = m_EmittersIndex;
+ gParticleSystemManager.activeEmitters[index]->m_EmittersIndex = -1;
+ gParticleSystemManager.activeEmitters[index] = gParticleSystemManager.activeEmitters.back();
+ if(gParticleSystemManager.activeEmitters[index] != this) // corner case
+ gParticleSystemManager.activeEmitters[index]->m_EmittersIndex = index;
+ gParticleSystemManager.activeEmitters.resize_uninitialized(gParticleSystemManager.activeEmitters.size() - 1);
+}
+
+void ParticleSystem::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+ m_InitialModule.AwakeFromLoad(this, *m_ReadOnlyState);
+ m_ShapeModule.AwakeFromLoad(this, *m_ReadOnlyState);
+
+ if (!IsActive () || (kDefaultAwakeFromLoad == awakeMode))
+ return;
+
+ m_State->localToWorld = GetComponent (Transform).GetLocalToWorldMatrixNoScale();
+ Matrix4x4f::Invert_General3D(m_State->localToWorld, m_State->worldToLocal);
+ m_State->maxSize = 0.0f;
+ m_State->invalidateProcedural = false;
+
+ if (IsWorldPlaying () && m_ReadOnlyState->playOnAwake)
+ Play ();
+
+ // Does this even happen?
+ if(GetParticleCount() || IsPlaying())
+ AddToManager();
+}
+
+void ParticleSystem::Deactivate (DeactivateOperation operation)
+{
+ Super::Deactivate(operation);
+ SyncJobs();
+ RemoveFromManager();
+}
+
+void ParticleSystem::SyncJobs()
+{
+ if(gParticleSystemManager.needSync)
+ {
+#if ENABLE_MULTITHREADED_PARTICLES
+ PROFILER_BEGIN(gParticleSystemWait, NULL);
+ JobScheduler& scheduler = GetJobScheduler();
+ scheduler.WaitForGroup (gParticleSystemManager.jobGroup);
+ PROFILER_END;
+
+#endif
+ const float deltaTimeEpsilon = 0.0001f;
+ float deltaTime = GetDeltaTime();
+ if(deltaTime < deltaTimeEpsilon)
+ return;
+
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); ++i)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ ParticleSystemState& state = *system.m_State;
+ system.Update2 (system, *system.m_ReadOnlyState, state, false);
+ }
+
+ gParticleSystemManager.needSync = false;
+ }
+}
+
+#if ENABLE_MULTITHREADED_PARTICLES
+void* ParticleSystem::UpdateFunction (void* rawData)
+{
+ Assert (rawData);
+ ParticleSystem* system = reinterpret_cast<ParticleSystem*>(rawData);
+ ParticleSystem::Update1 (*system, system->GetParticles((int)ParticleSystem::kParticleBuffer0), system->GetThreadScratchPad().deltaTime, false, false, system->m_RayBudget);
+ return NULL;
+}
+#endif
+
+#define MAX_NUM_SUB_EMIT_CMDS (1024)
+void ParticleSystem::Update(ParticleSystem& system, float deltaTime, bool fixedTimeStep, bool useProcedural, int rayBudget)
+{
+ system.m_State->recordSubEmits = false;
+ Update0 (system, *system.m_ReadOnlyState, *system.m_State, deltaTime, fixedTimeStep);
+ Update1 (system, system.GetParticles((int)ParticleSystem::kParticleBuffer0), deltaTime, fixedTimeStep, useProcedural, rayBudget);
+ Update2 (system, *system.m_ReadOnlyState, *system.m_State, fixedTimeStep);
+#if UNITY_EDITOR
+ ParticleSystemEditor::PerformInterpolationStep(&system);
+#endif
+}
+
+size_t ParticleSystem::EmitFromModules (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemEmissionState& emissionState, size_t& numContinuous, const Vector3f velocity, float fromT, float toT, float dt)
+{
+ if(system.m_EmissionModule.GetEnabled())
+ return EmitFromData(emissionState, numContinuous, system.m_EmissionModule.GetEmissionDataRef(), velocity, fromT, toT, dt, roState.lengthInSec);
+ return 0;
+}
+
+size_t ParticleSystem::EmitFromData (ParticleSystemEmissionState& emissionState, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length)
+{
+ size_t amountOfParticlesToEmit = 0;
+ EmissionModule::Emit(emissionState, amountOfParticlesToEmit, numContinuous, emissionData, velocity, fromT, toT, dt, length);
+ return amountOfParticlesToEmit;
+}
+
+size_t ParticleSystem::LimitParticleCount(size_t requestSize) const
+{
+ const size_t maxNumParticles = m_InitialModule.GetMaxNumParticles();
+ return min<size_t>(requestSize, maxNumParticles);
+}
+
+size_t ParticleSystem::AddNewParticles(ParticleSystemParticles& particles, size_t newParticles) const
+{
+ const size_t fromIndex = particles.array_size();
+ const size_t newSize = LimitParticleCount(fromIndex + newParticles);
+ particles.array_resize(newSize);
+ return min(fromIndex, newSize);
+}
+
+void ParticleSystem::SimulateParticles (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, float dt)
+{
+ size_t particleCount = ps.array_size();
+ for (size_t q = fromIndex; q < particleCount; )
+ {
+ ps.lifetime[q] -= dt;
+
+#if UNITY_EDITOR
+ if(ParticleSystemEditor::GetIsExtraInterpolationStep())
+ {
+ ps.lifetime[q] = max(ps.lifetime[q], 0.0f);
+ }
+ else
+#endif
+
+ if (ps.lifetime[q] < 0)
+ {
+ KillParticle(roState, state, ps, q, particleCount);
+ continue;
+ }
+ ++q;
+ }
+ ps.array_resize(particleCount);
+
+ for (size_t q = fromIndex; q < particleCount; ++q)
+ ps.position[q] += (ps.velocity[q] + ps.animatedVelocity[q]) * dt;
+
+ if(ps.usesRotationalSpeed)
+ for (size_t q = fromIndex; q < particleCount; ++q)
+ ps.rotation[q] += ps.rotationalSpeed[q] * dt;
+}
+
+// Copy staging particles into real particle buffer
+void ParticleSystem::AddStagingBuffer(ParticleSystem& system)
+{
+ if(0 == system.m_ParticlesStaging.array_size())
+ return;
+
+ bool needsAxisOfRotation = system.m_Particles[kParticleBuffer0].usesAxisOfRotation;
+ bool needsEmitAccumulator = system.m_Particles[kParticleBuffer0].numEmitAccumulators > 0;
+
+ ASSERT_RUNNING_ON_MAIN_THREAD;
+ const int numParticles = system.m_Particles[kParticleBuffer0].array_size();
+ const int numStaging = system.m_ParticlesStaging.array_size();
+ system.m_Particles->array_resize(numParticles + numStaging);
+ system.m_Particles->array_merge_preallocated(system.m_ParticlesStaging, numParticles, needsAxisOfRotation, needsEmitAccumulator);
+ system.m_ParticlesStaging.array_resize(0);
+}
+
+void ParticleSystem::Emit(ParticleSystem& system, const SubEmitterEmitCommand& command, ParticleSystemEmitMode emitMode)
+{
+ int amountOfParticlesToEmit = command.particlesToEmit;
+ if (amountOfParticlesToEmit > 0)
+ {
+ ParticleSystemState& state = *system.m_State;
+ const ParticleSystemReadOnlyState& roState = *system.m_ReadOnlyState;
+
+ const int numContinuous = command.particlesToEmitContinuous;
+ const float deltaTime = command.deltaTime;
+ float parentT = command.parentT;
+ Vector3f position = command.position;
+ Vector3f initialVelocity = command.velocity;
+
+ Matrix3x3f rotMat;
+ Vector3f normalizedVelocity = NormalizeSafe(initialVelocity);
+ float angle = Abs(Dot(normalizedVelocity, Vector3f::zAxis));
+ Vector3f up = Lerp(Vector3f::zAxis, Vector3f::yAxis, angle);
+ if (!LookRotationToMatrix(normalizedVelocity, up, &rotMat))
+ rotMat.SetIdentity();
+
+ Matrix4x4f parentParticleMatrix = rotMat;
+ parentParticleMatrix.SetPosition(position);
+
+ // Transform into local space of sub emitter
+ Matrix4x4f concatMatrix;
+ if(roState.useLocalSpace)
+ MultiplyMatrices3x4(state.worldToLocal, parentParticleMatrix, concatMatrix);
+ else
+ concatMatrix = parentParticleMatrix;
+
+ if(roState.useLocalSpace)
+ initialVelocity = state.worldToLocal.MultiplyVector3(initialVelocity);
+
+ DebugAssert(state.GetIsSubEmitter());
+ DebugAssert(state.stopEmitting);
+
+ const float commandAliveTime = command.timeAlive;
+ const float timeStep = GetTimeStep(commandAliveTime, true);
+
+ // @TODO: Perform culling: if max lifetime < timeAlive, then just skip emit
+
+ // Perform sub emitter loop
+ if(roState.looping)
+ parentT = fmodf(parentT, roState.lengthInSec);
+
+ ParticleSystemParticles& particles = (kParticleSystemEMDirect == emitMode) ? system.m_Particles[kParticleBuffer0] : system.m_ParticlesStaging;
+
+ size_t fromIndex = system.AddNewParticles(particles, amountOfParticlesToEmit);
+ StartModules (system, roState, state, command.emissionState, initialVelocity, concatMatrix, particles, fromIndex, deltaTime, parentT, numContinuous, 0.0f);
+
+ // Make sure particles get updated
+ if(0 == fromIndex)
+ system.KeepUpdating();
+
+ // Update incremental
+ if(timeStep > 0.0001f)
+ {
+ float accumulatedDt = commandAliveTime;
+ while (accumulatedDt >= timeStep)
+ {
+ accumulatedDt -= timeStep;
+ UpdateModulesIncremental(system, roState, state, particles, fromIndex, timeStep);
+ }
+ }
+ }
+}
+
+
+void ParticleSystem::PlaybackSubEmitterCommandBuffer(const ParticleSystem& parent, ParticleSystemState& state, bool fixedTimeStep)
+{
+ ParticleSystem* subEmittersBirth[kParticleSystemMaxSubBirth];
+ ParticleSystem* subEmittersCollision[kParticleSystemMaxSubCollision];
+ ParticleSystem* subEmittersDeath[kParticleSystemMaxSubDeath];
+ int numBirth = parent.m_SubModule->GetSubEmitterPtrsBirth(&subEmittersBirth[0]);
+ int numCollision = parent.m_SubModule->GetSubEmitterPtrsCollision(&subEmittersCollision[0]);
+ int numDeath = parent.m_SubModule->GetSubEmitterPtrsDeath(&subEmittersDeath[0]);
+
+ const int numCommands = state.subEmitterCommandBuffer.commandCount;
+ const SubEmitterEmitCommand* commands = state.subEmitterCommandBuffer.commands;
+ for(int i = 0; i < numCommands; i++)
+ {
+ const SubEmitterEmitCommand& command = commands[i];
+ ParticleSystem* shuriken = NULL;
+ if(command.subEmitterType == kParticleSystemSubTypeBirth)
+ shuriken = (command.subEmitterIndex < numBirth) ? subEmittersBirth[command.subEmitterIndex] : NULL;
+ else if(command.subEmitterType == kParticleSystemSubTypeCollision)
+ shuriken = (command.subEmitterIndex < numCollision) ? subEmittersCollision[command.subEmitterIndex] : NULL;
+ else if(command.subEmitterType == kParticleSystemSubTypeDeath)
+ shuriken = (command.subEmitterIndex < numDeath) ? subEmittersDeath[command.subEmitterIndex] : NULL;
+ else
+ Assert(!"PlaybackSubEmitterCommandBuffer: Sub emitter type not implemented");
+
+ DebugAssert(shuriken && "Y U NO HERE ANYMORE?");
+ if(!shuriken)
+ continue;
+
+ ParticleSystem::Emit(*shuriken, command, kParticleSystemEMDirect);
+ }
+
+ state.subEmitterCommandBuffer.commandCount = 0;
+}
+
+void ParticleSystem::UpdateBounds(const ParticleSystem& system, const ParticleSystemParticles& ps, ParticleSystemState& state)
+{
+ const size_t particleCount = ps.array_size();
+ if (particleCount == 0)
+ {
+ state.minMaxAABB = MinMaxAABB (Vector3f::zero, Vector3f::zero);
+ return;
+ }
+
+ state.minMaxAABB.Init();
+
+ if(CheckSupportsProcedural(system))
+ {
+ const Transform& transform = system.GetComponent (Transform);
+ Matrix4x4f localToWorld = transform.GetLocalToWorldMatrixNoScale ();
+
+ // Lifetime and max speed
+ const Vector2f minMaxLifeTime = system.m_InitialModule.GetLifeTimeCurve().FindMinMax();
+
+ const float maxLifeTime = minMaxLifeTime.y;
+ const Vector2f minMaxStartSpeed = system.m_InitialModule.GetSpeedCurve().FindMinMax() * maxLifeTime;
+
+ state.minMaxAABB = MinMaxAABB(Vector3f::zero, Vector3f::zero);
+ state.minMaxAABB.Encapsulate(Vector3f::zAxis * minMaxStartSpeed.x);
+ state.minMaxAABB.Encapsulate(Vector3f::zAxis * minMaxStartSpeed.y);
+ if(system.m_ShapeModule.GetEnabled())
+ system.m_ShapeModule.CalculateProceduralBounds(state.minMaxAABB, state.emitterScale, minMaxStartSpeed);
+
+ // Gravity
+ // @TODO: Do what we do for force and velocity here, i.e. transform bounds properly
+ const Vector3f gravityBounds = system.m_InitialModule.GetGravity(*system.m_ReadOnlyState, *system.m_State) * maxLifeTime * maxLifeTime * 0.5f;
+
+ state.minMaxAABB.m_Max += max(gravityBounds, Vector3f::zero);
+ state.minMaxAABB.m_Min += min(gravityBounds, Vector3f::zero);
+
+ MinMaxAABB velocityBounds (Vector3f::zero, Vector3f::zero);
+ if(system.m_VelocityModule->GetEnabled())
+ system.m_VelocityModule->CalculateProceduralBounds(velocityBounds, localToWorld, maxLifeTime);
+ state.minMaxAABB.m_Max += velocityBounds.m_Max;
+ state.minMaxAABB.m_Min += velocityBounds.m_Min;
+
+ MinMaxAABB forceBounds (Vector3f::zero, Vector3f::zero);
+ if(system.m_ForceModule->GetEnabled())
+ system.m_ForceModule->CalculateProceduralBounds(forceBounds, localToWorld, maxLifeTime);
+ state.minMaxAABB.m_Max += forceBounds.m_Max;
+ state.minMaxAABB.m_Min += forceBounds.m_Min;
+
+ // Modules that are not supported by procedural
+ DebugAssert(!system.m_RotationBySpeedModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ClampVelocityModule->GetEnabled()); // unsupported: (Need to compute velocity by deriving position curves...), possible to support?
+ DebugAssert(!system.m_CollisionModule->GetEnabled());
+ DebugAssert(!system.m_SubModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ExternalForcesModule->GetEnabled());
+ }
+ else
+ {
+ for (size_t q = 0; q < particleCount; ++q)
+ state.minMaxAABB.Encapsulate (ps.position[q]);
+
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(renderer && (renderer->GetRenderMode() == kSRMStretch3D))
+ {
+ const float velocityScale = renderer->GetVelocityScale();
+ const float lengthScale = renderer->GetLengthScale();
+ for(size_t q = 0; q < particleCount; ++q )
+ {
+ float sqrVelocity = SqrMagnitude (ps.velocity[q]+ps.animatedVelocity[q]);
+ if (sqrVelocity > Vector3f::epsilon)
+ {
+ float scale = velocityScale + FastInvSqrt (sqrVelocity) * lengthScale * ps.size[q];
+ state.minMaxAABB.Encapsulate(ps.position[q] - ps.velocity[q] * scale);
+ }
+ }
+ }
+ }
+
+ // Expand with maximum particle size * sqrt(2) (the length of a diagonal of a particle, if it should happen to be rotated)
+ const float kSqrtOf2 = 1.42f;
+ float maxSize = kSqrtOf2 * 0.5f * system.m_InitialModule.GetSizeCurve().FindMinMax().y;
+ if(system.m_SizeModule->GetEnabled())
+ maxSize *= system.m_SizeModule->GetCurve().FindMinMax().y;
+ if(system.m_SizeBySpeedModule->GetEnabled())
+ maxSize *= system.m_SizeBySpeedModule->GetCurve().FindMinMax().y;
+ state.minMaxAABB.Expand (max(maxSize, state.maxSize));
+
+ Assert (state.minMaxAABB.IsValid ());
+}
+
+IMPLEMENT_CLASS_HAS_INIT (ParticleSystem)
+IMPLEMENT_OBJECT_SERIALIZE (ParticleSystem)
+
+
+template<class TransferFunction>
+void ParticleSystem::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ m_ReadOnlyState->Transfer (transfer); m_ReadOnlyState->CheckConsistency();
+ m_State->Transfer (transfer);
+ transfer.Transfer(m_InitialModule, m_InitialModule.GetName ()); m_InitialModule.CheckConsistency ();
+ transfer.Transfer(m_ShapeModule, m_ShapeModule.GetName ()); m_ShapeModule.CheckConsistency ();
+ transfer.Transfer(m_EmissionModule, m_EmissionModule.GetName ()); m_EmissionModule.CheckConsistency ();
+ transfer.Transfer(*m_SizeModule, m_SizeModule->GetName ()); m_SizeModule->CheckConsistency ();
+ transfer.Transfer(*m_RotationModule, m_RotationModule->GetName ()); m_RotationModule->CheckConsistency ();
+ transfer.Transfer(*m_ColorModule, m_ColorModule->GetName ()); m_ColorModule->CheckConsistency ();
+ transfer.Transfer(*m_UVModule, m_UVModule->GetName ()); m_UVModule->CheckConsistency ();
+ transfer.Transfer(*m_VelocityModule, m_VelocityModule->GetName ()); m_VelocityModule->CheckConsistency ();
+ transfer.Transfer(*m_ForceModule, m_ForceModule->GetName ()); m_ForceModule->CheckConsistency ();
+ transfer.Transfer(*m_ExternalForcesModule, m_ExternalForcesModule->GetName ()); m_ExternalForcesModule->CheckConsistency ();
+ transfer.Transfer(*m_ClampVelocityModule, m_ClampVelocityModule->GetName ()); m_ClampVelocityModule->CheckConsistency ();
+ transfer.Transfer(*m_SizeBySpeedModule, m_SizeBySpeedModule->GetName ()); m_SizeBySpeedModule->CheckConsistency ();
+ transfer.Transfer(*m_RotationBySpeedModule, m_RotationBySpeedModule->GetName ()); m_RotationBySpeedModule->CheckConsistency ();
+ transfer.Transfer(*m_ColorBySpeedModule, m_ColorBySpeedModule->GetName ()); m_ColorBySpeedModule->CheckConsistency ();
+ transfer.Transfer(*m_CollisionModule, m_CollisionModule->GetName ()); m_CollisionModule->CheckConsistency ();
+ transfer.Transfer(*m_SubModule, m_SubModule->GetName ()); m_SubModule->CheckConsistency ();
+ if(transfer.IsReading())
+ {
+ m_State->supportsProcedural = DetermineSupportsProcedural(*this);
+ m_State->invalidateProcedural = true; // Stuff might have changed which we can't support (example: start speed has become smaller)
+ }
+}
+
+bool ParticleSystem::CheckSupportsProcedural(const ParticleSystem& system)
+{
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(renderer && (renderer->GetRenderMode() == kSRMStretch3D))
+ return false;
+ return system.m_State->supportsProcedural && !system.m_State->invalidateProcedural;
+}
+
+// TODO: Needs to match UpdateCullingSupportedString in all xxModule.cs files
+bool ParticleSystem::DetermineSupportsProcedural(const ParticleSystem& system)
+{
+ bool supportsProcedural = true;
+ supportsProcedural = supportsProcedural && system.m_ReadOnlyState->useLocalSpace;
+
+ // Can't be random as we recreate it every frame. TODO: Would be great if we can support this in procedural mode.
+ const short lifeTimeMinMaxState = system.m_InitialModule.GetLifeTimeCurve().minMaxState;
+ supportsProcedural = supportsProcedural && (lifeTimeMinMaxState == kMMCScalar || lifeTimeMinMaxState == kMMCCurve);
+
+ supportsProcedural = supportsProcedural && (EmissionModule::kEmissionTypeDistance != system.m_EmissionModule.GetEmissionDataRef().type);
+
+ supportsProcedural = supportsProcedural && !system.m_ExternalForcesModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_ClampVelocityModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_RotationBySpeedModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_CollisionModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_SubModule->GetEnabled();
+
+ if(system.m_RotationModule->GetEnabled())
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_RotationModule->GetCurve().editorCurves, system.m_RotationModule->GetCurve().minMaxState);
+
+ if(system.m_VelocityModule->GetEnabled())
+ {
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_VelocityModule->GetXCurve().editorCurves, system.m_VelocityModule->GetXCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_VelocityModule->GetYCurve().editorCurves, system.m_VelocityModule->GetYCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_VelocityModule->GetZCurve().editorCurves, system.m_VelocityModule->GetZCurve().minMaxState);
+ }
+
+ if(system.m_ForceModule->GetEnabled())
+ {
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_ForceModule->GetXCurve().editorCurves, system.m_ForceModule->GetXCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_ForceModule->GetYCurve().editorCurves, system.m_ForceModule->GetYCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_ForceModule->GetZCurve().editorCurves, system.m_ForceModule->GetZCurve().minMaxState);
+ supportsProcedural = supportsProcedural && !system.m_ForceModule->GetRandomizePerFrame();
+ }
+
+ return supportsProcedural;
+}
+
+bool ParticleSystem::ComputePrewarmStartParameters(float& prewarmTime, float t)
+{
+ const float fixedDelta = GetTimeManager().GetFixedDeltaTime();
+ const float maxLifetime = m_InitialModule.GetLifeTimeCurve().FindMinMax().y;
+ const float length = m_ReadOnlyState->lengthInSec;
+ if(!m_ReadOnlyState->looping && ((maxLifetime + length) < t))
+ return false;
+
+ prewarmTime = m_SubModule->GetEnabled() ? CalculateSubEmitterMaximumLifeTime(maxLifetime) : 0.0f;
+ prewarmTime = max(prewarmTime, maxLifetime);
+
+ float frac = fmodf(t, fixedDelta);
+ float startT = t - prewarmTime - frac;
+ prewarmTime += frac;
+
+ // Clamp to start
+ if(!m_ReadOnlyState->prewarm)
+ {
+ startT = max(startT, 0.0f);
+ prewarmTime = min(t, prewarmTime);
+ }
+
+ // This is needed to figure out if emitacc should be inverted or not
+ const float signStartT = Sign(startT);
+ const float absStartT = Abs(startT);
+
+ while(startT < 0.0f)
+ startT += length;
+ float endT = startT + absStartT;
+ m_State->t = fmodf(startT, length);
+
+ ParticleSystemEmissionState emissionState;
+ const Vector3f emitVelocity = Vector3f::zero;
+ const float epsilon = 0.0001f;
+ int seedIndex = 0;
+
+ const float prevStartT = startT;
+ const float nextStartT = startT + fixedDelta;
+ const float prevEndT = endT;
+ const float nextEndT = endT + fixedDelta;
+ if (!(nextStartT > prevStartT && nextEndT > prevEndT))
+ {
+ ErrorStringObject ("Precision issue while prewarming particle system - 'Duration' or 'Start Lifetime' is likely a too large value.", this);
+ return false;
+ }
+
+ while((startT + epsilon) < endT)
+ {
+ const float toT = fmodf(startT + fixedDelta, length);
+ const float fromT = fmodf(startT, length);
+
+ size_t numContinuous;
+ int numParticles = ParticleSystem::EmitFromModules(*this, *m_ReadOnlyState, emissionState, numContinuous, emitVelocity, fromT, toT, fixedDelta);
+ if(numParticles > 0)
+ seedIndex++;
+ startT += fixedDelta;
+ }
+ m_State->emissionState.m_ToEmitAccumulator = ((signStartT > 0.0f ? emissionState.m_ToEmitAccumulator : 1.0f - emissionState.m_ToEmitAccumulator)) + epsilon;
+#if UNITY_EDITOR
+ m_EditorRandomSeedIndex = signStartT > 0.0f ? seedIndex : -seedIndex-1;
+#endif
+
+ return true;
+}
+
+void ParticleSystem::Simulate (float t, bool restart)
+{
+ PROFILER_AUTO(gParticleSystemPrewarm, NULL)
+
+ if(restart)
+ {
+ m_InitialModule.ResetSeed(*m_ReadOnlyState);
+ m_ShapeModule.ResetSeed(*m_ReadOnlyState);
+ Stop();
+ Clear();
+ Play (false);
+
+ ApplyStartDelay (m_State->delayT, t);
+ float prewarmTime;
+ if(ComputePrewarmStartParameters(prewarmTime, t))
+ {
+ Update(*this, prewarmTime, true, CheckSupportsProcedural(*this));
+ Pause ();
+ }
+ else
+ {
+ Stop();
+ Clear();
+ }
+ }
+ else
+ {
+ m_State->playing = true;
+ Update(*this, t, true, false);
+ Pause ();
+ }
+}
+
+void ParticleSystem::Play (bool autoPrewarm)
+{
+ Assert (m_State);
+ if(!IsActive () || m_State->GetIsSubEmitter())
+ return;
+
+ m_State->stopEmitting = false;
+ m_State->playing = true;
+ if (m_State->needRestart)
+ {
+ if(m_ReadOnlyState->prewarm)
+ {
+ if (autoPrewarm)
+ AutoPrewarm();
+ }
+ else
+ {
+ m_State->delayT = m_ReadOnlyState->startDelay;
+ }
+
+ m_State->playing = true;
+ m_State->t = 0.0f;
+ m_State->numLoops = 0;
+ m_State->invalidateProcedural = false;
+ m_State->accumulatedDt = 0.0f;
+#if UNITY_EDITOR
+ m_EditorRandomSeedIndex = 0;
+#endif
+ m_State->emissionState.Clear();
+ }
+
+ if(m_State->culled && CheckSupportsProcedural(*this))
+ Cull();
+ else
+ AddToManager();
+}
+
+void ParticleSystem::AutoPrewarm()
+{
+ if(m_ReadOnlyState->prewarm && m_ReadOnlyState->looping)
+ {
+ DebugAssert(!m_State->GetIsSubEmitter());
+ DebugAssert(m_State->playing); // Only use together with play
+ Simulate (0.0f, true);
+ //DebugAssert(CompareApproximately(m_State->emissionState.m_ToEmitAccumulator, 1.0f, 0.01f) && "Particle emission gaps may occur");
+ }
+}
+
+void ParticleSystem::Stop ()
+{
+ Assert (m_State);
+ m_State->needRestart = true;
+ m_State->stopEmitting = true;
+}
+
+void ParticleSystem::Pause ()
+{
+ Assert (m_State);
+ m_State->playing = false;
+ m_State->needRestart = false;
+ RemoveFromManager();
+}
+
+bool ParticleSystem::IsPaused () const
+{
+ Assert (m_State);
+ return !m_State->playing && !m_State->needRestart;
+}
+
+bool ParticleSystem::IsStopped () const
+{
+ Assert (m_State);
+ return !m_State->playing && m_State->needRestart;
+}
+
+void ParticleSystem::KeepUpdating()
+{
+ if (IsActive())
+ {
+ // Ensure added particles will update, but stop emission
+ m_State->playing = true;
+ m_State->stopEmitting = true;
+ AddToManager();
+ }
+}
+
+void ParticleSystem::Emit (int count)
+{
+ // StartParticles() takes size_t so check for negative value here (case 495098)
+ if (count <= 0)
+ return;
+
+ KeepUpdating();
+ Assert (m_State);
+
+ const Transform& transform = GetComponent (Transform);
+ Matrix4x4f oldLocalToWorld = m_State->localToWorld;
+ Matrix4x4f oldWorldToLocal = m_State->worldToLocal;
+ Vector3f oldEmitterScale = m_State->emitterScale;
+ m_State->localToWorld = transform.GetLocalToWorldMatrixNoScale ();
+ Matrix4x4f::Invert_General3D(m_State->localToWorld, m_State->worldToLocal);
+ if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_2_a1))
+ m_State->emitterScale = transform.GetWorldScaleLossy();
+ else
+ m_State->emitterScale = transform.GetLocalScale();
+ StartParticles(*this, m_Particles[kParticleBuffer0], 0.0f, m_State->t, 0.0f, 0, count, 0.0f);
+ m_State->localToWorld = oldLocalToWorld;
+ m_State->worldToLocal = oldWorldToLocal;
+ m_State->emitterScale = oldEmitterScale;
+}
+
+void ParticleSystem::EmitParticleExternal(ParticleSystemParticle* particle)
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying() && IsStopped ())
+ return;
+#endif
+
+ m_State->invalidateProcedural = true;
+
+ CheckParticleConsistency(*m_State, *particle);
+ if(particle->lifetime <= 0.0f)
+ return;
+
+ KeepUpdating();
+
+ m_Particles[kParticleBuffer0].AddParticle(particle);
+
+#if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ m_Particles[kParticleBuffer1].AddParticle(particle);
+#endif
+
+ if(!IsPlaying())
+ UpdateBounds(*this, m_Particles[kParticleBuffer0], *m_State);
+}
+
+void ParticleSystem::Clear (bool updateBounds)
+{
+ for(int i = 0; i < kNumParticleBuffers; i++)
+ m_Particles[i].array_resize(0);
+ m_ParticlesStaging.array_resize(0);
+ m_State->emitReplay.resize_uninitialized(0);
+
+ if (m_State->stopEmitting)
+ {
+ // This triggers sometimes, why? (case 491684)
+ DebugAssert (m_State->needRestart);
+ m_State->playing = false;
+ RemoveFromManager();
+ }
+
+ if(updateBounds)
+ {
+ UpdateBounds(*this, m_Particles[kParticleBuffer0], *m_State);
+ Update2(*this, *m_ReadOnlyState, *m_State, false);
+ }
+}
+
+float ParticleSystem::GetStartDelay() const
+{
+ Assert(m_State);
+ return m_ReadOnlyState->startDelay;
+}
+
+void ParticleSystem::SetStartDelay(float value)
+{
+ Assert(m_State);
+ m_ReadOnlyState->startDelay = value;
+ SetDirty ();
+}
+
+bool ParticleSystem::IsAlive () const
+{
+ Assert(m_State);
+ return (!IsStopped() || (GetParticleCount() > 0));
+}
+
+bool ParticleSystem::IsPlaying () const
+{
+ Assert (m_State);
+ return m_State->playing;
+}
+
+bool ParticleSystem::GetLoop () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->looping;
+}
+
+void ParticleSystem::SetLoop (bool loop)
+{
+ Assert (m_State);
+ m_ReadOnlyState->looping = loop;
+ SetDirty ();
+}
+
+bool ParticleSystem::GetPlayOnAwake () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->playOnAwake;
+}
+
+void ParticleSystem::SetPlayOnAwake (bool playOnAwake)
+{
+ Assert (m_State);
+ m_ReadOnlyState->playOnAwake = playOnAwake;
+ SetDirty ();
+}
+
+ParticleSystemSimulationSpace ParticleSystem::GetSimulationSpace () const
+{
+ Assert (m_State);
+ return (m_ReadOnlyState->useLocalSpace ? kSimLocal : kSimWorld);
+}
+
+void ParticleSystem::SetSimulationSpace (ParticleSystemSimulationSpace simulationSpace)
+{
+ Assert (m_State);
+ m_ReadOnlyState->useLocalSpace = (simulationSpace == kSimLocal);
+ SetDirty ();
+}
+
+float ParticleSystem::GetSecPosition () const
+{
+ Assert (m_State);
+ return m_State->t;
+}
+
+void ParticleSystem::SetSecPosition (float pos)
+{
+ Assert (m_State);
+ m_State->t = pos;
+ SetDirty ();
+}
+
+float ParticleSystem::GetPlaybackSpeed () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->speed;
+}
+
+void ParticleSystem::SetPlaybackSpeed (float speed)
+{
+ Assert (m_State);
+ m_ReadOnlyState->speed = speed;
+ SetDirty ();
+}
+
+float ParticleSystem::GetLengthInSec () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->lengthInSec;
+}
+
+void ParticleSystem::GetNumTiles(int& uvTilesX, int& uvTilesY) const
+{
+ uvTilesX = uvTilesY = 1;
+ if(m_UVModule->GetEnabled())
+ m_UVModule->GetNumTiles(uvTilesX, uvTilesY);
+}
+
+Matrix4x4f ParticleSystem::GetLocalToWorldMatrix() const
+{
+ return m_State->localToWorld;
+}
+
+bool ParticleSystem::GetEnableEmission() const
+{
+ return m_EmissionModule.GetEnabled();
+}
+
+void ParticleSystem::SetEnableEmission(bool value)
+{
+ m_State->invalidateProcedural = true;
+ m_EmissionModule.SetEnabled(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetEmissionRate() const
+{
+ return m_EmissionModule.GetEmissionDataRef().rate.GetScalar();
+}
+
+void ParticleSystem::SetEmissionRate(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_EmissionModule.GetEmissionData().rate.SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetStartSpeed() const
+{
+ return m_InitialModule.GetSpeedCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartSpeed(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_InitialModule.GetSpeedCurve().SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetStartSize() const
+{
+ return m_InitialModule.GetSizeCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartSize(float value)
+{
+ m_InitialModule.GetSizeCurve().SetScalar(value);
+ SetDirty();
+}
+
+ColorRGBAf ParticleSystem::GetStartColor() const
+{
+ return m_InitialModule.GetColor().maxColor;
+}
+
+void ParticleSystem::SetStartColor(ColorRGBAf value)
+{
+ m_InitialModule.GetColor().maxColor = value;
+ SetDirty();
+}
+
+float ParticleSystem::GetStartRotation() const
+{
+ return m_InitialModule.GetRotationCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartRotation(float value)
+{
+ m_InitialModule.GetRotationCurve().SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetStartLifeTime() const
+{
+ return m_InitialModule.GetLifeTimeCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartLifeTime(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_InitialModule.GetLifeTimeCurve().SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetGravityModifier() const
+{
+ return m_InitialModule.GetGravityModifier();
+}
+
+void ParticleSystem::SetGravityModifier(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_InitialModule.SetGravityModifier(value);
+ SetDirty();
+}
+
+UInt32 ParticleSystem::GetRandomSeed() const
+{
+ return m_ReadOnlyState->randomSeed;
+}
+
+void ParticleSystem::SetRandomSeed(UInt32 value)
+{
+ m_ReadOnlyState->randomSeed = value;
+ SetDirty();
+}
+
+int ParticleSystem::GetMaxNumParticles () const
+{
+ return m_InitialModule.GetMaxNumParticles ();
+}
+
+void ParticleSystem::SetMaxNumParticles (int value)
+{
+ m_InitialModule.SetMaxNumParticles (value);
+ SetDirty();
+}
+
+void ParticleSystem::AllocateAllStructuresOfArrays()
+{
+ // Make sure all particle buffers have all elements
+ for(int i = 0; i < kNumParticleBuffers; i++)
+ {
+ if(!m_Particles[i].usesAxisOfRotation)
+ m_Particles[i].SetUsesAxisOfRotation();
+ m_Particles[i].SetUsesEmitAccumulator(kParticleSystemMaxNumEmitAccumulators);
+ }
+}
+
+void ParticleSystem::SetParticlesExternal (ParticleSystemParticle* particles, int size)
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying() && IsStopped ())
+ return;
+#endif
+
+ m_State->invalidateProcedural = true;
+
+ // Make sure particles are in the correct ranges etc.
+ for (size_t q = 0; q < size; q++)
+ CheckParticleConsistency(*m_State, particles[q]);
+
+ AllocateAllStructuresOfArrays();
+ ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ ps.array_resize(size);
+ ps.CopyFromArrayAOS(particles, size);
+ size_t particleCount = size;
+ for (size_t q = 0; q < particleCount; )
+ {
+ if (ps.lifetime[q] < 0)
+ {
+ KillParticle(*m_ReadOnlyState, *m_State, ps, q, particleCount);
+ continue;
+ }
+ ++q;
+ }
+ ps.array_resize(particleCount);
+
+ #if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ m_Particles[kParticleBuffer1].array_assign(ps);
+ #endif
+
+ if(!IsPlaying())
+ UpdateBounds(*this, ps, *m_State);
+}
+
+void ParticleSystem::GetParticlesExternal (ParticleSystemParticle* particles, int size)
+{
+ AllocateAllStructuresOfArrays();
+ ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ ps.CopyToArrayAOS(particles, size);
+}
+
+int ParticleSystem::GetSafeCollisionEventSize () const
+{
+ const ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ return ps.collisionEvents.GetCollisionEventCount ();
+}
+
+int ParticleSystem::GetCollisionEventsExternal (int instanceID, MonoParticleCollisionEvent* collisionEvents, int size) const
+{
+ const ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ return ps.collisionEvents.GetCollisionEvents (instanceID, collisionEvents, size);
+}
+
+ParticleSystemParticles& ParticleSystem::GetParticles (int index)
+{
+#if UNITY_EDITOR
+ if(index == -1)
+ if(ParticleSystemEditor::UseInterpolation(*this))
+ index = (int)kParticleBuffer1;
+ else
+ index = (int)kParticleBuffer0;
+ return m_Particles[index];
+#else
+ return m_Particles[kParticleBuffer0];
+#endif
+}
+
+const ParticleSystemParticles& ParticleSystem::GetParticles (int index) const
+{
+#if UNITY_EDITOR
+ if(index == -1)
+ if(ParticleSystemEditor::UseInterpolation(*this))
+ index = (int)kParticleBuffer1;
+ else
+ index = (int)kParticleBuffer0;
+ return m_Particles[index];
+#else
+ return m_Particles[kParticleBuffer0];
+#endif
+}
+
+size_t ParticleSystem::GetParticleCount () const
+{
+ return m_Particles[kParticleBuffer0].array_size ();
+}
+
+int ParticleSystem::SetupSubEmitters(ParticleSystem& shuriken, ParticleSystemState& state)
+{
+ Assert(!state.cachedSubDataBirth && !state.numCachedSubDataBirth);
+ Assert(!state.cachedSubDataCollision && !state.numCachedSubDataCollision);
+ Assert(!state.cachedSubDataDeath && !state.numCachedSubDataDeath);
+ int subEmitterCount = 0;
+
+ if(shuriken.m_SubModule->GetEnabled())
+ {
+ ParticleSystem* subEmittersBirth[kParticleSystemMaxSubBirth];
+ state.numCachedSubDataBirth = shuriken.m_SubModule->GetSubEmitterPtrsBirth(&subEmittersBirth[0]);
+ state.cachedSubDataBirth = ALLOC_TEMP_MANUAL(ParticleSystemSubEmitterData, state.numCachedSubDataBirth);
+ std::uninitialized_fill (state.cachedSubDataBirth, state.cachedSubDataBirth + state.numCachedSubDataBirth, ParticleSystemSubEmitterData());
+ for(int i = 0; i < state.numCachedSubDataBirth; i++)
+ {
+ ParticleSystem* subEmitter = subEmittersBirth[i];
+ ParticleSystemSubEmitterData& subData = state.cachedSubDataBirth[i];
+ subData.startDelayInSec = subEmitter->m_ReadOnlyState->startDelay;
+ subData.lengthInSec = subEmitter->GetLoop() ? numeric_limits<float>::max() : subEmitter->GetLengthInSec();
+ subData.maxLifetime = subEmitter->m_InitialModule.GetLifeTimeCurve().GetScalar();
+ subData.emitter = subEmitter;
+ subEmitter->m_EmissionModule.GetEmissionDataCopy(&subData.emissionData);
+ subEmitter->m_State->SetIsSubEmitter(true);
+ subEmitterCount++;
+ }
+ ParticleSystem* subEmittersCollision[kParticleSystemMaxSubCollision];
+ state.numCachedSubDataCollision = shuriken.m_SubModule->GetSubEmitterPtrsCollision(&subEmittersCollision[0]);
+ state.cachedSubDataCollision = ALLOC_TEMP_MANUAL(ParticleSystemSubEmitterData, state.numCachedSubDataCollision);
+ std::uninitialized_fill (state.cachedSubDataCollision, state.cachedSubDataCollision + state.numCachedSubDataCollision, ParticleSystemSubEmitterData());
+ for(int i = 0; i < state.numCachedSubDataCollision; i++)
+ {
+ ParticleSystem* subEmitter = subEmittersCollision[i];
+ ParticleSystemSubEmitterData& subData = state.cachedSubDataCollision[i];
+ subData.emitter = subEmitter;
+ subEmitter->m_EmissionModule.GetEmissionDataCopy(&subData.emissionData);
+ subEmitter->m_State->SetIsSubEmitter(true);
+ subEmitterCount++;
+ }
+ ParticleSystem* subEmittersDeath[kParticleSystemMaxSubDeath];
+ state.numCachedSubDataDeath = shuriken.m_SubModule->GetSubEmitterPtrsDeath(&subEmittersDeath[0]);
+ state.cachedSubDataDeath = ALLOC_TEMP_MANUAL(ParticleSystemSubEmitterData, state.numCachedSubDataDeath);
+ std::uninitialized_fill (state.cachedSubDataDeath, state.cachedSubDataDeath + state.numCachedSubDataDeath, ParticleSystemSubEmitterData());
+ for(int i = 0; i < state.numCachedSubDataDeath; i++)
+ {
+ ParticleSystem* subEmitter = subEmittersDeath[i];
+ ParticleSystemSubEmitterData& subData = state.cachedSubDataDeath[i];
+ subData.emitter = subEmitter;
+ subEmitter->m_EmissionModule.GetEmissionDataCopy(&subData.emissionData);
+ subEmitter->m_State->SetIsSubEmitter(true);
+ subEmitterCount++;
+ }
+ }
+ return subEmitterCount;
+}
+
+int ParticleSystem::CalculateMaximumSubEmitterEmitCount(ParticleSystem& shuriken, ParticleSystemState& state, float deltaTime, bool fixedTimeStep)
+{
+ // Save emission state
+ const ParticleSystemEmissionState orgEmissionState = state.emissionState;
+ ParticleSystemEmissionState emissionState;
+
+ // Simulate emission
+ float timeStep = GetTimeStep(deltaTime, fixedTimeStep);
+ DebugAssert(timeStep > 0.0001f);
+
+ // @TODO: Maybe we can go back to just picking the conservative solution, since no longer use this for scrubbing/prewarm we shouldn't allocate that much.
+
+
+ int existingParticleCount = shuriken.GetParticleCount();
+ int totalPossibleEmits = 0;
+ float acc = deltaTime;
+ float fromT = state.t;
+ while(acc >= timeStep)
+ {
+ acc -= timeStep;
+ float toT = fromT + (deltaTime - acc);
+ float dt;
+ const float length = shuriken.m_ReadOnlyState->lengthInSec;
+ if(shuriken.m_ReadOnlyState->looping)
+ {
+ toT = fmodf (toT, length);
+ dt = timeStep;
+ }
+ else
+ {
+ toT = min(toT, length);
+ dt = toT - fromT;
+ }
+
+ size_t numContinuous;
+ const Vector3f emitVelocity = state.emitterVelocity;
+ existingParticleCount += EmitFromModules(shuriken, *shuriken.m_ReadOnlyState, emissionState, numContinuous, emitVelocity, fromT, toT, dt);
+ totalPossibleEmits += existingParticleCount;
+ fromT = toT;
+ }
+
+ totalPossibleEmits += existingParticleCount;
+
+ // Restore emission state
+ state.emissionState = orgEmissionState;
+
+ const int kExtra = 5; // Give a few extra to avoid denying due to rounding etc.
+ return totalPossibleEmits + kExtra;
+}
+
+// @TODO: what about chained effects?
+float ParticleSystem::CalculateSubEmitterMaximumLifeTime(float parentLifeTime) const
+{
+ ParticleSystem* subEmitters[kParticleSystemMaxSubTotal];
+ m_SubModule->GetSubEmitterPtrs(subEmitters);
+
+ float maxLifeTime = 0.0f;
+ for(int i = 0; i < kParticleSystemMaxSubTotal; i++)
+ {
+ if (subEmitters[i] && (subEmitters[i] != this))
+ {
+ const float emitterMaximumLifetime = subEmitters[i]->m_InitialModule.GetLifeTimeCurve().FindMinMax().y;
+ maxLifeTime = max(maxLifeTime, parentLifeTime + emitterMaximumLifetime);
+ maxLifeTime = std::max(maxLifeTime, subEmitters[i]->CalculateSubEmitterMaximumLifeTime(parentLifeTime + emitterMaximumLifetime));
+ }
+ }
+ return maxLifeTime;
+}
+
+void ParticleSystem::SmartReset ()
+{
+ Super::SmartReset();
+ AddParticleSystemRenderer ();
+}
+
+void ParticleSystem::AddParticleSystemRenderer ()
+{
+ if (GameObject* go = GetGameObjectPtr ())
+ {
+ ParticleSystemRenderer* renderer = go->QueryComponent (ParticleSystemRenderer);
+ if (renderer == NULL)
+ {
+ string error;
+ AddComponent (*go, ClassID(ParticleSystemRenderer), NULL, &error);
+ if (error.empty ())
+ go->GetComponent (ParticleSystemRenderer).SetMaterial (GetDefaultParticleMaterial(), 0);
+ else
+ LogString (Format("%s", error.c_str()));
+ }
+ }
+}
+
+
+void ParticleSystem::SetUsesRotationalSpeed()
+{
+ ParticleSystemParticles& ps0 = m_Particles[kParticleBuffer0];
+ if(!ps0.usesRotationalSpeed)
+ ps0.SetUsesRotationalSpeed ();
+#if UNITY_EDITOR
+ ParticleSystemParticles& ps1 = m_Particles[kParticleBuffer1];
+ if(!ps1.usesRotationalSpeed)
+ ps1.SetUsesRotationalSpeed ();
+#endif
+ ParticleSystemParticles& pss = m_ParticlesStaging;
+ if(!pss.usesRotationalSpeed)
+ pss.SetUsesRotationalSpeed ();
+}
+
+void ParticleSystem::SetUsesAxisOfRotation()
+{
+ ParticleSystemParticles& ps0 = m_Particles[kParticleBuffer0];
+ if(!ps0.usesAxisOfRotation)
+ ps0.SetUsesAxisOfRotation ();
+#if UNITY_EDITOR
+ ParticleSystemParticles& ps1 = m_Particles[kParticleBuffer1];
+ if(!ps1.usesAxisOfRotation)
+ ps1.SetUsesAxisOfRotation ();
+#endif
+ ParticleSystemParticles& pss = m_ParticlesStaging;
+ if(!pss.usesAxisOfRotation)
+ pss.SetUsesAxisOfRotation ();
+}
+
+void ParticleSystem::SetUsesEmitAccumulator(int numAccumulators)
+{
+ m_Particles[kParticleBuffer0].SetUsesEmitAccumulator (numAccumulators);
+#if UNITY_EDITOR
+ m_Particles[kParticleBuffer1].SetUsesEmitAccumulator (numAccumulators);
+#endif
+ m_ParticlesStaging.SetUsesEmitAccumulator (numAccumulators);
+}
+
+bool ParticleSystem::GetIsDistanceEmitter() const
+{
+ return (EmissionModule::kEmissionTypeDistance == m_EmissionModule.GetEmissionDataRef().type);
+}
+
+// check if the system would like to use any raycasting in this frame
+bool ParticleSystem::SystemWannaRayCast(const ParticleSystem& system)
+{
+ return system.IsActive() && system.m_CollisionModule && system.m_CollisionModule->GetEnabled() && system.m_CollisionModule->IsWorldCollision() && system.m_RayBudgetState.ReceiveRays();
+}
+
+// check if the system will actually use any raycasting in this frame
+bool ParticleSystem::SystemWillRayCast(const ParticleSystem& system)
+{
+ return system.IsActive() && system.m_CollisionModule && system.m_CollisionModule->GetEnabled() && system.m_CollisionModule->IsWorldCollision() && (system.GetRayBudget() > 0);
+}
+
+// dole out ray budgets to each system that will do raycasting
+void ParticleSystem::AssignRayBudgets()
+{
+ int activeCount = gParticleSystemManager.activeEmitters.size();
+
+ // count the jobs and update quality setting
+ int numApproximateWorldCollisionJobs = 0;
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ system.m_RayBudgetState.SetQuality( system.m_CollisionModule->GetQuality() );
+ system.SetRayBudget( 0 );
+ if ( SystemWannaRayCast( system ) )
+ {
+ if ( system.m_CollisionModule->IsApproximate() )
+ {
+ numApproximateWorldCollisionJobs++;
+ }
+ else
+ {
+ // high quality always get to trace all rays!
+ system.SetRayBudget( (int)system.GetParticleCount() );
+ }
+ }
+ }
+ if (numApproximateWorldCollisionJobs<1) return;
+
+ // assign ray budget to particle systems
+ int totalBudget = GetQualitySettings().GetCurrent().particleRaycastBudget;
+ int raysPerSystem = std::max( 0, totalBudget / numApproximateWorldCollisionJobs );
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if ( SystemWannaRayCast( system ) && system.m_CollisionModule->IsApproximate() )
+ {
+ const int rays = std::min((int)system.GetParticleCount(),raysPerSystem);
+ system.SetRayBudget( rays );
+ totalBudget = std::max( totalBudget-rays, 0);
+ }
+ }
+
+ // assign any remaining rays
+ // TODO: possibly better to sort and go through the list incrementally updating the per system budget, than doing this hacky two pass thing
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if ( SystemWannaRayCast( system ) && system.m_CollisionModule->IsApproximate() )
+ {
+ const int rays = std::min(totalBudget,(int)system.GetParticleCount()-system.GetRayBudget());
+ system.SetRayBudget( system.GetRayBudget()+rays );
+ totalBudget -= rays;
+ }
+ system.m_RayBudgetState.Update();
+ }
+}
+
+void ParticleSystem::BeginUpdateAll ()
+{
+ const float deltaTimeEpsilon = 0.0001f;
+ float deltaTime = GetDeltaTime();
+ if(deltaTime < deltaTimeEpsilon)
+ return;
+
+ PROFILER_AUTO(gParticleSystemProfile, NULL)
+
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+
+ if (!system.IsActive ())
+ {
+ AssertStringObject( "UpdateParticle system should not happen on disabled vGO", &system);
+ system.RemoveFromManager();
+ continue;
+ }
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ system.m_State->recordSubEmits = true;
+#else
+ system.m_State->recordSubEmits = false;
+#endif
+ Update0 (system, *system.m_ReadOnlyState, *system.m_State, deltaTime, false);
+ }
+
+ gParticleSystemManager.needSync = true;
+
+ // make sure ray budgets are assigned for the frame
+ ParticleSystem::AssignRayBudgets();
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ JobScheduler& scheduler = GetJobScheduler();
+ int activeCount = gParticleSystemManager.activeEmitters.size();
+
+ // count the jobs
+ int numActiveWorldCollisionJobs = 0;
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if ( system.GetRayBudget() > 0 )
+ numActiveWorldCollisionJobs++;
+ }
+
+ // add collision jobs
+ gParticleSystemManager.worldCollisionJobGroup = scheduler.BeginGroup(numActiveWorldCollisionJobs);
+ gParticleSystemManager.jobGroup = scheduler.BeginGroup(activeCount-numActiveWorldCollisionJobs);
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ system.GetThreadScratchPad().deltaTime = deltaTime;
+ if ( system.GetRayBudget() > 0 )
+ scheduler.SubmitJob (gParticleSystemManager.worldCollisionJobGroup, ParticleSystem::UpdateFunction, &system, NULL);
+ else
+ scheduler.SubmitJob (gParticleSystemManager.jobGroup, ParticleSystem::UpdateFunction, &system, NULL);
+ }
+ scheduler.WaitForGroup(gParticleSystemManager.worldCollisionJobGroup);
+#else
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); i++)
+ {
+ //printf_console("BeginUpdateAll [%d]:\n",i);
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ system.Update1 (system, system.GetParticles((int)ParticleSystem::kParticleBuffer0), deltaTime, false, false);
+ }
+#endif
+
+}
+
+void ParticleSystem::EndUpdateAll ()
+{
+ SyncJobs();
+
+ // messages
+ for (int i = 0; i < gParticleSystemManager.activeEmitters.size(); ++i)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if (!system.IsActive ())
+ continue;
+ if (!system.m_CollisionModule->GetUsesCollisionMessages ())
+ continue;
+ ParticleSystemParticles& ps = system.GetParticles((int)ParticleSystem::kParticleBuffer0);
+ ps.collisionEvents.SwapCollisionEventArrays ();
+ ps.collisionEvents.SendCollisionEvents (system);
+ }
+
+ // Remove emitters that are finished (no longer emitting)
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size();)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ ParticleSystemState& state = *system.m_State;
+ const size_t particleCount = system.GetParticleCount();
+ if ((particleCount == 0) && state.playing && state.stopEmitting)
+ {
+ // collision subemitters may not have needRestart==true when being restarted
+ // from a paused state
+ //Assert (state.needRestart);
+ state.playing = false;
+ system.RemoveFromManager();
+ continue;
+ }
+
+ i++;
+ }
+
+ // Interpolate in the editor for very low preview speeds
+#if UNITY_EDITOR
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); i++)
+ ParticleSystemEditor::PerformInterpolationStep(gParticleSystemManager.activeEmitters[i]);
+#endif
+}
+
+void ParticleSystem::StartParticles(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset)
+{
+ if (amountOfParticlesToEmit <= 0)
+ return;
+
+ const ParticleSystemReadOnlyState& roState = *system.m_ReadOnlyState;
+ ParticleSystemState& state = *system.m_State;
+ size_t fromIndex = system.AddNewParticles(ps, amountOfParticlesToEmit);
+ const Matrix4x4f localToWorld = !roState.useLocalSpace ? state.localToWorld : Matrix4x4f::identity;
+ StartModules (system, roState, state, state.emissionState, state.emitterVelocity, localToWorld, ps, fromIndex, dt, t, numContinuous, frameOffset);
+}
+
+void ParticleSystem::StartParticlesProcedural(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset)
+{
+ DebugAssert(CheckSupportsProcedural(system));
+
+ ParticleSystemState& state = *system.m_State;
+
+ int numParticlesRecorded = 0;
+ for (int i=0;i<state.emitReplay.size();i++)
+ numParticlesRecorded += state.emitReplay[i].particlesToEmit;
+
+ float emissionOffset = state.emissionState.m_ToEmitAccumulator;
+ float emissionGap = state.emissionState.m_ParticleSpacing * dt;
+ amountOfParticlesToEmit = system.LimitParticleCount(numParticlesRecorded + amountOfParticlesToEmit) - numParticlesRecorded;
+
+ if (amountOfParticlesToEmit > 0)
+ {
+ UInt32 randomSeed = 0;
+#if UNITY_EDITOR
+ ParticleSystemEditor::UpdateRandomSeed(system);
+ randomSeed = ParticleSystemEditor::GetRandomSeed(system);
+#endif
+ state.emitReplay.push_back(ParticleSystemEmitReplay(t, amountOfParticlesToEmit, emissionOffset, emissionGap, numContinuous, randomSeed));
+ }
+}
+
+void ParticleSystem::StartModules (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, const ParticleSystemEmissionState& emissionState, Vector3f initialVelocity, const Matrix4x4f& matrix, ParticleSystemParticles& ps, size_t fromIndex, float dt, float t, size_t numContinuous, float frameOffset)
+{
+#if UNITY_EDITOR
+ ParticleSystemEditor::UpdateRandomSeed(system);
+ ParticleSystemEditor::ApplyRandomSeed(system, ParticleSystemEditor::GetRandomSeed(system));
+#endif
+
+ system.m_InitialModule.Start (roState, state, ps, matrix, fromIndex, t);
+ if(system.m_ShapeModule.GetEnabled())
+ system.m_ShapeModule.Start (roState, state, ps, matrix, fromIndex, t);
+
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ size_t count = ps.array_size();
+ const Vector3f velocityOffset = system.m_InitialModule.GetInheritVelocity() * initialVelocity;
+ for(size_t q = fromIndex; q < count; q++)
+ {
+ const float randomValue = GenerateRandom(ps.randomSeed[q] + kParticleSystemStartSpeedCurveId);
+ ps.velocity[q] *= Evaluate (system.m_InitialModule.GetSpeedCurve(), normalizedT, randomValue);
+ ps.velocity[q] += velocityOffset;
+ }
+
+ for(size_t q = fromIndex; q < count; ) // array size changes
+ {
+ // subFrameOffset allows particles to be spawned at increasing times, thus spacing particles within a single frame.
+ // For example if you spawn particles with high velocity you will get a continous streaming instead of a clump of particles.
+ const int particleIndex = q - fromIndex;
+ float subFrameOffset = (particleIndex < numContinuous) ? (float(particleIndex) + emissionState.m_ToEmitAccumulator) * emissionState.m_ParticleSpacing : 0.0f;
+ DebugAssert(subFrameOffset >= -0.01f);
+ DebugAssert(subFrameOffset <= 1.5f); // Not 1 due to possibly really bad precision
+ subFrameOffset = clamp01(subFrameOffset);
+
+ // Update from curves and apply forces etc.
+ UpdateModulesPreSimulationIncremental (system, roState, state, ps, q, q+1, subFrameOffset * dt);
+
+ // Position change due to where the emitter was at time of emission
+ ps.position[q] -= initialVelocity * (frameOffset + subFrameOffset) * dt;
+
+ // Position, rotation and energy change due to how much the particle has travelled since time of emission
+ // @TODO: Call Simulate instead?
+ ps.lifetime[q] -= subFrameOffset * dt;
+ if((ps.lifetime[q] < 0.0f) && (count > 0))
+ {
+ KillParticle(roState, state, ps, q, count);
+ continue;
+ }
+
+ ps.position[q] += (ps.velocity[q] + ps.animatedVelocity[q]) * subFrameOffset * dt;
+
+ if(ps.usesRotationalSpeed)
+ ps.rotation[q] += ps.rotationalSpeed[q] * subFrameOffset * dt;
+
+ if(system.m_SubModule->GetEnabled())
+ system.m_SubModule->Update (roState, state, ps, q, q+1, subFrameOffset * dt);
+
+ ++q;
+ }
+ ps.array_resize(count);
+}
+
+void ParticleSystem::UpdateModulesPreSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& particles, const size_t fromIndex, const size_t toIndex, float dt)
+{
+ const size_t count = particles.array_size();
+ system.m_InitialModule.Update (roState, state, particles, fromIndex, toIndex, dt);
+ if(system.m_RotationModule->GetEnabled())
+ system.m_RotationModule->Update (roState, state, particles, fromIndex, toIndex);
+ if(system.m_VelocityModule->GetEnabled())
+ system.m_VelocityModule->Update (roState, state, particles, fromIndex, toIndex);
+ if(system.m_ForceModule->GetEnabled())
+ system.m_ForceModule->Update (roState, state, particles, fromIndex, toIndex, dt);
+ if(system.m_ExternalForcesModule->GetEnabled())
+ system.m_ExternalForcesModule->Update (roState, state, particles, fromIndex, toIndex, dt);
+ if(system.m_ClampVelocityModule->GetEnabled())
+ system.m_ClampVelocityModule->Update (roState, state, particles, fromIndex, toIndex);
+ if(system.m_RotationBySpeedModule->GetEnabled())
+ system.m_RotationBySpeedModule->Update (roState, state, particles, fromIndex, toIndex);
+
+ Assert(count >= toIndex);
+ Assert(particles.array_size() == count);
+}
+
+void ParticleSystem::UpdateModulesPostSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& particles, const size_t fromIndex, float dt)
+{
+ const size_t count = particles.array_size();
+ if(system.m_SubModule->GetEnabled())
+ system.m_SubModule->Update (roState, state, particles, fromIndex, particles.array_size(), dt);
+ Assert(count == particles.array_size());
+
+ if(system.m_CollisionModule->GetEnabled())
+ {
+#if !ENABLE_MULTITHREADED_PARTICLES
+ PROFILER_AUTO(gParticleSystemUpdateCollisions, NULL)
+#endif
+ system.m_CollisionModule->Update (roState, state, particles, fromIndex, dt);
+ }
+}
+
+void ParticleSystem::UpdateModulesNonIncremental (const ParticleSystem& system, const ParticleSystemParticles& particles, ParticleSystemParticlesTempData& psTemp, size_t fromIndex, size_t toIndex)
+{
+ Assert(particles.array_size() == psTemp.particleCount);
+
+ for(int i = fromIndex; i < toIndex; i++)
+ psTemp.color[i] = particles.color[i];
+ for(int i = fromIndex; i < toIndex; i++)
+ psTemp.size[i] = particles.size[i];
+
+ if(system.m_ColorModule->GetEnabled())
+ system.m_ColorModule->Update (particles, psTemp.color, fromIndex, toIndex);
+ if(system.m_ColorBySpeedModule->GetEnabled())
+ system.m_ColorBySpeedModule->Update (particles, psTemp.color, fromIndex, toIndex);
+ if(system.m_SizeModule->GetEnabled())
+ system.m_SizeModule->Update (particles, psTemp.size, fromIndex, toIndex);
+ if(system.m_SizeBySpeedModule->GetEnabled())
+ system.m_SizeBySpeedModule->Update (particles, psTemp.size, fromIndex, toIndex);
+
+ if (gGraphicsCaps.needsToSwizzleVertexColors)
+ std::transform(&psTemp.color[fromIndex], &psTemp.color[toIndex], &psTemp.color[fromIndex], SwizzleColorForPlatform);
+
+ if(system.m_UVModule->GetEnabled())
+ {
+ // No other systems used sheet index yet, allocate!
+ if(!psTemp.sheetIndex)
+ {
+ psTemp.sheetIndex = ALLOC_TEMP_MANUAL(float, psTemp.particleCount);
+ for(int i = 0; i < fromIndex; i++)
+ psTemp.sheetIndex[i] = 0.0f;
+ }
+ system.m_UVModule->Update (particles, psTemp.sheetIndex, fromIndex, toIndex);
+ }
+ else if(psTemp.sheetIndex) // if this is present with disabled module, that means we have a combined buffer with one system not using UV module, just initislize to 0.0f
+ for(int i = fromIndex; i < toIndex; i++)
+ psTemp.sheetIndex[i] = 0.0f;
+}
+
+void ParticleSystem::UpdateModulesIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& particles, size_t fromIndex, float dt)
+{
+ UpdateModulesPreSimulationIncremental (system, roState, state, particles, fromIndex, particles.array_size(), dt);
+ SimulateParticles(roState, state, particles, fromIndex, dt);
+ UpdateModulesPostSimulationIncremental (system, roState, state, particles, fromIndex, dt);
+}
+
+void ParticleSystem::Update0 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, float dt, bool fixedTimeStep)
+{
+ const Transform& transform = system.GetComponent (Transform);
+ Vector3f oldPosition = state.localToWorld.GetPosition();
+ state.localToWorld = transform.GetLocalToWorldMatrixNoScale ();
+ Matrix4x4f::Invert_General3D(state.localToWorld, state.worldToLocal);
+
+ if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_1_a1))
+ state.emitterScale = transform.GetWorldScaleLossy();
+ else
+ state.emitterScale = transform.GetLocalScale();
+
+ if (state.playing && (dt > 0.0001f))
+ {
+ const Vector3f position = transform.GetPosition();
+
+ if (roState.useLocalSpace)
+ state.emitterVelocity = Vector3f::zero;
+ else
+ state.emitterVelocity = (position - oldPosition) / dt;
+ }
+
+ AddStagingBuffer(system);
+
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(renderer && !renderer->GetScreenSpaceRotation())
+ ParticleSystemRenderer::SetUsesAxisOfRotationRec(system, true);
+
+ if(system.m_RotationModule->GetEnabled() || system.m_RotationBySpeedModule->GetEnabled())
+ system.SetUsesRotationalSpeed();
+
+ int subEmitterBirthTypeCount = system.m_SubModule->GetSubEmitterTypeCount(kParticleSystemSubTypeBirth);
+ if(system.m_SubModule->GetEnabled() && subEmitterBirthTypeCount)
+ system.SetUsesEmitAccumulator (subEmitterBirthTypeCount);
+
+ int subEmitterCount = SetupSubEmitters(system, *system.m_State);
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ if(state.recordSubEmits)
+ {
+ const int numCommands = min(ParticleSystem::CalculateMaximumSubEmitterEmitCount(system, *system.m_State, dt, fixedTimeStep) * subEmitterCount, MAX_NUM_SUB_EMIT_CMDS);
+ ParticleSystemSubEmitCmdBuffer& buffer = system.m_State->subEmitterCommandBuffer;
+ Assert(NULL == buffer.commands);
+ buffer.commands = ALLOC_TEMP_MANUAL(SubEmitterEmitCommand, numCommands);
+ buffer.commandCount = 0;
+ buffer.maxCommandCount = numCommands;
+ }
+#endif
+
+ if(system.m_CollisionModule->GetEnabled())
+ system.m_CollisionModule->AllocateAndCache(roState, state);
+ if(system.m_ExternalForcesModule->GetEnabled())
+ system.m_ExternalForcesModule->AllocateAndCache(roState, state);
+}
+
+void ParticleSystem::Update1 (ParticleSystem& system, ParticleSystemParticles& ps, float dt, bool fixedTimeStep, bool useProcedural, int rayBudget)
+{
+ PROFILER_AUTO(gParticleSystemJobProfile, NULL)
+
+ const ParticleSystemReadOnlyState& roState = *system.m_ReadOnlyState;
+ ParticleSystemState& state = *system.m_State;
+ state.rayBudget = rayBudget;
+
+ // Exposed through script
+ dt *= std::max<float> (roState.speed, 0.0f);
+
+ float timeStep = GetTimeStep(dt, fixedTimeStep);
+ if(timeStep < 0.00001f)
+ return;
+
+ if (state.playing)
+ {
+ state.accumulatedDt += dt;
+
+ if(system.GetIsDistanceEmitter())
+ {
+ float t = state.t + state.accumulatedDt;
+ const float length = roState.lengthInSec;
+ t = roState.looping ? fmodf(t, length) : min(t, length);
+ size_t numContinuous = 0;
+ size_t amountOfParticlesToEmit = system.EmitFromModules (system, roState, state.emissionState, numContinuous, state.emitterVelocity, state.t, t, dt);
+ StartParticles(system, ps, state.t, t, dt, numContinuous, amountOfParticlesToEmit, 0.0f);
+ }
+
+ Update1Incremental(system, roState, state, ps, 0, timeStep, useProcedural);
+
+ if (useProcedural)
+ UpdateProcedural(system, roState, state, ps);
+ }
+
+ UpdateBounds(system, ps, state);
+}
+
+void ParticleSystem::Update2 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, bool fixedTimeStep)
+{
+ if(state.subEmitterCommandBuffer.commandCount > 0)
+ PlaybackSubEmitterCommandBuffer(system, state, fixedTimeStep);
+ state.ClearSubEmitterCommandBuffer();
+
+ CollisionModule::FreeCache(state);
+ ExternalForcesModule::FreeCache(state);
+
+ AddStagingBuffer(system);
+
+ //
+ // Update renderer
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if (renderer)
+ {
+ MinMaxAABB result;
+ ParticleSystemRenderer::CombineBoundsRec(system, result, true);
+ renderer->Update (result);
+ }
+}
+
+// Returns true if update loop is executed at least once
+void ParticleSystem::Update1Incremental(ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt, bool useProcedural)
+{
+ ApplyStartDelay (state.delayT, state.accumulatedDt);
+
+ int numTimeSteps = 0;
+ const int numTimeStepsTotal = int(state.accumulatedDt / dt);
+
+ while (state.accumulatedDt >= dt)
+ {
+ const float prevT = state.t;
+ state.Tick (roState, dt);
+ const float t = state.t;
+ const bool timePassedDuration = t >= (roState.lengthInSec);
+ const float frameOffset = float(numTimeStepsTotal - 1 - numTimeSteps);
+
+ if(!roState.looping && timePassedDuration)
+ system.Stop();
+
+ // Update simulation
+ if (!useProcedural)
+ UpdateModulesIncremental(system, roState, state, ps, fromIndex, dt);
+ else
+ for (int i=0;i<state.emitReplay.size();i++)
+ state.emitReplay[i].aliveTime += dt;
+
+ // Emission
+ bool emit = !system.GetIsDistanceEmitter() && !state.stopEmitting;
+ if(emit)
+ {
+ size_t numContinuous = 0;
+ size_t amountOfParticlesToEmit = system.EmitFromModules (system, roState, state.emissionState, numContinuous, state.emitterVelocity, prevT, t, dt);
+ if(useProcedural)
+ StartParticlesProcedural(system, ps, prevT, t, dt, numContinuous, amountOfParticlesToEmit, frameOffset);
+ else
+ StartParticles(system, ps, prevT, t, dt, numContinuous, amountOfParticlesToEmit, frameOffset);
+ }
+
+ state.accumulatedDt -= dt;
+
+ AddStagingBuffer(system);
+
+ // Workaround for external forces being dependent on AABB (need to update it before the next time step)
+ if(!useProcedural && (state.accumulatedDt >= dt) && system.m_ExternalForcesModule->GetEnabled())
+ UpdateBounds(system, ps, state);
+
+ numTimeSteps++;
+ }
+}
+
+void ParticleSystem::UpdateProcedural (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ DebugAssert(CheckSupportsProcedural(system));
+
+ // Clear all particles
+ ps.array_resize(0);
+
+ const Matrix4x4f localToWorld = !roState.useLocalSpace ? state.localToWorld : Matrix4x4f::identity;
+
+ // Emit all particles
+ for (int i=0; i<state.emitReplay.size(); i++)
+ {
+ const ParticleSystemEmitReplay& emit = state.emitReplay[i];
+
+#if UNITY_EDITOR
+ ParticleSystemEditor::ApplyRandomSeed(system, emit.randomSeed);
+#endif
+ //@TODO: remove passing m_State since that is very dangerous when making things procedural compatible
+ size_t previousParticleCount = ps.array_size();
+ system.m_InitialModule.GenerateProcedural (roState, state, ps, emit);
+
+ //@TODO: This can be moved out of the emit all particles loop...
+ if (system.m_ShapeModule.GetEnabled())
+ system.m_ShapeModule.Start (roState, state, ps, localToWorld, previousParticleCount, emit.t);
+
+ // Apply gravity & integrated velocity after shape module so that it picks up any changes done in shapemodule (for example rotating the velocity)
+ Vector3f gravity = system.m_InitialModule.GetGravity(roState, state);
+ float particleIndex = 0.0f;
+ const size_t particleCount = ps.array_size();
+ for (int q = previousParticleCount; q < particleCount; q++)
+ {
+ const float normalizedT = emit.t / roState.lengthInSec;
+ ps.velocity[q] *= Evaluate (system.m_InitialModule.GetSpeedCurve(), normalizedT, GenerateRandom(ps.randomSeed[q] + kParticleSystemStartSpeedCurveId));
+ Vector3f velocity = ps.velocity[q];
+ float frameOffset = (particleIndex + emit.emissionOffset) * emit.emissionGap * float(particleIndex < emit.numContinuous);
+ float aliveTime = emit.aliveTime + frameOffset;
+
+ ps.position[q] += velocity * aliveTime + gravity * aliveTime * aliveTime * 0.5F;
+ ps.velocity[q] += gravity * aliveTime;
+
+ particleIndex += 1.0f;
+ }
+
+ // If no particles were emitted we can get rid of the emit replay state...
+ if (previousParticleCount == ps.array_size())
+ {
+ state.emitReplay[i] = state.emitReplay.back();
+ state.emitReplay.pop_back();
+ i--;
+ }
+ }
+
+ if (system.m_RotationModule->GetEnabled())
+ system.m_RotationModule->UpdateProcedural (state, ps);
+
+ if (system.m_VelocityModule->GetEnabled())
+ system.m_VelocityModule->UpdateProcedural (roState, state, ps);
+
+ if (system.m_ForceModule->GetEnabled())
+ system.m_ForceModule->UpdateProcedural (roState, state, ps);
+
+ // Modules that are not supported by procedural
+ DebugAssert(!system.m_RotationBySpeedModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ClampVelocityModule->GetEnabled()); // unsupported: (Need to compute velocity by deriving position curves...), possible to support?
+ DebugAssert(!system.m_CollisionModule->GetEnabled());
+ DebugAssert(!system.m_SubModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ExternalForcesModule->GetEnabled());
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// // Editor only
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if UNITY_EDITOR
+void ParticleSystem::TransformChanged()
+{
+ if(!IsWorldPlaying() && ParticleSystemEditor::GetResimulation())
+ {
+ const Transform& transform = GetComponent (Transform);
+ Matrix4x4f newMatrix = transform.GetLocalToWorldMatrixNoScale ();
+ newMatrix.SetPosition(Vector3f::zero);
+ Matrix4x4f oldMatrix = m_State->localToWorld;
+ oldMatrix.SetPosition(Vector3f::zero);
+ bool rotationChanged = !CompareApproximately(oldMatrix, newMatrix);
+ if(m_ReadOnlyState->useLocalSpace || rotationChanged)
+ ParticleSystemEditor::PerformCompleteResimulation(this);
+ }
+}
+#endif
+
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystem.h b/Runtime/Graphics/ParticleSystem/ParticleSystem.h
new file mode 100644
index 0000000..10c8f2a
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystem.h
@@ -0,0 +1,298 @@
+#ifndef SHURIKEN_H
+#define SHURIKEN_H
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "ParticleSystemParticle.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Modules/InitialModule.h"
+#include "Modules/ShapeModule.h"
+#include "Modules/EmissionModule.h"
+
+struct ParticleSystemEmitReplay;
+struct ParticleSystemState;
+class ParticleSystemModule;
+class SizeModule;
+class RotationModule;
+class ColorModule;
+class UVModule;
+class VelocityModule;
+class ForceModule;
+class ExternalForcesModule;
+class ClampVelocityModule;
+class SizeBySpeedModule;
+class RotationBySpeedModule;
+class ColorBySpeedModule;
+class CollisionModule;
+class SubModule;
+
+enum ParticleSystemSimulationSpace {
+ kSimLocal = 0,
+ kSimWorld = 1,
+};
+
+// TODO: rename
+struct ParticleSystemThreadScratchPad
+{
+ ParticleSystemThreadScratchPad ()
+ : deltaTime (1.0f)
+ {}
+
+ float deltaTime;
+};
+
+enum { kGoodQualityDelay = 0, kMediumQualityDelay = 0, kLowQualityDelay = 4 };
+struct RayBudgetState
+{
+ void SetQuality(int quality)
+ {
+ if ( m_Quality != quality )
+ {
+ switch (quality)
+ {
+ case 0:
+ m_QualityFrameDelay = kGoodQualityDelay;
+ break;
+ case 1:
+ m_QualityFrameDelay = kMediumQualityDelay;
+ break;
+ case 2:
+ m_QualityFrameDelay = kLowQualityDelay;
+ break;
+ default:
+ m_QualityFrameDelay = kGoodQualityDelay;
+ }
+ m_FramesRemaining = m_QualityFrameDelay;
+ m_Quality = quality;
+ };
+ }
+ RayBudgetState() { m_Quality=m_QualityFrameDelay=m_FramesRemaining=0; }
+ bool ReceiveRays() const { return m_FramesRemaining==0; }
+ void Update() { m_FramesRemaining = ( m_FramesRemaining ? m_FramesRemaining-1 : m_QualityFrameDelay ); }
+ int m_Quality;
+ int m_QualityFrameDelay;
+ int m_FramesRemaining;
+};
+
+class ParticleSystem : public Unity::Component
+{
+public:
+ REGISTER_DERIVED_CLASS (ParticleSystem, Unity::Component)
+ DECLARE_OBJECT_SERIALIZE (ParticleSystem)
+
+ enum
+ {
+ kParticleBuffer0,
+#if UNITY_EDITOR // Double buffered + interpolation
+ kParticleBuffer1,
+#endif
+ kNumParticleBuffers,
+ };
+
+ ParticleSystem (MemLabelId label, ObjectCreationMode mode);
+
+ // ParticleSystem (); declared-by-macro
+
+ void SmartReset ();
+ void AddParticleSystemRenderer ();
+
+ void Deactivate (DeactivateOperation operation);
+ void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ #if UNITY_EDITOR
+ // ParticleSystemRenderer always goes with ParticleSystem
+ virtual int GetCoupledComponentClassID() const { return ClassID(ParticleSystemRenderer); }
+ #endif
+
+
+ static void* UpdateFunction (void* rawData);
+
+ static void BeginUpdateAll();
+ static void EndUpdateAll();
+ static void Update(ParticleSystem& system, float deltaTime, bool fixedTimeStep, bool useProcedural, int rayBudget = 0);
+
+ static bool SystemWannaRayCast(const ParticleSystem& system);
+ static bool SystemWillRayCast(const ParticleSystem& system);
+ static void AssignRayBudgets();
+
+ static void SyncJobs();
+
+ static void Emit(ParticleSystem& system, const SubEmitterEmitCommand& command, ParticleSystemEmitMode emitMode);
+
+ // Client interface
+ void Simulate (float t, bool restart); // Fastforwards the particle system by simulating particles over given period of time, then pauses it.
+ void Play (bool autoPrewarm = true);
+ void Stop ();
+ void Pause ();
+ void Emit (int count);
+ void EmitParticleExternal(ParticleSystemParticle* particle);
+ void Clear (bool updateBounds = true);
+ void AutoPrewarm();
+
+ bool IsAlive () const;
+ bool IsPlaying () const;
+ bool IsPaused () const;
+ bool IsStopped () const;
+ float GetStartDelay() const;
+ void SetStartDelay(float value);
+ bool GetLoop () const;
+ void SetLoop (bool loop);
+ bool GetPlayOnAwake () const;
+ void SetPlayOnAwake (bool playOnAwake);
+ ParticleSystemSimulationSpace GetSimulationSpace () const;
+ void SetSimulationSpace (ParticleSystemSimulationSpace simulationSpace);
+ float GetSecPosition () const;
+ void SetSecPosition (float pos);
+ float GetLengthInSec () const;
+ void SetPlaybackSpeed (float speed);
+ float GetPlaybackSpeed () const;
+ void SetRayBudget (int rayBudget);
+ int GetRayBudget() const;
+
+ bool GetEnableEmission() const;
+ void SetEnableEmission(bool value);
+ float GetEmissionRate() const;
+ void SetEmissionRate(float value);
+ float GetStartSpeed() const;
+ void SetStartSpeed(float value);
+ float GetStartSize() const;
+ void SetStartSize(float value);
+ ColorRGBAf GetStartColor() const;
+ void SetStartColor(ColorRGBAf value);
+ float GetStartRotation() const;
+ void SetStartRotation(float value);
+ float GetStartLifeTime() const;
+ void SetStartLifeTime(float value);
+ float GetGravityModifier() const;
+ void SetGravityModifier(float value);
+ UInt32 GetRandomSeed() const;
+ void SetRandomSeed(UInt32 value);
+ int GetMaxNumParticles() const;
+ void SetMaxNumParticles(int value);
+
+ ShapeModule& GetShapeModule () { return m_ShapeModule; }
+
+
+ Matrix4x4f GetLocalToWorldMatrix() const;
+
+ void GetNumTiles(int& uvTilesX, int& uvTilesY) const;
+
+ void AllocateAllStructuresOfArrays();
+ void SetParticlesExternal (ParticleSystemParticle* particles, int size);
+ void GetParticlesExternal (ParticleSystemParticle* particles, int size);
+
+ int GetSafeCollisionEventSize () const;
+ int GetCollisionEventsExternal (int instanceID, MonoParticleCollisionEvent* collisionEvents, int size) const;
+
+ ParticleSystemParticles& GetParticles (int index = -1);
+ const ParticleSystemParticles& GetParticles (int index = -1) const;
+ size_t GetParticleCount () const;
+
+ ParticleSystemThreadScratchPad& GetThreadScratchPad () { return m_ThreadScratchpad; }
+
+ static void InitializeClass ();
+ static void CleanupClass () {};
+
+ void DidModifyMesh ();
+ void DidDeleteMesh ();
+
+#if UNITY_EDITOR
+ void TransformChanged();
+#endif
+
+ void RendererBecameVisible();
+ void RendererBecameInvisible();
+
+ static size_t EmitFromData (ParticleSystemEmissionState& emissionState, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length);
+private:
+ static void Update0 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, float dt, bool fixedTimeStep);
+ static void Update1 (ParticleSystem& system, ParticleSystemParticles& ps, float dt, bool fixedTimeStep, bool useProcedural, int rayBudget = 0);
+ static void Update2 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, bool fixedTimeStep);
+ static void Update1Incremental(ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt, bool useProcedural);
+
+ static size_t EmitFromModules (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemEmissionState& emissionState, size_t& numContinuous, const Vector3f velocity, float fromT, float toT, float dt);
+ static void StartModules (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, const ParticleSystemEmissionState& emissionState, Vector3f initialVelocity, const Matrix4x4f& matrix, ParticleSystemParticles& ps, size_t fromIndex, float dt, float t, size_t numContinuous, float frameOffset);
+ static void StartParticles(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset);
+ static void StartParticlesProcedural(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset);
+ static void UpdateProcedural(ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps);
+ static void UpdateModulesPreSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt);
+ static void UpdateModulesPostSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, float dt);
+ static void UpdateModulesIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt);
+ static void UpdateModulesNonIncremental (const ParticleSystem& system, const ParticleSystemParticles& ps, ParticleSystemParticlesTempData& psTemp, size_t fromIndex, size_t toIndex);
+ static void SimulateParticles (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, float dt);
+ static void PlaybackSubEmitterCommandBuffer(const ParticleSystem& shuriken, ParticleSystemState& state, bool fixedTimeStep);
+ static void UpdateBounds(const ParticleSystem& system, const ParticleSystemParticles& ps, ParticleSystemState& state);
+
+ static void AddStagingBuffer(ParticleSystem& system);
+ static int CalculateMaximumSubEmitterEmitCount(ParticleSystem& shuriken, ParticleSystemState& state, float deltaTime, bool fixedTimeStep);
+ static int SetupSubEmitters(ParticleSystem& shuriken, ParticleSystemState& state);
+
+ static bool CheckSupportsProcedural(const ParticleSystem& system);
+ static bool DetermineSupportsProcedural(const ParticleSystem& system);
+
+ bool ComputePrewarmStartParameters(float& prewarmTime, float t);
+
+ void SetUsesAxisOfRotation();
+ void SetUsesEmitAccumulator(int numAccumulators);
+ void SetUsesRotationalSpeed();
+ void KeepUpdating();
+ void Cull();
+
+ size_t AddNewParticles(ParticleSystemParticles& particles, size_t newParticles) const;
+ size_t LimitParticleCount(size_t requestSize) const;
+ float CalculateSubEmitterMaximumLifeTime(float parentLifeTime) const;
+
+ bool GetIsDistanceEmitter() const;
+
+ void AddToManager();
+ void RemoveFromManager();
+
+ ParticleSystemParticles m_Particles[kNumParticleBuffers];
+ ParticleSystemParticles m_ParticlesStaging; // staging buffer for emitting into the emitter
+ ParticleSystemReadOnlyState* m_ReadOnlyState;
+ ParticleSystemState* m_State;
+ InitialModule m_InitialModule;
+ ShapeModule m_ShapeModule;
+ EmissionModule m_EmissionModule;
+
+ // Dependent on energy value
+ SizeModule* m_SizeModule;
+ RotationModule* m_RotationModule; // @TODO: Requires outputs angular velocity and thus requires integration (Inconsistent with other modules in this group)
+ ColorModule* m_ColorModule;
+ UVModule* m_UVModule;
+
+ // Dependent on energy value
+ VelocityModule* m_VelocityModule;
+ ForceModule* m_ForceModule;
+ ExternalForcesModule* m_ExternalForcesModule;
+
+ // Depends on velocity and modifies velocity
+ ClampVelocityModule* m_ClampVelocityModule;
+
+ // Dependent on velocity value
+ SizeBySpeedModule* m_SizeBySpeedModule;
+ RotationBySpeedModule* m_RotationBySpeedModule;
+ ColorBySpeedModule* m_ColorBySpeedModule;
+
+ // Dependent on a position and velocity
+ CollisionModule* m_CollisionModule;
+
+ SubModule* m_SubModule;
+
+ ParticleSystemThreadScratchPad m_ThreadScratchpad;
+
+ RayBudgetState m_RayBudgetState;
+ int m_RayBudget;
+
+private:
+ int m_EmittersIndex;
+
+#if UNITY_EDITOR
+public:
+ int m_EditorRandomSeedIndex;
+ ListNode<ParticleSystem> m_EditorListNode;
+ friend class ParticleSystemEditor;
+#endif
+ friend class ParticleSystemRenderer;
+};
+
+#endif // SHURIKEN_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h b/Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h
new file mode 100644
index 0000000..fd59987
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h
@@ -0,0 +1,48 @@
+#ifndef PARTICLESYSTEMCOMMON_H
+#define PARTICLESYSTEMCOMMON_H
+
+enum ParticleSystemSubType
+{
+ kParticleSystemSubTypeBirth,
+ kParticleSystemSubTypeCollision,
+ kParticleSystemSubTypeDeath,
+};
+
+enum ParticleSystemEmitMode
+{
+ kParticleSystemEMDirect,
+ kParticleSystemEMStaging,
+};
+
+enum
+{
+ kParticleSystemMaxSubBirth = 2,
+ kParticleSystemMaxSubCollision = 2,
+ kParticleSystemMaxSubDeath = 2,
+ kParticleSystemMaxSubTotal = kParticleSystemMaxSubBirth + kParticleSystemMaxSubCollision + kParticleSystemMaxSubDeath,
+};
+
+// Curve id's needed to offset randomness for curves, to avoid visible patterns due to only storing 1 random value per particle
+enum ParticleSystemRandomnessIds
+{
+ // Curves
+ kParticleSystemClampVelocityCurveId = 0x13371337,
+ kParticleSystemForceCurveId = 0x12460f3b,
+ kParticleSystemRotationCurveId = 0x6aed452e,
+ kParticleSystemRotationBySpeedCurveId = 0xdec4aea1,
+ kParticleSystemStartSpeedCurveId = 0x96aa4de3,
+ kParticleSystemSizeCurveId = 0x8d2c8431,
+ kParticleSystemSizeBySpeedCurveId = 0xf3857f6f,
+ kParticleSystemVelocityCurveId = 0xe0fbd834,
+ kParticleSystemUVCurveId = 0x13740583,
+
+ // Gradient
+ kParticleSystemColorGradientId = 0x591bc05c,
+ kParticleSystemColorByVelocityGradientId = 0x40eb95e4,
+
+ // Misc
+ kParticleSystemMeshSelectionId = 0xbc524e5f,
+ kParticleSystemUVRowSelectionId = 0xaf502044,
+};
+
+#endif // PARTICLESYSTEMCOMMON_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp
new file mode 100644
index 0000000..3ac445c
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp
@@ -0,0 +1,196 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemCurves.h"
+#include "Runtime/Math/Polynomials.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+// Calculates the min max range of an animation curve analytically
+static void CalculateCurveRangesValue(Vector2f& minMaxValue, const AnimationCurve& curve)
+{
+ const int keyCount = curve.GetKeyCount();
+ if (keyCount == 0)
+ return;
+
+ if (keyCount == 1)
+ {
+ CalculateMinMax(minMaxValue, curve.GetKey(0).value);
+ }
+ else
+ {
+ int segmentCount = keyCount - 1;
+ CalculateMinMax(minMaxValue, curve.GetKey(0).value);
+ for (int i = 0;i<segmentCount;i++)
+ {
+ DebugAssert(i+1 < keyCount);
+ AnimationCurve::Cache cache;
+ curve.CalculateCacheData(cache, i, i + 1, 0.0F);
+
+ // Differentiate polynomial
+ float a = 3.0f * cache.coeff[0];
+ float b = 2.0f * cache.coeff[1];
+ float c = 1.0f * cache.coeff[2];
+
+ const float start = curve.GetKey(i).time;
+ const float end = curve.GetKey(i+1).time;
+
+ float roots[2];
+ int numRoots = QuadraticPolynomialRootsGeneric(a, b, c, roots[0], roots[1]);
+ for(int r = 0; r < numRoots; r++)
+ if((roots[r] >= 0.0f) && ((roots[r] + start) < end))
+ CalculateMinMax(minMaxValue, Polynomial::EvalSegment(roots[r], cache.coeff));
+ CalculateMinMax(minMaxValue, Polynomial::EvalSegment(end-start, cache.coeff));
+ }
+ }
+}
+
+bool BuildCurves (MinMaxOptimizedPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState)
+{
+ bool isOptimizedCurve = polyCurves.max.BuildOptimizedCurve(editorCurves.max, scalar);
+ if ((minMaxState != kMMCTwoCurves) && (minMaxState != kMMCTwoConstants))
+ isOptimizedCurve = isOptimizedCurve && polyCurves.min.BuildOptimizedCurve(editorCurves.max, scalar);
+ else
+ isOptimizedCurve = isOptimizedCurve && polyCurves.min.BuildOptimizedCurve(editorCurves.min, scalar);
+ return isOptimizedCurve;
+}
+
+void BuildCurves (MinMaxPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState)
+{
+ polyCurves.max.BuildCurve(editorCurves.max, scalar);
+ if ((minMaxState != kMMCTwoCurves) && (minMaxState != kMMCTwoConstants))
+ polyCurves.min.BuildCurve(editorCurves.max, scalar);
+ else
+ polyCurves.min.BuildCurve(editorCurves.min, scalar);
+}
+
+bool CurvesSupportProcedural (const MinMaxAnimationCurves& editorCurves, short minMaxState)
+{
+ bool isValid = PolynomialCurve::IsValidCurve(editorCurves.max);
+ if ((minMaxState != kMMCTwoCurves) && (minMaxState != kMMCTwoConstants))
+ return isValid;
+ else
+ return isValid && PolynomialCurve::IsValidCurve(editorCurves.min);
+}
+
+
+void MinMaxOptimizedPolyCurves::Integrate ()
+{
+ max.Integrate ();
+ min.Integrate ();
+}
+
+void MinMaxOptimizedPolyCurves::DoubleIntegrate ()
+{
+ max.DoubleIntegrate ();
+ min.DoubleIntegrate ();
+}
+
+Vector2f MinMaxOptimizedPolyCurves::FindMinMaxIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxIntegrated();
+ Vector2f maxRange = max.FindMinMaxIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+Vector2f MinMaxOptimizedPolyCurves::FindMinMaxDoubleIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxDoubleIntegrated();
+ Vector2f maxRange = max.FindMinMaxDoubleIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+void MinMaxPolyCurves::Integrate ()
+{
+ max.Integrate ();
+ min.Integrate ();
+}
+
+void MinMaxPolyCurves::DoubleIntegrate ()
+{
+ max.DoubleIntegrate ();
+ min.DoubleIntegrate ();
+}
+
+Vector2f MinMaxPolyCurves::FindMinMaxIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxIntegrated();
+ Vector2f maxRange = max.FindMinMaxIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+Vector2f MinMaxPolyCurves::FindMinMaxDoubleIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxDoubleIntegrated();
+ Vector2f maxRange = max.FindMinMaxDoubleIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+MinMaxCurve::MinMaxCurve ()
+: scalar (1.0f)
+, minMaxState (kMMCScalar)
+, isOptimizedCurve(false)
+{
+ SetPolynomialCurveToValue (editorCurves.max, polyCurves.max, 1.0f);
+ SetPolynomialCurveToValue (editorCurves.min, polyCurves.min, 0.0f);
+}
+
+Vector2f MinMaxCurve::FindMinMax() const
+{
+ Vector2f result = Vector2f(std::numeric_limits<float>::infinity (), -std::numeric_limits<float>::infinity ());
+ CalculateCurveRangesValue(result, editorCurves.max);
+ if((minMaxState == kMMCTwoCurves) || (minMaxState == kMMCTwoConstants))
+ CalculateCurveRangesValue(result, editorCurves.min);
+ return result * GetScalar();
+}
+
+Vector2f MinMaxCurve::FindMinMaxIntegrated() const
+{
+ if(IsOptimized())
+ {
+ MinMaxOptimizedPolyCurves integrated = polyCurves;
+ integrated.Integrate();
+ return integrated.FindMinMaxIntegrated();
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (editorCurves, minMaxState));
+ MinMaxPolyCurves integrated;
+ BuildCurves(integrated, editorCurves, GetScalar(), minMaxState);
+ integrated.Integrate();
+ return integrated.FindMinMaxIntegrated();
+ }
+}
+
+Vector2f MinMaxCurve::FindMinMaxDoubleIntegrated() const
+{
+ if(IsOptimized())
+ {
+ MinMaxOptimizedPolyCurves doubleIntegrated = polyCurves;
+ doubleIntegrated.DoubleIntegrate();
+ return doubleIntegrated.FindMinMaxDoubleIntegrated();
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (editorCurves, minMaxState));
+ MinMaxPolyCurves doubleIntegrated;
+ BuildCurves(doubleIntegrated, editorCurves, GetScalar(), minMaxState);
+ doubleIntegrated.DoubleIntegrate();
+ return doubleIntegrated.FindMinMaxDoubleIntegrated();
+ }
+}
+
+template<class TransferFunction>
+void MinMaxCurve::Transfer (TransferFunction& transfer)
+{
+ transfer.Transfer (scalar, "scalar");
+ transfer.Transfer (editorCurves.max, "maxCurve");
+ transfer.Transfer (editorCurves.min, "minCurve");
+ TRANSFER (minMaxState); transfer.Align ();
+ if (transfer.IsReading ())
+ isOptimizedCurve = BuildCurves(polyCurves, editorCurves, scalar, minMaxState);
+}
+INSTANTIATE_TEMPLATE_TRANSFER(MinMaxCurve)
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h
new file mode 100644
index 0000000..5d59eb2
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h
@@ -0,0 +1,187 @@
+#ifndef SHURIKENCURVES_H
+#define SHURIKENCURVES_H
+
+#include "ParticleSystemParticle.h"
+#include "PolynomialCurve.h"
+#include "Runtime/Math/AnimationCurve.h"
+
+
+struct MinMaxCurve;
+
+// Some profile numbers from a run with 250,000 particles evaluating 3 velocity properties each on Intel i7-2600 CPU @ 3.4 GHz
+// Scalar: 4.6 ms
+// Optimized curve: 7.2 ms
+// Random between 2 scalars: 9.5 ms
+// Random between 2 curves: 9.5 ms
+// Non-optimized curve: 10.0 ms
+// Random between 2 non-optimized curves: 12.0 ms
+
+enum ParticleSystemCurveEvalMode
+{
+ kEMScalar,
+ kEMOptimized,
+ kEMOptimizedMinMax,
+ kEMSlow,
+};
+
+enum MinMaxCurveState
+{
+ kMMCScalar = 0,
+ kMMCCurve = 1,
+ kMMCTwoCurves = 2,
+ kMMCTwoConstants = 3
+};
+
+struct MinMaxOptimizedPolyCurves
+{
+ void Integrate();
+ void DoubleIntegrate();
+ Vector2f FindMinMaxIntegrated();
+ Vector2f FindMinMaxDoubleIntegrated();
+
+ OptimizedPolynomialCurve max;
+ OptimizedPolynomialCurve min;
+};
+
+inline float EvaluateIntegrated (const MinMaxOptimizedPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateIntegrated (t);
+ const float v1 = curves.max.EvaluateIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+inline float EvaluateDoubleIntegrated (const MinMaxOptimizedPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateDoubleIntegrated (t);
+ const float v1 = curves.max.EvaluateDoubleIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+struct MinMaxPolyCurves
+{
+ void Integrate();
+ void DoubleIntegrate();
+ Vector2f FindMinMaxIntegrated();
+ Vector2f FindMinMaxDoubleIntegrated();
+
+ PolynomialCurve max;
+ PolynomialCurve min;
+};
+
+inline float EvaluateIntegrated (const MinMaxPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateIntegrated (t);
+ const float v1 = curves.max.EvaluateIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+inline float EvaluateDoubleIntegrated (const MinMaxPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateDoubleIntegrated (t);
+ const float v1 = curves.max.EvaluateDoubleIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+struct MinMaxAnimationCurves
+{
+ bool SupportsProcedural ();
+
+ AnimationCurve max;
+ AnimationCurve min;
+};
+
+bool BuildCurves (MinMaxOptimizedPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState);
+void BuildCurves (MinMaxPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState);
+bool CurvesSupportProcedural (const MinMaxAnimationCurves& editorCurves, short minMaxState);
+
+struct MinMaxCurve
+{
+ MinMaxOptimizedPolyCurves polyCurves;
+private:
+ float scalar; // Since scalar is baked into the optimized curve we use the setter function to modify it.
+
+public:
+ short minMaxState; // see enum MinMaxCurveState
+ bool isOptimizedCurve;
+
+ MinMaxAnimationCurves editorCurves;
+
+ MinMaxCurve ();
+
+ inline float GetScalar() const { return scalar; }
+ inline void SetScalar(float value) { scalar = value; BuildCurves(polyCurves, editorCurves, scalar, minMaxState); }
+
+ bool IsOptimized () const { return isOptimizedCurve; }
+ bool UsesMinMax () const { return (minMaxState == kMMCTwoCurves) || (minMaxState == kMMCTwoConstants); }
+
+ DEFINE_GET_TYPESTRING (MinMaxCurve)
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+ Vector2f FindMinMax() const;
+ Vector2f FindMinMaxIntegrated() const;
+ Vector2f FindMinMaxDoubleIntegrated() const;
+};
+
+inline float EvaluateSlow (const MinMaxCurve& curve, float t, float factor)
+{
+ const float v = curve.editorCurves.max.Evaluate(t) * curve.GetScalar ();
+ if (curve.minMaxState == kMMCTwoCurves)
+ return Lerp (curve.editorCurves.min.Evaluate(t) * curve.GetScalar (), v, factor);
+ else
+ return v;
+}
+
+template<ParticleSystemCurveEvalMode mode>
+inline float Evaluate (const MinMaxCurve& curve, float t, float factor = 1.0F)
+{
+ if(mode == kEMScalar)
+ {
+ return curve.GetScalar();
+ }
+ if(mode == kEMOptimized)
+ {
+ DebugAssert(curve.isOptimizedCurve);
+ return curve.polyCurves.max.Evaluate (t);
+ }
+ else if (mode == kEMOptimizedMinMax)
+ {
+ DebugAssert(curve.isOptimizedCurve);
+ const float v0 = curve.polyCurves.min.Evaluate (t);
+ const float v1 = curve.polyCurves.max.Evaluate (t);
+ return Lerp (v0, v1, factor);
+ }
+ else if (mode == kEMSlow)
+ {
+ return EvaluateSlow (curve, t, factor);
+ }
+}
+
+inline float Evaluate (const MinMaxCurve& curve, float t, float randomValue = 1.0F)
+{
+ if (curve.minMaxState == kMMCScalar)
+ return curve.GetScalar ();
+
+ if (curve.minMaxState == kMMCTwoConstants)
+ return Lerp ( curve.editorCurves.min.GetKey(0).value * curve.GetScalar (),
+ curve.editorCurves.max.GetKey(0).value * curve.GetScalar (), randomValue);
+
+ DebugAssert(t <= 1.0F && t >= 0.0F);
+ if (curve.isOptimizedCurve)
+ return Evaluate<kEMOptimizedMinMax> (curve, t, randomValue);
+ else
+ return Evaluate<kEMSlow> (curve, t, randomValue);
+}
+
+struct DualMinMax3DPolyCurves
+{
+ MinMaxOptimizedPolyCurves optX;
+ MinMaxOptimizedPolyCurves optY;
+ MinMaxOptimizedPolyCurves optZ;
+ MinMaxPolyCurves x;
+ MinMaxPolyCurves y;
+ MinMaxPolyCurves z;
+};
+
+#endif // SHURIKENCURVES_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp
new file mode 100644
index 0000000..efe95c0
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp
@@ -0,0 +1,27 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemGradients.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+MinMaxGradient::MinMaxGradient()
+: minMaxState (kMMGColor), minColor (255,255,255,255), maxColor (255,255,255,255)
+{
+}
+
+void MinMaxGradient::InitializeOptimized(OptimizedMinMaxGradient& g)
+{
+ maxGradient.InitializeOptimized(g.max);
+ if(minMaxState == kMMGRandomBetweenTwoGradients)
+ minGradient.InitializeOptimized(g.min);
+}
+
+template<class TransferFunction>
+void MinMaxGradient::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (maxGradient);
+ TRANSFER (minGradient);
+ TRANSFER (minColor);
+ TRANSFER (maxColor);
+ TRANSFER (minMaxState); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(MinMaxGradient)
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h
new file mode 100644
index 0000000..bb74cde
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h
@@ -0,0 +1,87 @@
+#ifndef SHURIKENGRADIENTS_H
+#define SHURIKENGRADIENTS_H
+
+#include "Runtime/Math/Gradient.h"
+
+enum MinMaxGradientEvalMode
+{
+ kGEMGradient,
+ kGEMGradientMinMax,
+ kGEMSlow,
+};
+
+enum MinMaxGradientState
+{
+ kMMGColor = 0,
+ kMMGGradient = 1,
+ kMMGRandomBetweenTwoColors = 2,
+ kMMGRandomBetweenTwoGradients = 3
+};
+
+struct OptimizedMinMaxGradient
+{
+ OptimizedGradient max;
+ OptimizedGradient min;
+};
+
+inline ColorRGBA32 EvaluateGradient (const OptimizedMinMaxGradient& g, float t)
+{
+ return g.max.Evaluate(t);
+}
+
+inline ColorRGBA32 EvaluateRandomGradient (const OptimizedMinMaxGradient& g, float t, UInt32 factor)
+{
+ return Lerp (g.min.Evaluate(t), g.max.Evaluate(t), factor);
+}
+
+struct MinMaxGradient
+{
+ GradientNEW maxGradient;
+ GradientNEW minGradient;
+ ColorRGBA32 minColor; // we have the colors separate to prevent destroying the gradients
+ ColorRGBA32 maxColor;
+ short minMaxState; // see enum State
+
+ MinMaxGradient();
+
+ DEFINE_GET_TYPESTRING (MinMaxGradient)
+
+ void InitializeOptimized(OptimizedMinMaxGradient& g);
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+};
+
+inline ColorRGBA32 EvaluateColor (const MinMaxGradient& gradient)
+{
+ return gradient.maxColor;
+}
+
+inline ColorRGBA32 EvaluateGradient (const MinMaxGradient& gradient, float t)
+{
+ return gradient.maxGradient.Evaluate(t);
+}
+
+inline ColorRGBA32 EvaluateRandomColor (const MinMaxGradient& gradient, UInt32 factor)
+{
+ return Lerp (gradient.minColor, gradient.maxColor, factor);
+}
+
+inline ColorRGBA32 EvaluateRandomGradient (const MinMaxGradient& gradient, float t, UInt32 factor)
+{
+ return Lerp (gradient.minGradient.Evaluate(t), gradient.maxGradient.Evaluate(t), factor);
+}
+
+inline ColorRGBA32 Evaluate (const MinMaxGradient& gradient, float t, UInt32 factor = 0xff)
+{
+ if (gradient.minMaxState == kMMGColor)
+ return EvaluateColor(gradient);
+ else if (gradient.minMaxState == kMMGGradient)
+ return EvaluateGradient(gradient, t);
+ else if (gradient.minMaxState == kMMGRandomBetweenTwoColors)
+ return EvaluateRandomColor(gradient, factor);
+ else // gradient.minMaxState == kMMGRandomBetweenTwoGradients
+ return EvaluateRandomGradient(gradient, t, factor);
+}
+
+#endif // SHURIKENGRADIENTS_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp
new file mode 100644
index 0000000..d10bee0
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp
@@ -0,0 +1,323 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemParticle.h"
+
+#include "Runtime/Dynamics/Collider.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Threads/Thread.h"
+
+void InitializeEmitAccumulator(dynamic_array<float>& emitAccumulator, const size_t count)
+{
+ emitAccumulator.resize_uninitialized(count);
+ memset(emitAccumulator.begin(), 0, count * sizeof(float));
+}
+
+ParticleCollisionEvent::ParticleCollisionEvent (const Vector3f& intersection, const Vector3f& normal, const Vector3f& velocity, int colliderInstanceID, int rigidBodyOrColliderInstanceID)
+{
+ m_Intersection = intersection;
+ m_Normal = normal;
+ m_Velocity = velocity;
+ m_ColliderInstanceID = colliderInstanceID;
+ m_RigidBodyOrColliderInstanceID = rigidBodyOrColliderInstanceID;
+}
+
+size_t ParticleSystemParticles::GetParticleSize()
+{
+ // @TODO: How do we guard against elements we remove
+ return sizeof(ParticleSystemParticle);
+}
+
+void ParticleSystemParticles::SetUsesAxisOfRotation()
+{
+ usesAxisOfRotation = true;
+ const size_t count = position.size();
+ axisOfRotation.resize_uninitialized(count);
+ for(int i = 0; i < count; i++)
+ axisOfRotation[i] = Vector3f::yAxis;
+}
+
+void ParticleSystemParticles::SetUsesRotationalSpeed()
+{
+ usesRotationalSpeed = true;
+ const size_t count = position.size();
+ rotationalSpeed.resize_uninitialized(count);
+ for(int i = 0; i < count; i++)
+ rotationalSpeed[i] = 0.0f;
+}
+
+void ParticleSystemParticles::SetUsesCollisionEvents (bool wantUsesCollisionEvents)
+{
+ if (usesCollisionEvents == wantUsesCollisionEvents) return;
+ usesCollisionEvents = wantUsesCollisionEvents;
+ if (!usesCollisionEvents)
+ {
+ collisionEvents.Clear ();
+ }
+}
+
+bool ParticleSystemParticles::GetUsesCollisionEvents () const
+{
+ return usesCollisionEvents;
+}
+
+void ParticleSystemParticles::SetUsesEmitAccumulator(int numAccumulators)
+{
+ Assert(numAccumulators <= kParticleSystemMaxNumEmitAccumulators);
+ const size_t count = position.size();
+ for(int i = numEmitAccumulators; i < numAccumulators; i++)
+ InitializeEmitAccumulator(emitAccumulator[i], count);
+
+ numEmitAccumulators = numAccumulators;
+}
+
+void ParticleSystemParticles::CopyFromArrayAOS(ParticleSystemParticle* particles, int size)
+{
+ Assert(usesAxisOfRotation);
+ Assert(numEmitAccumulators == kParticleSystemMaxNumEmitAccumulators);
+ for(int i = 0; i < size; i++)
+ {
+ (*this).position[i] = particles[i].position;
+ (*this).velocity[i] = particles[i].velocity;
+ (*this).animatedVelocity[i] = particles[i].animatedVelocity;
+ (*this).axisOfRotation[i] = particles[i].axisOfRotation;
+ (*this).rotation[i] = particles[i].rotation;
+ if(usesRotationalSpeed)
+ (*this).rotationalSpeed[i] = particles[i].rotationalSpeed;
+ (*this).size[i] = particles[i].size;
+ (*this).color[i] = particles[i].color;
+ (*this).randomSeed[i] = particles[i].randomSeed;
+ (*this).lifetime[i] = particles[i].lifetime;
+ (*this).startLifetime[i] = particles[i].startLifetime;
+ for(int acc = 0; acc < kParticleSystemMaxNumEmitAccumulators; acc++)
+ (*this).emitAccumulator[acc][i] = particles[i].emitAccumulator[acc];
+ }
+}
+
+void ParticleSystemParticles::CopyToArrayAOS(ParticleSystemParticle* particles, int size)
+{
+ Assert(usesAxisOfRotation);
+ Assert(numEmitAccumulators == kParticleSystemMaxNumEmitAccumulators);
+ for(int i = 0; i < size; i++)
+ {
+ particles[i].position = (*this).position[i];
+ particles[i].velocity = (*this).velocity[i];
+ particles[i].animatedVelocity = (*this).animatedVelocity[i];
+ particles[i].axisOfRotation = (*this).axisOfRotation[i];
+ particles[i].rotation = (*this).rotation[i];
+ if(usesRotationalSpeed)
+ particles[i].rotationalSpeed = (*this).rotationalSpeed[i];
+ particles[i].size = (*this).size[i];
+ particles[i].color = (*this).color[i];
+ particles[i].randomSeed = (*this).randomSeed[i];
+ particles[i].lifetime = (*this).lifetime[i];
+ particles[i].startLifetime = (*this).startLifetime[i];
+ for(int acc = 0; acc < kParticleSystemMaxNumEmitAccumulators; acc++)
+ particles[i].emitAccumulator[acc] = (*this).emitAccumulator[acc][i];
+ }
+}
+
+void ParticleSystemParticles::AddParticle(ParticleSystemParticle* particle)
+{
+ const size_t count = array_size();
+ array_resize(count+1);
+ position[count] = particle->position;
+ velocity[count] = particle->velocity;
+ animatedVelocity[count] = Vector3f::zero;
+ lifetime[count] = particle->lifetime;
+ startLifetime[count] = particle->startLifetime;
+ size[count] = particle->size;
+ rotation[count] = particle->rotation;
+ if(usesRotationalSpeed)
+ rotationalSpeed[count] = particle->rotationalSpeed;
+ color[count] = particle->color;
+ randomSeed[count] = particle->randomSeed;
+ if(usesAxisOfRotation)
+ axisOfRotation[count] = particle->axisOfRotation;
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc][count] = 0.0f;
+}
+
+size_t ParticleSystemParticles::array_size () const
+{
+ return position.size();
+}
+
+void ParticleSystemParticles::array_resize (size_t i)
+{
+ position.resize_uninitialized(i);
+ velocity.resize_uninitialized(i);
+ animatedVelocity.resize_uninitialized(i);
+ rotation.resize_uninitialized(i);
+ if(usesRotationalSpeed)
+ rotationalSpeed.resize_uninitialized(i);
+ size.resize_uninitialized(i);
+ color.resize_uninitialized(i);
+ randomSeed.resize_uninitialized(i);
+ lifetime.resize_uninitialized(i);
+ startLifetime.resize_uninitialized(i);
+ if(usesAxisOfRotation)
+ axisOfRotation.resize_uninitialized(i);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc].resize_uninitialized(i);
+}
+
+void ParticleSystemParticles::element_swap(size_t i, size_t j)
+{
+ std::swap(position[i], position[j]);
+ std::swap(velocity[i], velocity[j]);
+ std::swap(animatedVelocity[i], animatedVelocity[j]);
+ std::swap(rotation[i], rotation[j]);
+ if(usesRotationalSpeed)
+ std::swap(rotationalSpeed[i], rotationalSpeed[j]);
+ std::swap(size[i], size[j]);
+ std::swap(color[i], color[j]);
+ std::swap(randomSeed[i], randomSeed[j]);
+ std::swap(lifetime[i], lifetime[j]);
+ std::swap(startLifetime[i], startLifetime[j]);
+ if(usesAxisOfRotation)
+ std::swap(axisOfRotation[i], axisOfRotation[j]);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ std::swap(emitAccumulator[acc][i], emitAccumulator[acc][j]);
+}
+
+void ParticleSystemParticles::element_assign(size_t i, size_t j)
+{
+ position[i] = position[j];
+ velocity[i] = velocity[j];
+ animatedVelocity[i] = animatedVelocity[j];
+ rotation[i] = rotation[j];
+ if(usesRotationalSpeed)
+ rotationalSpeed[i] = rotationalSpeed[j];
+ size[i] = size[j];
+ color[i] = color[j];
+ randomSeed[i] = randomSeed[j];
+ lifetime[i] = lifetime[j];
+ startLifetime[i] = startLifetime[j];
+ if(usesAxisOfRotation)
+ axisOfRotation[i] = axisOfRotation[j];
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc][i] = emitAccumulator[acc][j];
+}
+
+void ParticleSystemParticles::array_assign_external(void* data, const int numParticles)
+{
+#define SHURIKEN_INCREMENT_ASSIGN_PTRS(element, type) { beginPtr = endPtr; endPtr += numParticles * sizeof(type); (element).assign_external((type*)beginPtr, (type*)endPtr); }
+
+ UInt8* beginPtr = 0;
+ UInt8* endPtr = (UInt8*)data;
+
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(position, Vector3f);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(velocity, Vector3f);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(animatedVelocity, Vector3f);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(rotation, float);
+ if(usesRotationalSpeed)
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(rotationalSpeed, float);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(size, float);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(color, ColorRGBA32);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(randomSeed, UInt32);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(lifetime, float);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(startLifetime, float);
+ if(usesAxisOfRotation)
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(axisOfRotation, Vector3f);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(emitAccumulator[acc], float);
+#undef SHURIKEN_INCREMENT_ASSIGN_PTRS
+}
+
+void ParticleSystemParticles::array_merge_preallocated(const ParticleSystemParticles& rhs, const int offset, const bool needAxisOfRotation, const bool needEmitAccumulator)
+{
+#define SHURIKEN_COPY_DATA(element, type) memcpy(&element[offset], &rhs.element[0], count * sizeof(type))
+
+ const size_t count = rhs.array_size();
+ if(0 == count)
+ return;
+
+ Assert((rhs.array_size() + offset) <= array_size());
+ SHURIKEN_COPY_DATA(position, Vector3f);
+ SHURIKEN_COPY_DATA(velocity, Vector3f);
+ SHURIKEN_COPY_DATA(animatedVelocity, Vector3f);
+ SHURIKEN_COPY_DATA(rotation, float);
+ if(usesRotationalSpeed)
+ SHURIKEN_COPY_DATA(rotationalSpeed, float);
+ SHURIKEN_COPY_DATA(size, float);
+ SHURIKEN_COPY_DATA(color, ColorRGBA32);
+ SHURIKEN_COPY_DATA(randomSeed, UInt32);
+ SHURIKEN_COPY_DATA(lifetime, float);
+ SHURIKEN_COPY_DATA(startLifetime, float);
+
+ if(needAxisOfRotation)
+ {
+ Assert(usesAxisOfRotation && rhs.usesAxisOfRotation);
+ SHURIKEN_COPY_DATA(axisOfRotation, Vector3f);
+ }
+ if(needEmitAccumulator)
+ {
+ Assert(numEmitAccumulators && rhs.numEmitAccumulators);
+ Assert(numEmitAccumulators == rhs.numEmitAccumulators);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ SHURIKEN_COPY_DATA(emitAccumulator[acc], float);
+ }
+
+#undef SHURIKEN_COPY_DATA
+}
+
+
+void ParticleSystemParticles::array_assign(const ParticleSystemParticles& rhs)
+{
+ position.assign(rhs.position.begin(), rhs.position.end());
+ velocity.assign(rhs.velocity.begin(), rhs.velocity.end());
+ animatedVelocity.assign(rhs.animatedVelocity.begin(), rhs.animatedVelocity.end());
+ rotation.assign(rhs.rotation.begin(), rhs.rotation.end());
+ if(usesRotationalSpeed)
+ rotationalSpeed.assign(rhs.rotationalSpeed.begin(), rhs.rotationalSpeed.end());
+ size.assign(rhs.size.begin(), rhs.size.end());
+ color.assign(rhs.color.begin(), rhs.color.end());
+ randomSeed.assign(rhs.randomSeed.begin(), rhs.randomSeed.end());
+ lifetime.assign(rhs.lifetime.begin(), rhs.lifetime.end());
+ startLifetime.assign(rhs.startLifetime.begin(), rhs.startLifetime.end());
+ if(usesAxisOfRotation)
+ axisOfRotation.assign(rhs.axisOfRotation.begin(), rhs.axisOfRotation.end());
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc].assign(rhs.emitAccumulator[acc].begin(), rhs.emitAccumulator[acc].end());
+}
+
+void ParticleSystemParticles::array_lerp(ParticleSystemParticles& output, const ParticleSystemParticles& a, const ParticleSystemParticles& b, float factor)
+{
+ DebugAssert(a.array_size() == b.array_size()); // else it doesn't really make sense
+ DebugAssert(a.usesRotationalSpeed == b.usesRotationalSpeed);
+
+ const int count = a.array_size();
+ output.array_resize(count);
+
+ // Note: Not all data is interpolated here intentionally, because it doesn't change anything or because it's incorrect
+
+ #define SHURIKEN_LERP_DATA(element, type) for(int i = 0; i < count; i++) output.element[i] = Lerp(a.element[i], b.element[i], factor)
+ SHURIKEN_LERP_DATA(position, Vector3f);
+ SHURIKEN_LERP_DATA(velocity, Vector3f);
+ SHURIKEN_LERP_DATA(animatedVelocity, Vector3f);
+
+ SHURIKEN_LERP_DATA(rotation, float);
+ if(a.usesRotationalSpeed)
+ SHURIKEN_LERP_DATA(rotationalSpeed, float);
+ SHURIKEN_LERP_DATA(lifetime, float);
+
+ #undef SHURIKEN_LERP_DATA
+}
+
+ParticleSystemParticlesTempData::ParticleSystemParticlesTempData()
+:color(0)
+,size(0)
+,sheetIndex(0)
+,particleCount(0)
+{}
+
+void ParticleSystemParticlesTempData::element_swap(size_t i, size_t j)
+{
+ DebugAssert(i <= particleCount);
+ DebugAssert(j <= particleCount);
+
+ std::swap(color[i], color[j]);
+ std::swap(size[i], size[j]);
+ if(sheetIndex)
+ std::swap(sheetIndex[i], sheetIndex[j]);
+}
+
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h
new file mode 100644
index 0000000..65d6894
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h
@@ -0,0 +1,116 @@
+#ifndef SHURIKENPARTICLE_H
+#define SHURIKENPARTICLE_H
+
+#include "Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+class Collider;
+// Keep in sync with struct ParticleSystem.Particle
+enum{ kParticleSystemMaxNumEmitAccumulators = 2 };
+
+// TODO: Optimization:
+// . Store startLifetime as 1.0f/startLifeTime and store lifetime as 1.0f - lifetime.
+// This means that a lot of fdivs turn into muls and mads turns into nothing (NormalizedTime will become cheaper).
+// Remember: script must still convert into legacy format.
+
+// Keep in sync with struct ParticleSystem.Particle
+struct ParticleSystemParticle
+{
+ Vector3f position;
+ Vector3f velocity;
+ Vector3f animatedVelocity;
+ Vector3f axisOfRotation;
+ float rotation;
+ float rotationalSpeed;
+ float size;
+ ColorRGBA32 color;
+ UInt32 randomSeed;
+ float lifetime;
+ float startLifetime;
+ float emitAccumulator[kParticleSystemMaxNumEmitAccumulators];
+};
+
+typedef dynamic_array<Vector3f> ParticleSystemVector3Array;
+typedef dynamic_array<float> ParticleSystemFloatArray;
+typedef dynamic_array<ColorRGBA32> ParticleSystemColor32Array;
+typedef dynamic_array<UInt32> ParticleSystemUInt32Array;
+
+// Keep in sync with struct ParticleSystem.Particle
+struct ParticleSystemParticles
+{
+ ParticleSystemParticles()
+ :numEmitAccumulators(0)
+ ,usesAxisOfRotation(false)
+ ,usesRotationalSpeed(false)
+ ,usesCollisionEvents(false)
+ ,currentCollisionEventThreadArray(0)
+ {}
+
+ ParticleSystemVector3Array position;
+ ParticleSystemVector3Array velocity;
+ ParticleSystemVector3Array animatedVelocity; // Would actually only need this when modules with force and velocity curves are used
+ ParticleSystemVector3Array axisOfRotation;
+ ParticleSystemFloatArray rotation;
+ ParticleSystemFloatArray rotationalSpeed;
+ ParticleSystemFloatArray size;
+ ParticleSystemColor32Array color;
+ ParticleSystemUInt32Array randomSeed;
+ ParticleSystemFloatArray lifetime;
+ ParticleSystemFloatArray startLifetime;
+ ParticleSystemFloatArray emitAccumulator[kParticleSystemMaxNumEmitAccumulators]; // Usage: Only needed if particle system has time sub emitter
+ CollisionEvents collisionEvents;
+
+ bool usesAxisOfRotation;
+ bool usesRotationalSpeed;
+ bool usesCollisionEvents;
+ int currentCollisionEventThreadArray;
+ int numEmitAccumulators;
+
+ void AddParticle(ParticleSystemParticle* particle);
+
+ void SetUsesAxisOfRotation ();
+ void SetUsesRotationalSpeed();
+
+ void SetUsesCollisionEvents(bool usesCollisionEvents);
+ bool GetUsesCollisionEvents() const;
+ void SetUsesEmitAccumulator (int numAccumulators);
+
+ static size_t GetParticleSize();
+
+ size_t array_size () const;
+ void array_resize (size_t i);
+ void element_swap(size_t i, size_t j);
+ void element_assign(size_t i, size_t j);
+ void array_assign_external(void* data, const int numParticles);
+ void array_merge_preallocated(const ParticleSystemParticles& rhs, const int offset, const bool needAxisOfRotation, const bool needEmitAccumulator);
+ void array_assign(const ParticleSystemParticles& rhs);
+ static void array_lerp(ParticleSystemParticles& output, const ParticleSystemParticles& a, const ParticleSystemParticles& b, float factor);
+
+ void CopyFromArrayAOS(ParticleSystemParticle* particles, int size);
+ void CopyToArrayAOS(ParticleSystemParticle* particles, int size);
+};
+
+struct ParticleSystemParticlesTempData
+{
+ ParticleSystemParticlesTempData();
+ void element_swap(size_t i, size_t j);
+
+ ColorRGBA32* color;
+ float* size;
+ float* sheetIndex;
+ size_t particleCount;
+};
+
+inline float NormalizedTime (const ParticleSystemParticles& ps, size_t i)
+{
+ return (ps.startLifetime[i] - ps.lifetime[i]) / ps.startLifetime[i];
+}
+
+inline float NormalizedTime (float wholeTime, float currentTime)
+{
+ return (wholeTime - currentTime) / wholeTime;
+}
+
+#endif // SHURIKENPARTICLE_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp
new file mode 100644
index 0000000..7b14e62
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp
@@ -0,0 +1,1241 @@
+#include "UnityPrefix.h"
+#include "ParticleSystem.h"
+#include "ParticleSystemParticle.h"
+#include "ParticleSystemRenderer.h"
+#include "ParticleSystemUtils.h"
+#include "Modules/SubModule.h"
+#include "Modules/UVModule.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Graphics/DrawUtil.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include "Runtime/Filters/Misc/LineBuilder.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "Runtime/Graphics/TriStripper.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/BaseClasses/GameObject.h"
+
+IMPLEMENT_CLASS_INIT_ONLY (ParticleSystemRenderer)
+IMPLEMENT_OBJECT_SERIALIZE (ParticleSystemRenderer)
+
+PROFILER_INFORMATION(gParticlesSort, "ParticleSystem.Sort", kProfilerParticles)
+PROFILER_INFORMATION(gParticlesSingleProfile, "ParticleSystem.RenderSingle", kProfilerParticles)
+PROFILER_INFORMATION(gParticlesBatchProfile, "ParticleSystem.RenderBatch", kProfilerParticles)
+PROFILER_INFORMATION(gSubmitVBOParticleProfile, "Mesh.SubmitVBO", kProfilerRender)
+
+#define DEBUG_PARTICLE_SORTING (0)
+#if UNITY_WII
+#define kMaxNumParticlesPerBatch (65536/6)
+#else
+#define kMaxNumParticlesPerBatch (min<int>(kDynamicBatchingIndicesThreshold/6, VBO::kMaxQuads))
+#endif
+
+struct ParticleSystemVertex
+{
+ Vector3f vert;
+ Vector3f normal;
+ ColorRGBA32 color;
+ Vector2f uv;
+ Vector4f tangent; // Here, we put 2nd uv + blend factor
+};
+
+struct ParticleSystemGeomConstInputData
+{
+ Matrix4x4f m_ViewMatrix;
+ Vector3f m_CameraVelocity;
+ Object* m_Renderer;
+ UInt16 const* m_MeshIndexBuffer[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ int m_MeshIndexCount[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ int m_NumTilesX;
+ int m_NumTilesY;
+ float maxPlaneScale;
+ float maxOrthoSize;
+ float numUVFrame;
+ float animUScale;
+ float animVScale;
+ Vector3f xSpan;
+ Vector3f ySpan;
+ bool usesSheetIndex;
+ float bentNormalFactor;
+ Vector3f bentNormalVector;
+};
+
+inline void ScaleMatrix(Matrix4x4f& matrix, float scale)
+{
+ matrix.m_Data[0] *= scale;
+ matrix.m_Data[1] *= scale;
+ matrix.m_Data[2] *= scale;
+ matrix.m_Data[4] *= scale;
+ matrix.m_Data[5] *= scale;
+ matrix.m_Data[6] *= scale;
+ matrix.m_Data[8] *= scale;
+ matrix.m_Data[9] *= scale;
+ matrix.m_Data[10] *= scale;
+}
+
+struct ParticleSort
+{
+ inline static void SetValues(ParticleSort& sort, UInt32 inIndex, int inIntValue)
+ {
+ sort.index = inIndex;
+ sort.intValue = inIntValue;
+ }
+
+ inline static bool CompareValue (const ParticleSort& left, const ParticleSort& right)
+ {
+ return (left.intValue < right.intValue);
+ }
+
+ inline static void Swap(ParticleSort* oneOfThem, ParticleSort* theOtherOne)
+ {
+ ParticleSort temp = *oneOfThem;
+ *oneOfThem = *theOtherOne;
+ *theOtherOne = temp;
+ }
+
+ UInt32 index;
+ int intValue;
+};
+
+void GenerateSortIndices(ParticleSort* indices, const Vector3f& distFactor, const ParticleSystemParticles& ps, ParticleSystemSortMode sortMode)
+{
+ const size_t particleCount = ps.array_size();
+ if(IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_0_a1))
+ {
+ if(sortMode == kSSMByDistance)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(Dot (distFactor, ps.position[i]) * 40000.0f));
+ else if(sortMode == kSSMOldestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)((ps.startLifetime[i]- ps.lifetime[i]) * -40000.0f));
+ else if(sortMode == kSSMYoungestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)((ps.startLifetime[i]- ps.lifetime[i]) * 40000.0f));
+ }
+ else
+ {
+ // 3.5 used lifetime - this is pretty broken if you have random lifetimes, as you get random sorting
+ if(sortMode == kSSMByDistance)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(Dot (distFactor, ps.position[i]) * 40000.0f));
+ else if(sortMode == kSSMOldestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(ps.lifetime[i] * 40000.0f));
+ else if(sortMode == kSSMYoungestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(ps.lifetime[i] * -40000.0f));
+ }
+}
+
+template<bool sortTempData>
+void ApplySortRemap(ParticleSort* particleSortIndexBuffer, ParticleSystemParticlesTempData* tempData, ParticleSystemParticles& ps)
+{
+ const size_t count = ps.array_size();
+ for(int i = 0; i < count; i++)
+ {
+ int dst = particleSortIndexBuffer[i].intValue;
+ while(i != dst)
+ {
+ ParticleSort::Swap(&particleSortIndexBuffer[i], &particleSortIndexBuffer[dst]);
+ ps.element_swap(i, dst);
+ if(sortTempData)
+ tempData->element_swap(i, dst);
+
+ dst = particleSortIndexBuffer[i].intValue;
+ }
+ }
+}
+
+void Sort (const Matrix4x4f& matrix, ParticleSystemParticles& ps, ParticleSystemSortMode mode, ParticleSystemParticlesTempData* tempData, bool sortTempData)
+{
+ PROFILER_AUTO_GFX(gParticlesSort, 0);
+
+ DebugAssert(mode != kSSMNone);
+
+ const Vector3f distFactor = Vector3f (matrix.Get (2, 0), matrix.Get (2, 1), + matrix.Get (2, 2));
+ const size_t count = ps.array_size();
+
+ ParticleSort* particleSortIndexBuffer;
+ ALLOC_TEMP(particleSortIndexBuffer, ParticleSort, count);
+ GenerateSortIndices(&particleSortIndexBuffer[0], distFactor, ps, mode);
+
+ // Sort
+ std::sort(&particleSortIndexBuffer[0], &particleSortIndexBuffer[0] + count, ParticleSort::CompareValue);
+
+ // Create inverse mapping
+ for(int i = 0; i < count; i++)
+ particleSortIndexBuffer[particleSortIndexBuffer[i].index].intValue = i;
+
+ if(sortTempData)
+ ApplySortRemap<true>(particleSortIndexBuffer, tempData, ps);
+ else
+ ApplySortRemap<false>(particleSortIndexBuffer, tempData, ps);
+}
+
+struct ParticleMeshData
+{
+ int vertexCount;
+ StrideIterator<Vector3f> positions;
+ StrideIterator<Vector3f> normals;
+ StrideIterator<Vector4f> tangents;
+ StrideIterator<ColorRGBA32> colors;
+ StrideIterator<Vector2f> texCoords;
+ int indexCount;
+ const UInt16* indexBuffer;
+};
+
+template<bool hasNormals, bool hasTangents>
+void TransformParticleMesh(const ParticleMeshData& src, ColorRGBA32 particleColor,
+ const Matrix4x4f& xform, const Matrix4x4f& xformNoScale, UInt8** dest)
+{
+ for(int vertex = 0; vertex < src.vertexCount; vertex++)
+ {
+ // Vertex format is position, color, uv, and optional normals and tangents
+ xform.MultiplyPoint3(src.positions[vertex], *reinterpret_cast<Vector3f*>(*dest));
+ *dest += sizeof(Vector3f);
+ if (hasNormals)
+ {
+ xformNoScale.MultiplyVector3(src.normals[vertex], *reinterpret_cast<Vector3f*>(*dest));
+ *dest += sizeof(Vector3f);
+ }
+ *reinterpret_cast<ColorRGBA32*>(*dest) = particleColor * src.colors[vertex];
+ *dest += sizeof(ColorRGBA32);
+ *reinterpret_cast<Vector2f*>(*dest) = src.texCoords[vertex];
+ *dest += sizeof(Vector2f);
+ // Tangent is last in vertex format
+ if (hasTangents)
+ {
+ Vector3f newTangent = xformNoScale.MultiplyVector3((const Vector3f&)src.tangents[vertex]);
+ *reinterpret_cast<Vector4f*>(*dest) = Vector4f(newTangent, src.tangents[vertex].w);
+ *dest += sizeof(Vector4f);
+ }
+ }
+}
+
+
+ParticleSystemRenderer::ParticleSystemRenderer (MemLabelId label, ObjectCreationMode mode)
+: Super(kRendererParticleSystem, label, mode)
+, m_LocalSpaceAABB (Vector3f::zero, Vector3f::zero)
+{
+ SetVisible (false);
+
+ for (int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; ++i)
+ m_Data.cachedMeshUserNode[i].SetData (this);
+
+#if UNITY_EDITOR
+ m_EditorEnabled = true;
+#endif
+}
+
+ParticleSystemRenderer::~ParticleSystemRenderer ()
+{
+}
+
+void ParticleSystemRenderer::InitializeClass ()
+{
+ REGISTER_MESSAGE_PTR (ParticleSystemRenderer, kDidDeleteMesh, OnDidDeleteMesh, Mesh);
+}
+
+void ParticleSystemRenderer::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+ UpdateCachedMesh ();
+}
+
+void ParticleSystemRenderer::UpdateCachedMesh ()
+{
+ int dst = 0;
+ for(int src = 0; src < ParticleSystemRendererData::kMaxNumParticleMeshes; src++)
+ {
+ m_Data.cachedMesh[src] = NULL;
+ m_Data.cachedMeshUserNode[src].RemoveFromList ();
+
+ Mesh* mesh = m_Mesh[src];
+ if (mesh)
+ {
+ if (mesh->GetSubMeshCount() == 1)
+ {
+ m_Data.cachedMesh[dst] = mesh;
+ const SubMesh& sm = mesh->GetSubMeshFast(0);
+ const UInt16* buffer = mesh->GetSubMeshBuffer16(0);
+
+ if (sm.topology == kPrimitiveTriangleStripDeprecated)
+ {
+ const int capacity = CountTrianglesInStrip(buffer, sm.indexCount) * 3;
+ m_CachedIndexBuffer[dst].resize_uninitialized(capacity);
+ Destripify(buffer, sm.indexCount, m_CachedIndexBuffer[dst].begin(), capacity);
+ }
+ else if (sm.topology == kPrimitiveTriangles)
+ {
+ const int capacity = sm.indexCount;
+ m_CachedIndexBuffer[dst].resize_uninitialized(capacity);
+ memcpy(m_CachedIndexBuffer[dst].begin(), buffer, capacity*kVBOIndexSize);
+ }
+ else
+ {
+ m_CachedIndexBuffer[dst].resize_uninitialized(0);
+ }
+
+ // Hook into mesh's user notifications.
+ mesh->AddObjectUser (m_Data.cachedMeshUserNode[dst]);
+
+ dst++;
+ }
+ else
+ {
+ m_Data.cachedMesh[src] = NULL;
+ m_CachedIndexBuffer[src].resize_uninitialized(0);
+ AssertString ("Particle system meshes will only work with exactly one (1) sub mesh");
+ }
+ }
+ }
+}
+
+void ParticleSystemRenderer::OnDidDeleteMesh (Mesh* mesh)
+{
+ // Clear out cached pointer to mesh.
+ for (int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; ++i)
+ {
+ if (m_Data.cachedMesh[i] != mesh)
+ continue;
+
+ m_Data.cachedMesh[i] = NULL;
+ m_Data.cachedMeshUserNode[i].RemoveFromList ();
+ }
+}
+
+void ParticleSystemRenderer::GetLocalAABB (AABB& result)
+{
+ result = m_LocalSpaceAABB;
+}
+
+void ParticleSystemRenderer::GetWorldAABB (AABB& result)
+{
+ TransformAABB (m_LocalSpaceAABB, GetTransform ().GetPosition (), GetTransform ().GetRotation (), result);
+}
+
+float ParticleSystemRenderer::GetSortingFudge () const
+{
+ return m_Data.sortingFudge;
+}
+
+void ParticleSystemRenderer::CheckConsistency ()
+{
+ Super::CheckConsistency ();
+ m_Data.maxParticleSize = std::max (0.0F, m_Data.maxParticleSize);
+ m_Data.normalDirection = clamp<float>(m_Data.normalDirection, 0.0f, 1.0f);
+}
+
+void ParticleSystemRenderer::Reset ()
+{
+ Super::Reset ();
+ m_Data.renderMode = kSRMBillboard;
+ m_Data.lengthScale = 2.0F;
+ m_Data.velocityScale = 0.0F;
+ m_Data.cameraVelocityScale = 0.0F;
+ m_Data.maxParticleSize = 0.5F;
+ m_Data.sortingFudge = 0.0F;
+ m_Data.sortMode = kSSMNone;
+ m_Data.normalDirection = 1.0f;
+
+ for(int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; i++)
+ m_Mesh[i] = NULL;
+ m_LocalSpaceAABB.SetCenterAndExtent (Vector3f::zero, Vector3f::zero);
+
+
+
+#if UNITY_EDITOR
+ m_EditorEnabled = true;
+#endif
+}
+
+void ParticleSystemRenderer::UpdateRenderer ()
+{
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if (system)
+ {
+ SetVisible (true);
+ BoundsChanged();
+ }
+ else
+ {
+ UpdateManagerState (false);
+ }
+
+ Super::UpdateRenderer ();
+}
+
+void ParticleSystemRenderer::Update (const AABB& aabb)
+{
+ m_LocalSpaceAABB = aabb;
+ UpdateManagerState (true);
+}
+
+void ParticleSystemRenderer::RendererBecameVisible()
+{
+ Super::RendererBecameVisible();
+
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if(system)
+ system->RendererBecameVisible();
+}
+
+void ParticleSystemRenderer::RendererBecameInvisible()
+{
+ Super::RendererBecameInvisible();
+
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if(system)
+ system->RendererBecameInvisible();
+}
+
+void ParticleSystemRenderer::UpdateLocalAABB()
+{
+ AABB aabb;
+ GetLocalAABB(aabb);
+ m_TransformInfo.localAABB = aabb;
+}
+
+inline Rectf GetFrameUV (int index, int tilesX, float animUScale, float animVScale)
+{
+ int vIdx = index / tilesX;
+ int uIdx = index - vIdx * tilesX; // slightly faster than index % m_UVAnimation.xTile
+ float uOffset = (float)uIdx * animUScale;
+ float vOffset = 1.0f - animVScale - (float)vIdx * animVScale;
+
+ return Rectf(uOffset, vOffset, animUScale, animVScale);
+}
+
+template<ParticleSystemRenderMode renderMode>
+void GenerateParticleGeometry (ParticleSystemVertex* vbPtr,
+ const ParticleSystemGeomConstInputData& constData,
+ const ParticleSystemRendererData& rendererData,
+ const ParticleSystemParticles& ps,
+ const ParticleSystemParticlesTempData& psTemp,
+ size_t startIndex,
+ size_t endIndex,
+ const Matrix4x4f& worldViewMatrix,
+ const Matrix4x4f& viewToWorldMatrix)
+{
+ float maxPlaneScale = constData.maxPlaneScale;
+ float maxOrthoSize = constData.maxOrthoSize;
+ float numUVFrame = constData.numUVFrame;
+ Vector3f xSpan = constData.xSpan;
+ Vector3f ySpan = constData.ySpan;
+ Vector3f cameraVelocity = constData.m_CameraVelocity * rendererData.cameraVelocityScale;
+ int numTilesX = constData.m_NumTilesX;
+ float animUScale = constData.animUScale;
+ float animVScale = constData.animVScale;
+ bool usesSheetIndex = constData.usesSheetIndex;
+ float lengthScale = rendererData.lengthScale;
+ float velocityScale = rendererData.velocityScale;
+
+ float bentNormalFactor = constData.bentNormalFactor;
+ Vector3f bentNormalVector = constData.bentNormalVector;
+
+ Vector2f uv[4] = { Vector2f(0.0f, 1.0f),
+ Vector2f(1.0f, 1.0f),
+ Vector2f(1.0f, 0.0f),
+ Vector2f(0.0f, 0.0f)};
+ Vector4f uv2[4] = { Vector4f(0.0f, 1.0f, 0.0f, 0.0f),
+ Vector4f(1.0f, 1.0f, 0.0f, 0.0f),
+ Vector4f(1.0f, 0.0f, 0.0f, 0.0f),
+ Vector4f(0.0f, 0.0f, 0.0f, 0.0f)};
+
+ float invAnimVScale = 1.0f - animVScale;
+
+ for( int i = startIndex; i < endIndex; ++i )
+ {
+ Vector3f vert[4];
+ Vector3f n0, n1;
+
+ Vector3f position;
+ worldViewMatrix.MultiplyPoint3 (ps.position[i], position);
+
+ // Constrain the size to be a fraction of the viewport size.
+ // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1]
+ // Also all valid z's are negative so we just negate the whole equation
+ float maxWorldSpaceLength = position.z * maxPlaneScale + maxOrthoSize;
+ float hsize = std::min (psTemp.size[i], maxWorldSpaceLength) * 0.5f;
+ if (renderMode == kSRMBillboard)
+ {
+ float s = Sin (ps.rotation[i]);
+ float c = Cos (ps.rotation[i]);
+ n0 = Vector3f(-c+s, s+c, 0.0f);
+ n1 = Vector3f( c+s, -s+c, 0.0f);
+ vert[0] = position + n0 * hsize;
+ vert[1] = position + n1 * hsize;
+ vert[2] = position - n0 * hsize;
+ vert[3] = position - n1 * hsize;
+ }
+ else if (renderMode == kSRMBillboardFixedHorizontal || renderMode == kSRMBillboardFixedVertical)
+ {
+ float s = Sin (ps.rotation[i]+0.78539816339744830961566084581988f);
+ float c = Cos (ps.rotation[i]+0.78539816339744830961566084581988f);
+ n0 = xSpan*c + ySpan*s;
+ n1 = ySpan*c - xSpan*s;
+ vert[0] = position + n0 * hsize;
+ vert[1] = position + n1 * hsize;
+ vert[2] = position - n0 * hsize;
+ vert[3] = position - n1 * hsize;
+ }
+ else if (renderMode == kSRMStretch3D)
+ {
+ //RH BUG FOR LATER: Here we see the stretching bug as described by case no 434115...this is a Flash VM error, where a writeFloat (or readFloat) fails.
+ Vector3f velocity;
+ worldViewMatrix.MultiplyVector3(ps.velocity[i] + ps.animatedVelocity[i], velocity);
+ velocity -= cameraVelocity;
+ float sqrVelocity = SqrMagnitude (velocity);
+
+ Vector2f delta;
+ Vector3f endProj;
+ bool nonZeroVelocity = sqrVelocity > Vector3f::epsilon;
+ if (nonZeroVelocity)
+ {
+ endProj = position - velocity * (velocityScale + FastInvSqrt (sqrVelocity) * (lengthScale * psTemp.size[i]));
+ delta.x = position.z*endProj.y - position.y*endProj.z;
+ delta.y = position.x*endProj.z - position.z*endProj.x;
+ delta = NormalizeFast(delta);
+ }
+ else
+ {
+ endProj = position;
+ delta = Vector2f::xAxis;
+ }
+ n0 = n1 = Vector3f(delta.x, delta.y, 0.0f);
+ vert[0] = position + n0 * hsize;
+ vert[1] = endProj + n1 * hsize;
+ vert[2] = endProj - n0 * hsize;
+ vert[3] = position - n1 * hsize;
+ }
+
+ // UV animation
+ float sheetIndex;
+ if(usesSheetIndex)
+ {
+ // TODO: Pretty much the perfect candidate for SIMD
+
+ sheetIndex = psTemp.sheetIndex[i] * numUVFrame;
+ Assert (psTemp.sheetIndex[i] >= 0.0f && psTemp.sheetIndex[i] <= 1.0f);
+
+ const int index0 = FloorfToIntPos (sheetIndex);
+ const int index1 = index0 + 1;
+ Vector2f offset0, offset1;
+ const float blend = sheetIndex - (float)index0;
+
+ int vIdx = index0 / numTilesX;
+ int uIdx = index0 - vIdx * numTilesX;
+ offset0.x = (float)uIdx * animUScale;
+ offset0.y = invAnimVScale - (float)vIdx * animVScale;
+
+ vIdx = index1 / numTilesX;
+ uIdx = index1 - vIdx * numTilesX;
+ offset1.x = (float)uIdx * animUScale;
+ offset1.y = invAnimVScale - (float)vIdx * animVScale;
+
+ uv[0].Set(offset0.x, offset0.y + animVScale );
+ uv[1].Set(offset0.x + animUScale, offset0.y + animVScale );
+ uv[2].Set(offset0.x + animUScale, offset0.y );
+ uv[3].Set(offset0.x, offset0.y );
+
+ uv2[0].Set(offset1.x, offset1.y + animVScale, blend, 0.0f );
+ uv2[1].Set(offset1.x + animUScale, offset1.y + animVScale, blend, 0.0f );
+ uv2[2].Set(offset1.x + animUScale, offset1.y, blend, 0.0f );
+ uv2[3].Set(offset1.x, offset1.y, blend, 0.0f );
+ }
+
+ n0 = viewToWorldMatrix.MultiplyVector3(n0 * bentNormalFactor);
+ n1 = viewToWorldMatrix.MultiplyVector3(n1 * bentNormalFactor);
+
+ ColorRGBA32 color = psTemp.color[i];
+
+ vbPtr[0].vert = vert[0];
+ vbPtr[0].normal = bentNormalVector + n0;
+ vbPtr[0].color = color;
+ vbPtr[0].uv = uv[0];
+ vbPtr[0].tangent = uv2[0];
+
+ vbPtr[1].vert = vert[1];
+ vbPtr[1].normal = bentNormalVector + n1;
+ vbPtr[1].color = color;
+ vbPtr[1].uv = uv[1];
+ vbPtr[1].tangent = uv2[1];
+
+ vbPtr[2].vert = vert[2];
+ vbPtr[2].normal = bentNormalVector - n0;
+ vbPtr[2].color = color;
+ vbPtr[2].uv = uv[2];
+ vbPtr[2].tangent = uv2[2];
+
+ vbPtr[3].vert = vert[3];
+ vbPtr[3].normal = bentNormalVector - n1;
+ vbPtr[3].color = color;
+ vbPtr[3].uv = uv[3];
+ vbPtr[3].tangent = uv2[3];
+
+ // Next four vertices
+ vbPtr += 4;
+ }
+}
+
+static void DrawMeshParticles (const ParticleSystemGeomConstInputData& constInput, const ParticleSystemRendererData& rendererData, const Matrix4x4f& worldMatrix, const ParticleSystemParticles& ps, const ParticleSystemParticlesTempData& psTemp, const ChannelAssigns& channels)
+{
+ int numMeshes = 0;
+ ParticleMeshData particleMeshes[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ Vector3f defaultNormal(0, 0, 0);
+ Vector4f defaultTangent(0, 0, 0, 0);
+ ColorRGBA32 defaultColor(255, 255, 255, 255);
+ Vector2f defaultTexCoords(0, 0);
+ for(int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; i++)
+ {
+ if(constInput.m_MeshIndexCount[i] == 0)
+ break;
+ const Mesh* mesh = rendererData.cachedMesh[i];
+ if(mesh == NULL || !mesh->HasVertexData())
+ break;
+ ParticleMeshData& dest = particleMeshes[i];
+ dest.vertexCount = mesh->GetVertexCount();
+ dest.positions = mesh->GetVertexBegin();
+ dest.normals = mesh->GetNormalBegin();
+ if (dest.normals.IsNull())
+ dest.normals = StrideIterator<Vector3f>(&defaultNormal, 0);
+ dest.tangents = mesh->GetTangentBegin();
+ if (dest.tangents.IsNull())
+ dest.tangents = StrideIterator<Vector4f>(&defaultTangent, 0);
+ dest.texCoords = mesh->GetUvBegin();
+ if (dest.texCoords.IsNull())
+ dest.texCoords = StrideIterator<Vector2f>(&defaultTexCoords, 0);
+ dest.colors = mesh->GetColorBegin();
+ if (dest.colors.IsNull())
+ dest.colors = StrideIterator<ColorRGBA32>(&defaultColor, 0);
+ dest.indexCount = constInput.m_MeshIndexCount[i];
+ dest.indexBuffer = constInput.m_MeshIndexBuffer[i];
+ numMeshes++;
+ }
+
+ if(0 == numMeshes)
+ return;
+
+ GfxDevice& device = GetGfxDevice();
+
+ Matrix4x4f viewMatrix;
+ CopyMatrix (device.GetViewMatrix (), viewMatrix.GetPtr ());
+
+ const size_t particleCount = ps.array_size ();
+
+ float probability = 1.0f / (float)numMeshes;
+
+ // @TODO: We should move all these platform dependent numbers into Gfx specific code and get it from there.
+ const int kMaxVertices = 65536;
+
+#if UNITY_WII
+ const int kMaxIndices = 65536;
+#else
+ const int kMaxIndices = kDynamicBatchingIndicesThreshold;
+#endif
+
+ int particleOffset = 0;
+ while (particleOffset < particleCount)
+ {
+ int numVertices = 0;
+ int numIndices = 0;
+ int particleCountBatch = 0;
+
+ // Figure out batch size
+ for(int i = particleOffset; i < particleCount; i++)
+ {
+ const float randomValue = GenerateRandom(ps.randomSeed[i] + kParticleSystemMeshSelectionId);
+ int lastNumVertices = 0;
+ int lastNumIndices = 0;
+ for(int j = 0; j < numMeshes; j++)
+ {
+ const float lower = probability * j;
+ const float upper = probability * (j + 1);
+ if((randomValue >= lower) && (randomValue <= upper))
+ {
+ lastNumVertices = particleMeshes[j].vertexCount;
+ lastNumIndices = particleMeshes[j].indexCount;
+ break;
+ }
+ }
+ if((numVertices >= kMaxVertices) || (numIndices >= kMaxIndices))
+ {
+ break;
+ }
+ else
+ {
+ numVertices += lastNumVertices;
+ numIndices += lastNumIndices;
+ particleCountBatch++;
+ }
+ }
+
+ const int vertexCount = numVertices;
+ const int indexCount = numIndices;
+
+ // Figure out if normals and tangents are needed by shader
+ UInt32 normalTangentMask = channels.GetSourceMap() & VERTEX_FORMAT2(Normal, Tangent);
+
+ // Tangents requires normals
+ if( normalTangentMask & VERTEX_FORMAT1(Tangent) )
+ normalTangentMask |= VERTEX_FORMAT1(Normal);
+
+ // Get VBO chunk
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ UInt8* vbPtr = NULL;
+ UInt16* ibPtr = NULL;
+ const UInt32 mandatoryChannels = VERTEX_FORMAT3(Vertex, Color, TexCoord0);
+ if( !vbo.GetChunk( mandatoryChannels | normalTangentMask,
+ vertexCount, indexCount,
+ DynamicVBO::kDrawIndexedTriangles,
+ (void**)&vbPtr, (void**)&ibPtr ) )
+ {
+ return;
+ }
+
+ int vertexOffset = 0;
+ int indexOffset = 0;
+ const int startIndex = particleOffset;
+ const int endIndex = particleOffset + particleCountBatch;
+ for( int i = startIndex; i < endIndex; ++i )
+ {
+ const Vector3f position = ps.position[i];
+ const float rotation = ps.rotation[i];
+ const float size = psTemp.size[i];
+ const Vector3f axisOfRotation = NormalizeSafe (ps.axisOfRotation[i], Vector3f::yAxis);
+ const ColorRGBA32 particleColor = psTemp.color[i];
+
+ // Only shared part is actually rotation. xformNoScale doesn't need a translation, so no need to copy that data
+ Matrix4x4f xformNoScale;
+ xformNoScale.SetTR (position, AxisAngleToQuaternion (axisOfRotation, rotation));
+
+ Matrix4x4f xform = xformNoScale;
+ ScaleMatrix(xform, size);
+
+ // Figure out which mesh to use
+ const float randomValue = GenerateRandom(ps.randomSeed[i] + kParticleSystemMeshSelectionId);
+ int meshIndex = 0;
+ for(int j = 0; j < numMeshes; j++)
+ {
+ const float lower = probability * j;
+ const float upper = probability * (j + 1);
+ if((randomValue >= lower) && (randomValue <= upper))
+ {
+ meshIndex = j;
+ break;
+ }
+ }
+
+ const ParticleMeshData& mesh = particleMeshes[meshIndex];
+
+ // Fill up vbo here
+ if( normalTangentMask == VERTEX_FORMAT2(Normal, Tangent) )
+ TransformParticleMesh<true, true>(mesh, particleColor, xform, xformNoScale, &vbPtr);
+ else if( normalTangentMask == VERTEX_FORMAT1(Normal) )
+ TransformParticleMesh<true, false>(mesh, particleColor, xform, xformNoScale, &vbPtr);
+ else if( normalTangentMask == 0 )
+ TransformParticleMesh<false, false>(mesh, particleColor, xform, xformNoScale, &vbPtr);
+ else
+ ErrorString("Invalid normalTangentMask");
+
+ const int meshIndexMax = mesh.indexCount - 2;
+ for(int index = 0; index < meshIndexMax; index+=3)
+ {
+ ibPtr[index+0] = mesh.indexBuffer[index+0] + vertexOffset;
+ ibPtr[index+1] = mesh.indexBuffer[index+1] + vertexOffset;
+ ibPtr[index+2] = mesh.indexBuffer[index+2] + vertexOffset;
+ }
+ ibPtr += mesh.indexCount;
+
+ vertexOffset += mesh.vertexCount;
+ indexOffset += mesh.indexCount;
+ }
+
+ vbo.ReleaseChunk (vertexCount, indexCount);
+ device.SetViewMatrix(viewMatrix.GetPtr());
+ device.SetWorldMatrix(worldMatrix.GetPtr());
+ vbo.DrawChunk (channels);
+ GPU_TIMESTAMP();
+
+ particleOffset += particleCountBatch;
+ }
+}
+
+static void DrawParticlesInternal(const ParticleSystemGeomConstInputData& constData, const ParticleSystemRendererData& rendererData, const Matrix4x4f& worldViewMatrix, const Matrix4x4f& viewToWorldMatrix, const ParticleSystemParticles& ps, const ParticleSystemParticlesTempData& psTemp, ParticleSystemVertex* vbPtr, const size_t particleOffset, const size_t numParticles, int renderMode)
+{
+ const size_t endIndex = particleOffset + numParticles;
+
+ if (renderMode == kSRMBillboard)
+ GenerateParticleGeometry<kSRMBillboard> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+ if (renderMode == kSRMStretch3D)
+ GenerateParticleGeometry<kSRMStretch3D> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+ if (renderMode == kSRMBillboardFixedHorizontal)
+ GenerateParticleGeometry<kSRMBillboardFixedHorizontal> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+ if (renderMode == kSRMBillboardFixedVertical)
+ GenerateParticleGeometry<kSRMBillboardFixedVertical> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+}
+
+static void DrawParticles(const ParticleSystemGeomConstInputData& constData, const ParticleSystemRendererData& rendererData, const Matrix4x4f& worldViewMatrix, const Matrix4x4f& viewToWorldMatrix, const ParticleSystemParticles& ps, const ParticleSystemParticlesTempData& psTemp, const ChannelAssigns& channels, ParticleSystemVertex* vbPtr)
+{
+ GfxDevice& device = GetGfxDevice();
+ const size_t particleCount = ps.array_size();
+
+ if(vbPtr)
+ {
+ DrawParticlesInternal(constData, rendererData, worldViewMatrix, viewToWorldMatrix, ps, psTemp, vbPtr, 0, particleCount, rendererData.renderMode);
+ }
+ else
+ {
+ int particleOffset = 0;
+ while (particleOffset < particleCount)
+ {
+ const int particleCountBatch = min(kMaxNumParticlesPerBatch, (int)particleCount - particleOffset);
+
+ // Get VBO chunk
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor) | (1<<kShaderChannelTangent),
+ particleCountBatch * 4, 0,
+ DynamicVBO::kDrawQuads,
+ (void**)&vbPtr, NULL ) )
+ {
+ continue;
+ }
+
+ DrawParticlesInternal(constData, rendererData, worldViewMatrix, viewToWorldMatrix, ps, psTemp, vbPtr, particleOffset, particleCountBatch, rendererData.renderMode);
+ particleOffset += particleCountBatch;
+
+ vbo.ReleaseChunk (particleCountBatch * 4, 0);
+
+ // Draw
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+
+ PROFILER_BEGIN(gSubmitVBOParticleProfile, constData.m_Renderer)
+ vbo.DrawChunk (channels);
+ GPU_TIMESTAMP();
+ PROFILER_END
+
+ device.SetViewMatrix(constData.m_ViewMatrix.GetPtr ());
+ }
+ }
+}
+
+void ParticleSystemRenderer::CalculateTotalParticleCount(UInt32& totalNumberOfParticles, ParticleSystem& system, bool first)
+{
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ totalNumberOfParticles += system.GetParticleCount();
+
+ Transform* t = system.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* child = (**i).QueryComponent(ParticleSystem);
+ if (child)
+ CalculateTotalParticleCount(totalNumberOfParticles, *child, false);
+ }
+ }
+}
+
+void ParticleSystemRenderer::CombineParticleBuffersRec(int& offset, ParticleSystemParticles& ps, ParticleSystemParticlesTempData& psTemp, ParticleSystem& system, bool first, bool needsAxisOfRotation)
+{
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ int particleCount = system.GetParticleCount();
+ ps.array_merge_preallocated(system.GetParticles(), offset, needsAxisOfRotation, false);
+
+ if(system.m_ReadOnlyState->useLocalSpace)
+ {
+ Matrix4x4f localToWorld = system.GetComponent (Transform).GetLocalToWorldMatrixNoScale ();
+
+ int endIndex = offset + particleCount;
+ for(int i = offset; i < endIndex; i++)
+ ps.position[i] = localToWorld.MultiplyPoint3(ps.position[i]);
+ for(int i = offset; i < endIndex; i++)
+ ps.velocity[i] = localToWorld.MultiplyVector3(ps.velocity[i]);
+ for(int i = offset; i < endIndex; i++)
+ ps.animatedVelocity[i] = localToWorld.MultiplyVector3(ps.animatedVelocity[i]);
+ if(ps.usesAxisOfRotation)
+ for(int i = offset; i < endIndex; i++)
+ ps.axisOfRotation[i] = localToWorld.MultiplyVector3(ps.axisOfRotation[i]);
+ }
+
+ ParticleSystem::UpdateModulesNonIncremental(system, ps, psTemp, offset, offset + particleCount);
+ offset += particleCount;
+
+ Transform* t = system.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* child = (**i).QueryComponent(ParticleSystem);
+ if (child)
+ CombineParticleBuffersRec(offset, ps, psTemp, *child, false, needsAxisOfRotation);
+ }
+ }
+}
+
+void ParticleSystemRenderer::SetUsesAxisOfRotationRec(ParticleSystem& shuriken, bool first)
+{
+ ParticleSystemRenderer* renderer = shuriken.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ shuriken.SetUsesAxisOfRotation();
+
+ Transform* t = shuriken.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* shuriken = (**i).QueryComponent(ParticleSystem);
+ if (shuriken)
+ SetUsesAxisOfRotationRec(*shuriken, false);
+ }
+ }
+}
+
+void ParticleSystemRenderer::CombineBoundsRec(ParticleSystem& shuriken, MinMaxAABB& aabb, bool first)
+{
+ ParticleSystemRenderer* renderer = shuriken.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ AABB result = shuriken.m_State->minMaxAABB;
+ if(!shuriken.m_ReadOnlyState->useLocalSpace)
+ InverseTransformAABB (result, renderer->GetTransform().GetPosition (), renderer->GetTransform().GetRotation (), result);
+
+ if(first)
+ aabb = result;
+ else
+ aabb.Encapsulate(result);
+
+ Transform* t = shuriken.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* shuriken = (**i).QueryComponent(ParticleSystem);
+ if (shuriken)
+ CombineBoundsRec(*shuriken, aabb, false);
+ }
+ }
+}
+
+void ParticleSystemRenderer::Render (int/* materialIndex*/, const ChannelAssigns& channels)
+{
+ ParticleSystem::SyncJobs();
+
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if(!system)
+ return;
+
+ // Can't render without an active camera (case 568930)
+ // Can remove check when we finally kill Renderer.Render()
+ if (!GetCurrentCameraPtr())
+ return;
+
+ PROFILER_AUTO_GFX(gParticlesSingleProfile, this);
+
+ UInt32 numParticles = 0;
+ CalculateTotalParticleCount(numParticles, *system, true);
+ if(numParticles)
+ RenderInternal(*system, *this, channels, 0, numParticles);
+}
+
+void ParticleSystemRenderer::RenderMultiple (const BatchInstanceData* instances, size_t count, const ChannelAssigns& channels)
+{
+ ParticleSystem::SyncJobs();
+
+ size_t numParticlesBatch = 0;
+
+ BatchInstanceData const* instancesEnd = instances + count;
+ BatchInstanceData const* iBatchBegin = instances;
+ BatchInstanceData const* iBatchEnd = instances;
+ while(iBatchEnd != instancesEnd)
+ {
+ Assert(iBatchEnd->renderer->GetRendererType() == kRendererParticleSystem);
+ ParticleSystemRenderer* psRenderer = (ParticleSystemRenderer*)iBatchEnd->renderer;
+ Assert(psRenderer->GetRenderMode() != kSRMMesh);
+ ParticleSystem* system = psRenderer->QueryComponent(ParticleSystem);
+ if (!system )
+ {
+ iBatchEnd++;
+ continue;
+ }
+ UInt32 numParticles = 0;
+ psRenderer->CalculateTotalParticleCount(numParticles, *system, true);
+
+ if((numParticlesBatch + numParticles) <= kMaxNumParticlesPerBatch)
+ {
+ numParticlesBatch += numParticles;
+ iBatchEnd++;
+ }
+ else
+ {
+ if(numParticlesBatch)
+ {
+ RenderBatch(iBatchBegin, iBatchEnd - iBatchBegin, numParticlesBatch, channels);
+ numParticlesBatch = 0;
+ iBatchBegin = iBatchEnd;
+ }
+ else // Can't fit in one draw call
+ {
+ RenderBatch(iBatchEnd, 1, numParticles, channels);
+ iBatchEnd++;
+ iBatchBegin = iBatchEnd;
+ }
+ }
+ }
+
+ if((iBatchBegin != iBatchEnd) && numParticlesBatch)
+ RenderBatch(iBatchBegin, iBatchEnd - iBatchBegin, numParticlesBatch, channels);
+}
+
+void ParticleSystemRenderer::RenderBatch (const BatchInstanceData* instances, size_t count, size_t numParticles, const ChannelAssigns& channels)
+{
+ DebugAssert(numParticles);
+
+ GfxDevice& device = GetGfxDevice();
+
+ const MaterialPropertyBlock* customProps = count > 0 ? instances[0].renderer->GetCustomProperties() : NULL;
+ if (customProps)
+ device.SetMaterialProperties (*customProps);
+
+ Matrix4x4f viewMatrix;
+ CopyMatrix (device.GetViewMatrix (), viewMatrix.GetPtr ());
+
+ ParticleSystemVertex* vbPtr = 0;
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ if(numParticles <= kMaxNumParticlesPerBatch)
+ {
+ if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor) | (1<<kShaderChannelTangent),
+ numParticles * 4, 0,
+ DynamicVBO::kDrawQuads,
+ (void**)&vbPtr, NULL ) )
+ {
+ return;
+ }
+ }
+
+ PROFILER_AUTO_GFX(gParticlesBatchProfile, 0);
+
+ // Allocate VBO if count is not above threshold. Else just pass null down
+ BatchInstanceData const* iBatchBegin = instances;
+ BatchInstanceData const* instancesEnd = instances + count;
+ size_t particleOffset = 0;
+ while (iBatchBegin != instancesEnd)
+ {
+ Assert(iBatchBegin->renderer->GetRendererType() == kRendererParticleSystem);
+ ParticleSystemRenderer* psRenderer = (ParticleSystemRenderer*)iBatchBegin->renderer;
+ Assert(psRenderer->GetRenderMode() != kSRMMesh);
+ ParticleSystem* system = psRenderer->QueryComponent(ParticleSystem);
+ UInt32 particleCountTotal = 0;
+ if (system)
+ {
+ // It would be nice to filter out NULL particle systems earlier, but we don't (case 504744)
+ CalculateTotalParticleCount(particleCountTotal, *system, true);
+ if(particleCountTotal)
+ RenderInternal(*system, *psRenderer, channels, vbPtr + particleOffset * 4, particleCountTotal);
+ }
+ iBatchBegin++;
+ particleOffset += particleCountTotal;
+ }
+
+ if(vbPtr)
+ {
+ vbo.ReleaseChunk (numParticles * 4, 0);
+
+ // Draw
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+
+ PROFILER_BEGIN(gSubmitVBOParticleProfile, 0)
+ vbo.DrawChunk (channels);
+ GPU_TIMESTAMP();
+ PROFILER_END
+
+ if (count > 1)
+ device.AddBatchingStats(numParticles * 2, numParticles * 4, count);
+
+ device.SetViewMatrix(viewMatrix.GetPtr());
+ }
+}
+
+void ParticleSystemRenderer::RenderInternal (ParticleSystem& system, const ParticleSystemRenderer& renderer, const ChannelAssigns& channels, ParticleSystemVertex* vbPtr, UInt32 particleCountTotal)
+{
+ Assert(particleCountTotal);
+
+#if UNITY_EDITOR
+ if (!renderer.m_EditorEnabled)
+ return;
+#endif
+
+ GfxDevice& device = GetGfxDevice();
+
+ // Render matrix
+ Matrix4x4f viewMatrix;
+ CopyMatrix (device.GetViewMatrix (), viewMatrix.GetPtr ());
+
+ ParticleSystemParticles* ps = &system.GetParticles ();
+ size_t particleCount = ps->array_size ();
+ AssertBreak(particleCountTotal >= particleCount);
+
+ UInt8* combineBuffer = 0;
+ ParticleSystemParticles combineParticles;
+ if(particleCountTotal > particleCount)
+ {
+ particleCount = particleCountTotal;
+ combineBuffer = ALLOC_TEMP_MANUAL(UInt8, particleCountTotal * ParticleSystemParticles::GetParticleSize());
+ combineParticles.array_assign_external((void*)&combineBuffer[0], particleCountTotal);
+ ps = &combineParticles;
+ }
+
+ if(!particleCount)
+ return;
+
+ const bool needsAxisOfRotation = !renderer.GetScreenSpaceRotation();
+ if(needsAxisOfRotation)
+ {
+ if ( IS_CONTENT_NEWER_OR_SAME (GetNumericVersion ("4.0.0f7")) && !IS_CONTENT_NEWER_OR_SAME (GetNumericVersion ("4.1.1a1")) )
+ {
+ // this was introduced in 4.0 and is wrong, it will effectively force the rotation axis to be the y-axis for all particles, the function is meant only
+ // to initialize the axis array (once) and should only be called from SetUsesAxisOfRotationRec
+ ps->SetUsesAxisOfRotation ();
+ }
+ else
+ {
+ // this is the intended functionality, but was broken for 4.0 and 4.0.1 see above
+ SetUsesAxisOfRotationRec (system, true);
+ }
+ }
+
+ ParticleSystemSortMode sortMode = (ParticleSystemSortMode)renderer.m_Data.sortMode;
+ bool isInLocalSpace = system.m_ReadOnlyState->useLocalSpace && !combineBuffer;
+ Matrix4x4f worldMatrix = Matrix4x4f::identity;
+ if(isInLocalSpace)
+ worldMatrix = system.GetComponent (Transform).GetLocalToWorldMatrixNoScale ();
+
+ ParticleSystemParticlesTempData psTemp;
+ psTemp.color = ALLOC_TEMP_MANUAL(ColorRGBA32, particleCount);
+ psTemp.size = ALLOC_TEMP_MANUAL(float, particleCount);
+ psTemp.sheetIndex = 0;
+ psTemp.particleCount = particleCount;
+ if(combineBuffer)
+ {
+ int offset = 0;
+ CombineParticleBuffersRec(offset, *ps, psTemp, system, true, needsAxisOfRotation);
+ if (kSSMNone != sortMode)
+ Sort(viewMatrix, *ps, sortMode, &psTemp, true);
+ }
+ else
+ {
+ if(system.m_UVModule->GetEnabled())
+ psTemp.sheetIndex = ALLOC_TEMP_MANUAL(float, particleCount);
+
+ if (kSSMNone != sortMode)
+ {
+ Matrix4x4f objectToViewMatrix;
+ MultiplyMatrices3x4(viewMatrix, worldMatrix, objectToViewMatrix);
+ Sort(objectToViewMatrix, *ps, sortMode, 0, false);
+ }
+ ParticleSystem::UpdateModulesNonIncremental(system, *ps, psTemp, 0, particleCount);
+ }
+
+ // Constrain the size to be a fraction of the viewport size.
+ // In perspective case, max size is (z*factorA). In ortho case, max size is just factorB. To have both
+ // without branches, we do (z*factorA+factorB) and set one of factors to zero.
+ float maxPlaneScale = 0.0f;
+ float maxOrthoSize = 0.0f;
+ // Getting the camera isn't totally free, so do it once.
+ const Camera& camera = GetCurrentCamera();
+ if (!camera.GetOrthographic())
+ maxPlaneScale = -camera.CalculateFarPlaneWorldSpaceLength() * renderer.m_Data.maxParticleSize / camera.GetFar();
+ else
+ maxOrthoSize = camera.CalculateFarPlaneWorldSpaceLength() * renderer.m_Data.maxParticleSize;
+
+ int numMeshes = 0;
+ for(int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; i++)
+ if(renderer.m_Data.cachedMesh[i])
+ numMeshes++;
+
+ ParticleSystemGeomConstInputData constData;
+ constData.m_ViewMatrix = viewMatrix;
+ constData.m_CameraVelocity = viewMatrix.MultiplyVector3(camera.GetVelocity ());
+ constData.m_Renderer = (Object*)&renderer;
+ for(int i = 0; i < numMeshes; i++)
+ {
+ constData.m_MeshIndexBuffer[i] = renderer.m_CachedIndexBuffer[i].begin();
+ constData.m_MeshIndexCount[i] = renderer.m_CachedIndexBuffer[i].size();
+ AssertBreak((constData.m_MeshIndexCount[i] % 3) == 0);
+ }
+
+ system.GetNumTiles(constData.m_NumTilesX, constData.m_NumTilesY);
+ constData.maxPlaneScale = maxPlaneScale;
+ constData.maxOrthoSize = maxOrthoSize;
+ constData.numUVFrame = constData.m_NumTilesX * constData.m_NumTilesY;
+ constData.animUScale = 1.0f / (float)constData.m_NumTilesX;
+ constData.animVScale = 1.0f / (float)constData.m_NumTilesY;
+ constData.xSpan = Vector3f(-1.0f,0.0f,0.0f);
+ constData.ySpan = Vector3f(0.0f,0.0f,1.0f);
+ if (renderer.m_Data.renderMode == kSRMBillboardFixedVertical)
+ {
+ constData.ySpan = Vector3f(0.0f,1.0f,0.0f);
+ const Vector3f zSpan = viewMatrix.MultiplyVector3 (Vector3f::zAxis);// (RotateVectorByQuat (cameraRotation, Vector3f(0.0f,0.0f,1.0f));
+ constData.xSpan = NormalizeSafe (Cross (constData.ySpan, zSpan));
+ }
+ constData.xSpan = viewMatrix.MultiplyVector3(constData.xSpan);
+ constData.ySpan = viewMatrix.MultiplyVector3(constData.ySpan);
+ constData.usesSheetIndex = psTemp.sheetIndex != NULL;
+
+ const float bentNormalAngle = renderer.m_Data.normalDirection * 90.0f * kDeg2Rad;
+ const float scale = (renderer.m_Data.renderMode == kSRMBillboard) ? 0.707106781f : 1.0f;
+
+ Matrix4x4f viewToWorldMatrix;
+ Matrix4x4f::Invert_General3D(viewMatrix, viewToWorldMatrix);
+
+ Matrix4x4f worldViewMatrix;
+ MultiplyMatrices4x4(&viewMatrix, &worldMatrix, &worldViewMatrix);
+
+ Vector3f billboardNormal = Vector3f::zAxis;
+ if((renderer.m_Data.renderMode == kSRMBillboardFixedHorizontal) || (renderer.m_Data.renderMode == kSRMBillboardFixedVertical))
+ billboardNormal = viewMatrix.MultiplyVector3 (NormalizeSafe (Cross (constData.xSpan, constData.ySpan)));
+ constData.bentNormalVector = viewToWorldMatrix.MultiplyVector3(Sin(bentNormalAngle) * billboardNormal);
+ constData.bentNormalFactor = Cos(bentNormalAngle) * scale;
+
+ if (renderer.m_Data.renderMode == kSRMMesh)
+ DrawMeshParticles (constData, renderer.m_Data, worldMatrix, *ps, psTemp, channels);
+ else
+ DrawParticles(constData, renderer.m_Data, worldViewMatrix, viewToWorldMatrix, *ps, psTemp, channels, vbPtr);
+
+ FREE_TEMP_MANUAL(psTemp.color);
+ FREE_TEMP_MANUAL(psTemp.size);
+ if(psTemp.sheetIndex)
+ FREE_TEMP_MANUAL(psTemp.sheetIndex);
+ if(combineBuffer)
+ FREE_TEMP_MANUAL(combineBuffer);
+}
+
+template<class TransferFunction> inline
+void ParticleSystemRenderer::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.Transfer (m_Data.renderMode, "m_RenderMode");
+ transfer.Transfer (m_Data.maxParticleSize, "m_MaxParticleSize");
+ transfer.Transfer (m_Data.cameraVelocityScale, "m_CameraVelocityScale");
+ transfer.Transfer (m_Data.velocityScale, "m_VelocityScale");
+ transfer.Transfer (m_Data.lengthScale, "m_LengthScale");
+ transfer.Transfer (m_Data.sortingFudge, "m_SortingFudge");
+ transfer.Transfer (m_Data.normalDirection, "m_NormalDirection");
+ transfer.Transfer (m_Data.sortMode, "m_SortMode");
+ transfer.Transfer (m_Mesh[0], "m_Mesh");
+ transfer.Transfer (m_Mesh[1], "m_Mesh1");
+ transfer.Transfer (m_Mesh[2], "m_Mesh2");
+ transfer.Transfer (m_Mesh[3], "m_Mesh3");
+}
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h
new file mode 100644
index 0000000..07bcb0d
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h
@@ -0,0 +1,134 @@
+#ifndef SHURIKENRENDERER_H
+#define SHURIKENRENDERER_H
+
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Vector2.h"
+
+
+
+
+class Mesh;
+class MinMaxAABB;
+struct ParticleSystemVertex;
+
+struct ParticleSystemRendererData
+{
+ // Must match the one in RendererModuleUI.cs
+ enum { kMaxNumParticleMeshes = 4 };
+
+ int renderMode; ///< enum { Billboard = 0, Stretched = 1, Horizontal Billboard = 2, Vertical Billboard = 3, Mesh = 4 }
+ int sortMode; ///< enum { None = 0, By Distance = 1, Youngest First = 2, Oldest First = 3 }
+ float maxParticleSize; ///< How large is a particle allowed to be on screen at most? 1 is entire viewport. 0.5 is half viewport.
+ float cameraVelocityScale; ///< How much the camera motion is factored in when determining particle stretching.
+ float velocityScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its velocity.
+ float lengthScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its width.
+ float sortingFudge; ///< Lower the number, most likely that these particles will appear in front of other transparent objects, including other particles.
+ float normalDirection; ///< Value between 0.0 and 1.0. If 1.0 is used, normals will point towards camera. If 0.0 is used, normals will point out in the corner direction of the particle.
+ Mesh* cachedMesh[kMaxNumParticleMeshes];
+
+ /// Node hooked into the mesh user list of cached meshes so we get notified
+ /// when a mesh goes away.
+ ///
+ /// NOTE: Must be initialized properly after construction to point to the
+ /// ParticleSystemRenderer.
+ ListNode<Object> cachedMeshUserNode[kMaxNumParticleMeshes];
+};
+
+
+enum ParticleSystemRenderMode {
+ kSRMBillboard = 0,
+ kSRMStretch3D = 1,
+ kSRMBillboardFixedHorizontal = 2,
+ kSRMBillboardFixedVertical = 3,
+ kSRMMesh = 4,
+};
+
+enum ParticleSystemSortMode
+{
+ kSSMNone,
+ kSSMByDistance,
+ kSSMYoungestFirst,
+ kSSMOldestFirst,
+};
+
+struct ParticleSystemParticles;
+struct ParticleSystemParticlesTempData;
+class ParticleSystem;
+struct ParticleSystemGeomConstInputData;
+class ParticleSystemRenderer : public Renderer {
+public:
+ REGISTER_DERIVED_CLASS (ParticleSystemRenderer, Renderer)
+ DECLARE_OBJECT_SERIALIZE (ParticleSystemRenderer)
+ static void InitializeClass ();
+
+ ParticleSystemRenderer (MemLabelId label, ObjectCreationMode mode);
+ // ParticleSystemRenderer(); declared-by-macro
+
+ virtual void Render (int materialIndex, const ChannelAssigns& channels);
+ static void RenderMultiple (const BatchInstanceData* instances, size_t count, const ChannelAssigns& channels);
+
+ virtual void GetLocalAABB (AABB& result);
+ virtual void GetWorldAABB (AABB& result);
+ virtual float GetSortingFudge () const;
+
+ virtual void CheckConsistency ();
+ virtual void Reset ();
+
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ void Update (const AABB& aabb);
+ void UpdateLocalAABB();
+
+ virtual void RendererBecameVisible();
+ virtual void RendererBecameInvisible();
+
+ static void SetUsesAxisOfRotationRec(ParticleSystem& system, bool first);
+ static void CombineBoundsRec(ParticleSystem& shuriken, MinMaxAABB& aabb, bool first);
+
+ GET_SET_DIRTY (ParticleSystemRenderMode, RenderMode, m_Data.renderMode) ;
+ GET_SET_DIRTY (ParticleSystemSortMode, SortMode, m_Data.sortMode) ;
+ GET_SET_DIRTY (float, MaxParticleSize, m_Data.maxParticleSize) ;
+ GET_SET_DIRTY (float, CameraVelocityScale, m_Data.cameraVelocityScale) ;
+ GET_SET_DIRTY (float, VelocityScale, m_Data.velocityScale) ;
+ GET_SET_DIRTY (float, LengthScale, m_Data.lengthScale) ;
+ void SetMesh (PPtr<Mesh> mesh) { m_Mesh[0] = mesh; SetDirty(); UpdateCachedMesh (); }
+ PPtr<Mesh> GetMesh () const { return m_Mesh[0]; }
+
+ const PPtr<Mesh>* GetMeshes () const { return m_Mesh; }
+ const ParticleSystemRendererData& GetData() const { return m_Data; }
+
+#if UNITY_EDITOR
+ bool GetEditorEnabled() const { return m_EditorEnabled; }
+ void SetEditorEnabled(bool value) { m_EditorEnabled = value; }
+#endif
+
+ // For mesh we use world space rotation, else screen space
+ bool GetScreenSpaceRotation() const { return m_Data.renderMode != kSRMMesh; };
+private:
+ // from Renderer
+ virtual void UpdateRenderer ();
+ void UpdateCachedMesh ();
+ void OnDidDeleteMesh (Mesh* mesh);
+
+private:
+ static void CalculateTotalParticleCount(UInt32& totalNumberOfParticles, ParticleSystem& shuriken, bool first);
+ static void CombineParticleBuffersRec(int& offset, ParticleSystemParticles& particles, ParticleSystemParticlesTempData& psTemp, ParticleSystem& shuriken, bool first, bool needsAxisOfRotation);
+
+ static void RenderBatch (const BatchInstanceData* instances, size_t count, size_t numParticles, const ChannelAssigns& channels);
+ static void RenderInternal (ParticleSystem& system, const ParticleSystemRenderer& renderer, const ChannelAssigns& channels, ParticleSystemVertex* vbPtr, UInt32 particleCountTotal);
+
+ ParticleSystemRendererData m_Data;
+ dynamic_array<UInt16> m_CachedIndexBuffer[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ PPtr<Mesh> m_Mesh[ParticleSystemRendererData::kMaxNumParticleMeshes];
+
+ AABB m_LocalSpaceAABB;
+
+#if UNITY_EDITOR
+ bool m_EditorEnabled;
+#endif
+};
+
+#endif // SHURIKENRENDERER_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp
new file mode 100644
index 0000000..47db7ee
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp
@@ -0,0 +1,29 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Testing/TestFixtures.h"
+
+
+SUITE (ParticleSystemRendererTests)
+{
+ typedef ObjectTestFixture<ParticleSystemRenderer> Fixture;
+
+ TEST_FIXTURE (Fixture, DeletingMeshClearsOutCachedMeshPointers)
+ {
+ // Arrange.
+ PPtr<Mesh> mesh (NEW_OBJECT_RESET_AND_AWAKE (Mesh));
+ m_ObjectUnderTest->SetMesh (mesh);
+
+ // Act.
+ DestroySingleObject (mesh);
+
+ // Assert.
+ CHECK (m_ObjectUnderTest->GetData().cachedMesh[0] == NULL);
+ }
+}
+
+#endif
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp
new file mode 100644
index 0000000..032cd19
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp
@@ -0,0 +1,113 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemUtils.h"
+#include "ParticleSystem.h"
+#include "ParticleSystemCurves.h"
+#include "Modules/ParticleSystemModule.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/ParticleSystem/ParticleSystemEditor.h"
+#endif
+
+UInt32 randomSeed = 0x1337;
+
+UInt32 GetGlobalRandomSeed ()
+{
+ return ++randomSeed;
+}
+
+void ResetGlobalRandomSeed ()
+{
+ randomSeed = 0x1337;
+}
+
+Vector2f CalculateInverseLerpOffsetScale (const Vector2f& range)
+{
+ Assert (range.x < range.y);
+ float scale = 1.0F / (range.y - range.x);
+ return Vector2f (scale, -range.x * scale);
+}
+
+void CalculatePositionAndVelocity(Vector3f& initialPosition, Vector3f& initialVelocity, const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const size_t index)
+{
+ initialPosition = ps.position[index];
+ initialVelocity = ps.velocity[index] + ps.animatedVelocity[index];
+ if(roState.useLocalSpace)
+ {
+ // If we are in local space, transform to world space to make independent of this emitters transform
+ initialPosition = state.localToWorld.MultiplyPoint3(initialPosition);
+ initialVelocity = state.localToWorld.MultiplyVector3(initialVelocity);
+ }
+}
+
+void KillParticle(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t index, size_t& particleCount)
+{
+ Assert(particleCount > 0);
+
+ for(int i = 0; i < state.numCachedSubDataDeath; i++)
+ {
+ ParticleSystemEmissionState emissionState;
+ RecordEmit(emissionState, state.cachedSubDataDeath[i], roState, state, ps, kParticleSystemSubTypeDeath, i, index, 0.0f, 0.0001f, 1.0f);
+ }
+
+ ps.element_assign (index, particleCount - 1);
+ --particleCount;
+
+}
+
+void RecordEmit(ParticleSystemEmissionState& emissionState, const ParticleSystemSubEmitterData& subEmitterData, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, ParticleSystemSubType type, int subEmitterIndex, size_t particleIndex, float t, float dt, float length)
+{
+ size_t numContinuous = 0;
+ Vector3f initialPosition;
+ Vector3f initialVelocity;
+ CalculatePositionAndVelocity(initialPosition, initialVelocity, roState, state, ps, particleIndex);
+ int amountOfParticlesToEmit = ParticleSystem::EmitFromData (emissionState, numContinuous, subEmitterData.emissionData, initialVelocity, t, std::min(t + dt, length), dt, length);
+ if(amountOfParticlesToEmit)
+ {
+ if(!state.recordSubEmits)
+ ParticleSystem::Emit(*subEmitterData.emitter, SubEmitterEmitCommand(emissionState, initialPosition, initialVelocity, type, subEmitterIndex, amountOfParticlesToEmit, numContinuous, t, dt), kParticleSystemEMStaging);
+ else if(!state.subEmitterCommandBuffer.IsFull())
+ state.subEmitterCommandBuffer.AddCommand(emissionState, initialPosition, initialVelocity, type, subEmitterIndex, amountOfParticlesToEmit, numContinuous, t, dt);
+ }
+}
+
+bool GetTransformationMatrix(Matrix4x4f& output, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld)
+{
+ if(isCurveInWorld != isSystemInWorld)
+ {
+ if(isSystemInWorld)
+ output = localToWorld;
+ else
+ Matrix4x4f::Invert_General3D(localToWorld, output);
+ return true;
+ }
+ else
+ {
+ output = Matrix4x4f::identity;
+ return false;
+ }
+}
+
+bool GetTransformationMatrices(Matrix4x4f& output, Matrix4x4f& outputInverse, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld)
+{
+ if(isCurveInWorld != isSystemInWorld)
+ {
+ if(isSystemInWorld)
+ {
+ output = localToWorld;
+ Matrix4x4f::Invert_General3D(localToWorld, outputInverse);
+ }
+ else
+ {
+ Matrix4x4f::Invert_General3D(localToWorld, output);
+ outputInverse = localToWorld;
+ }
+ return true;
+ }
+ else
+ {
+ output = Matrix4x4f::identity;
+ outputInverse = Matrix4x4f::identity;
+ return false;
+ }
+}
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h
new file mode 100644
index 0000000..45ec0a3
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h
@@ -0,0 +1,48 @@
+#ifndef SHURIKENUTILS_H
+#define SHURIKENUTILS_H
+
+#include "ParticleSystemCommon.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Math/Random/Random.h"
+
+class Matrix4x4f;
+struct ParticleSystemParticles;
+struct ParticleSystemReadOnlyState;
+struct ParticleSystemState;
+struct ParticleSystemEmissionState;
+struct ParticleSystemSubEmitterData;
+
+inline float InverseLerpFast01 (const Vector2f& scaleOffset, float v)
+{
+ return clamp01 (v * scaleOffset.x + scaleOffset.y);
+}
+
+inline float GenerateRandom(UInt32 randomIn)
+{
+ Rand rand(randomIn);
+ return Random01(rand);
+}
+
+inline void GenerateRandom3(Vector3f& randomOut, UInt32 randomIn)
+{
+ Rand rand(randomIn);
+ randomOut.x = Random01(rand);
+ randomOut.y = Random01(rand);
+ randomOut.z = Random01(rand);
+}
+
+inline UInt8 GenerateRandomByte (UInt32 seed)
+{
+ Rand rand (seed);
+ return Rand::GetByteFromInt (rand.Get ());
+}
+
+UInt32 GetGlobalRandomSeed ();
+void ResetGlobalRandomSeed ();
+Vector2f CalculateInverseLerpOffsetScale (const Vector2f& range);
+void KillParticle(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t index, size_t& particleCount);
+void RecordEmit(ParticleSystemEmissionState& emissionState, const ParticleSystemSubEmitterData& subEmitterData, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, ParticleSystemSubType type, int subEmitterIndex, size_t particleIndex, float t, float dt, float length);
+bool GetTransformationMatrix(Matrix4x4f& output, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld);
+bool GetTransformationMatrices(Matrix4x4f& output, Matrix4x4f& outputInverse, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld);
+
+#endif // SHURIKENUTILS_H
diff --git a/Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp b/Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp
new file mode 100644
index 0000000..f20b0ac
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp
@@ -0,0 +1,405 @@
+#include "UnityPrefix.h"
+#include "PolynomialCurve.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Math/Polynomials.h"
+#include "Runtime/Math/AnimationCurve.h"
+
+static void DoubleIntegrateSegment (float* coeff)
+{
+ coeff[0] /= 20.0F;
+ coeff[1] /= 12.0F;
+ coeff[2] /= 6.0F;
+ coeff[3] /= 2.0F;
+}
+
+static void IntegrateSegment (float* coeff)
+{
+ coeff[0] /= 4.0F;
+ coeff[1] /= 3.0F;
+ coeff[2] /= 2.0F;
+ coeff[3] /= 1.0F;
+}
+
+void CalculateMinMax(Vector2f& minmax, float value)
+{
+ minmax.x = std::min(minmax.x, value);
+ minmax.y = std::max(minmax.y, value);
+}
+
+void ConstrainToPolynomialCurve (AnimationCurve& curve)
+{
+ const int max = OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount;
+
+ // Maximum 3 keys
+ if (curve.GetKeyCount () > max)
+ curve.RemoveKeys(curve.begin() + max, curve.end());
+
+ // Clamp begin and end to 0...1 range
+ if (curve.GetKeyCount () >= 2)
+ {
+ curve.GetKey(0).time = 0;
+ curve.GetKey(curve.GetKeyCount ()-1).time = 1;
+ }
+}
+
+bool IsValidPolynomialCurve (const AnimationCurve& curve)
+{
+ // Maximum 3 keys
+ if (curve.GetKeyCount () > OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount)
+ return false;
+ // One constant key can always be representated
+ else if (curve.GetKeyCount () <= 1)
+ return true;
+ // First and last keyframe must be at 0 and 1 time
+ else
+ {
+ float beginTime = curve.GetKey(0).time;
+ float endTime = curve.GetKey(curve.GetKeyCount ()-1).time;
+
+ return CompareApproximately(beginTime, 0.0F, 0.0001F) && CompareApproximately(endTime, 1.0F, 0.0001F);
+ }
+}
+
+void SetPolynomialCurveToValue (AnimationCurve& a, OptimizedPolynomialCurve& c, float value)
+{
+ AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, value), AnimationCurve::Keyframe(1.0f, value) };
+ a.Assign(keys, keys + 2);
+ c.BuildOptimizedCurve(a, 1.0f);
+}
+
+void SetPolynomialCurveToLinear (AnimationCurve& a, OptimizedPolynomialCurve& c)
+{
+ AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
+ keys[0].inSlope = 0.0f; keys[0].outSlope = 1.0f;
+ keys[1].inSlope = 1.0f; keys[1].outSlope = 0.0f;
+ a.Assign(keys, keys + 2);
+ c.BuildOptimizedCurve(a, 1.0f);
+}
+
+bool OptimizedPolynomialCurve::BuildOptimizedCurve (const AnimationCurve& editorCurve, float scale)
+{
+ if (!IsValidPolynomialCurve(editorCurve))
+ return false;
+
+ const size_t keyCount = editorCurve.GetKeyCount ();
+
+ timeValue = 1.0F;
+ memset(segments, 0, sizeof(segments));
+
+ // Handle corner case 1 & 0 keyframes
+ if (keyCount == 0)
+ ;
+ else if (keyCount == 1)
+ {
+ // Set constant value coefficient
+ for (int i=0;i<kSegmentCount;i++)
+ segments[i].coeff[3] = editorCurve.GetKey(0).value * scale;
+ }
+ else
+ {
+ float segmentStartTime[kSegmentCount];
+
+ for (int i=0;i<kSegmentCount;i++)
+ {
+ bool hasSegment = i+1 < keyCount;
+ if (hasSegment)
+ {
+ AnimationCurve::Cache cache;
+ editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
+
+ memcpy(segments[i].coeff, cache.coeff, sizeof(Polynomial));
+ segmentStartTime[i] = editorCurve.GetKey(i).time;
+ }
+ else
+ {
+ memcpy(segments[i].coeff, segments[i-1].coeff, sizeof(Polynomial));
+ segmentStartTime[i] = 1.0F;//timeValue[i-1];
+ }
+ }
+
+ // scale curve
+ for (int i=0;i<kSegmentCount;i++)
+ {
+ segments[i].coeff[0] *= scale;
+ segments[i].coeff[1] *= scale;
+ segments[i].coeff[2] *= scale;
+ segments[i].coeff[3] *= scale;
+ }
+
+ // Timevalue 0 is always 0.0F. No need to store it.
+ timeValue = segmentStartTime[1];
+
+ #if !UNITY_RELEASE
+ // Check that UI editor curve matches polynomial curve except if the
+ // scale happens to be infinite (trying to subtract infinity values from
+ // each other yields NaN with IEEE floats).
+ if (scale != std::numeric_limits<float>::infinity () &&
+ scale != -std::numeric_limits<float>::infinity())
+ {
+ for (int i=0;i<=50;i++)
+ {
+ // The very last element at 1.0 can be different when using step curves.
+ // The AnimationCurve implementation will sample the position of the last key.
+ // The OptimizedPolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
+ // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
+ // thus we just don't do the automatic assert when the curves are not in sync in that case.
+ float t = std::min(i / 50.0F, 0.99999F);
+ float dif;
+
+ DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
+ }
+ }
+ #endif
+ }
+
+ return true;
+}
+
+void OptimizedPolynomialCurve::Integrate ()
+{
+ for (int i=0;i<kSegmentCount;i++)
+ IntegrateSegment(segments[i].coeff);
+}
+
+void OptimizedPolynomialCurve::DoubleIntegrate ()
+{
+ Polynomial velocity0 = segments[0];
+ IntegrateSegment (velocity0.coeff);
+
+ velocityValue = Polynomial::EvalSegment(timeValue, velocity0.coeff) * timeValue;
+
+ for (int i=0;i<kSegmentCount;i++)
+ DoubleIntegrateSegment(segments[i].coeff);
+}
+
+Vector2f OptimizedPolynomialCurve::FindMinMaxDoubleIntegrated() const
+{
+ // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
+ // TODO: Find all roots of quartic polynomial
+ Vector2f result = Vector2f::zero;
+ const int numSteps = 20;
+ const float delta = 1.0f / float(numSteps);
+ float acc = delta;
+ for(int i = 0; i < numSteps; i++)
+ {
+ CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
+ acc += delta;
+ }
+ return result;
+}
+
+// Find the maximum of the integrated curve (x: min, y: max)
+Vector2f OptimizedPolynomialCurve::FindMinMaxIntegrated() const
+{
+ Vector2f result = Vector2f::zero;
+
+ float start[kSegmentCount] = {0.0f, timeValue};
+ float end[kSegmentCount] = {timeValue, 1.0f};
+ for(int i = 0; i < kSegmentCount; i++)
+ {
+ // Differentiate coefficients
+ float a = 4.0f*segments[i].coeff[0];
+ float b = 3.0f*segments[i].coeff[1];
+ float c = 2.0f*segments[i].coeff[2];
+ float d = 1.0f*segments[i].coeff[3];
+
+ float roots[3];
+ int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
+ for(int r = 0; r < numRoots; r++)
+ {
+ float root = roots[r] + start[i];
+ if((root >= start[i]) && (root < end[i]))
+ CalculateMinMax(result, EvaluateIntegrated(root));
+ }
+
+ // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
+ CalculateMinMax(result, EvaluateIntegrated(end[i]));
+ }
+ return result;
+}
+
+// Find the maximum of a double integrated curve (x: min, y: max)
+Vector2f PolynomialCurve::FindMinMaxDoubleIntegrated() const
+{
+ // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
+ // TODO: Find all roots of quartic polynomial
+ Vector2f result = Vector2f::zero;
+ const int numSteps = 20;
+ const float delta = 1.0f / float(numSteps);
+ float acc = delta;
+ for(int i = 0; i < numSteps; i++)
+ {
+ CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
+ acc += delta;
+ }
+ return result;
+}
+
+Vector2f PolynomialCurve::FindMinMaxIntegrated() const
+{
+ Vector2f result = Vector2f::zero;
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ // Differentiate coefficients
+ float a = 4.0f*segments[i].coeff[0];
+ float b = 3.0f*segments[i].coeff[1];
+ float c = 2.0f*segments[i].coeff[2];
+ float d = 1.0f*segments[i].coeff[3];
+
+ float roots[3];
+ int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
+ for(int r = 0; r < numRoots; r++)
+ {
+ float root = roots[r] + prevTimeValue;
+ if((root >= prevTimeValue) && (root < times[i]))
+ CalculateMinMax(result, EvaluateIntegrated(root));
+ }
+
+ // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
+ CalculateMinMax(result, EvaluateIntegrated(times[i]));
+ prevTimeValue = times[i];
+ }
+ return result;
+}
+
+bool PolynomialCurve::IsValidCurve(const AnimationCurve& editorCurve)
+{
+ int keyCount = editorCurve.GetKeyCount();
+ int segmentCount = keyCount - 1;
+ if(editorCurve.GetKey(0).time != 0.0f)
+ segmentCount++;
+ if(editorCurve.GetKey(keyCount-1).time != 1.0f)
+ segmentCount++;
+ return segmentCount <= kMaxNumSegments;
+}
+
+bool PolynomialCurve::BuildCurve(const AnimationCurve& editorCurve, float scale)
+{
+ int keyCount = editorCurve.GetKeyCount();
+ segmentCount = 1;
+
+ const float kMaxTime = 1.01f;
+
+ memset(segments, 0, sizeof(segments));
+ memset(integrationCache, 0, sizeof(integrationCache));
+ memset(doubleIntegrationCache, 0, sizeof(doubleIntegrationCache));
+ memset(times, 0, sizeof(times));
+ times[0] = kMaxTime;
+
+ // Handle corner case 1 & 0 keyframes
+ if (keyCount == 0)
+ ;
+ else if (keyCount == 1)
+ {
+ // Set constant value coefficient
+ segments[0].coeff[3] = editorCurve.GetKey(0).value * scale;
+ }
+ else
+ {
+ segmentCount = keyCount - 1;
+ int segmentOffset = 0;
+
+ // Add extra key to start if it doesn't match up
+ if(editorCurve.GetKey(0).time != 0.0f)
+ {
+ segments[0].coeff[3] = editorCurve.GetKey(0).value;
+ times[0] = editorCurve.GetKey(0).time;
+ segmentOffset = 1;
+ }
+
+ for (int i = 0;i<segmentCount;i++)
+ {
+ DebugAssert(i+1 < keyCount);
+ AnimationCurve::Cache cache;
+ editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
+ memcpy(segments[i+segmentOffset].coeff, cache.coeff, 4 * sizeof(float));
+ times[i+segmentOffset] = editorCurve.GetKey(i+1).time;
+ }
+ segmentCount += segmentOffset;
+
+ // Add extra key to start if it doesn't match up
+ if(editorCurve.GetKey(keyCount-1).time != 1.0f)
+ {
+ segments[segmentCount].coeff[3] = editorCurve.GetKey(keyCount-1).value;
+ segmentCount++;
+ }
+
+ // Fixup last key time value
+ times[segmentCount-1] = kMaxTime;
+
+ for (int i = 0;i<segmentCount;i++)
+ {
+ segments[i].coeff[0] *= scale;
+ segments[i].coeff[1] *= scale;
+ segments[i].coeff[2] *= scale;
+ segments[i].coeff[3] *= scale;
+ }
+ }
+
+ DebugAssert(segmentCount <= kMaxNumSegments);
+
+#if !UNITY_RELEASE
+ // Check that UI editor curve matches polynomial curve
+ for (int i=0;i<=10;i++)
+ {
+ // The very last element at 1.0 can be different when using step curves.
+ // The AnimationCurve implementation will sample the position of the last key.
+ // The PolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
+ // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
+ // thus we just don't do the automatic assert when the curves are not in sync in that case.
+ float t = std::min(i / 50.0F, 0.99999F);
+ float dif;
+ DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
+ }
+#endif
+ return true;
+}
+
+void GenerateIntegrationCache(PolynomialCurve& curve)
+{
+ curve.integrationCache[0] = 0.0f;
+ float prevTimeValue0 = curve.times[0];
+ float prevTimeValue1 = 0.0f;
+ for (int i=1;i<curve.segmentCount;i++)
+ {
+ float coeff[4];
+ memcpy(coeff, curve.segments[i-1].coeff, 4*sizeof(float));
+ IntegrateSegment (coeff);
+ float time = prevTimeValue0 - prevTimeValue1;
+ curve.integrationCache[i] = curve.integrationCache[i-1] + Polynomial::EvalSegment(time, coeff) * time;
+ prevTimeValue1 = prevTimeValue0;
+ prevTimeValue0 = curve.times[i];
+ }
+}
+
+// Expects double integrated segments and valid integration cache
+void GenerateDoubleIntegrationCache(PolynomialCurve& curve)
+{
+ float sum = 0.0f;
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < curve.segmentCount; i++)
+ {
+ curve.doubleIntegrationCache[i] = sum;
+ float time = curve.times[i] - prevTimeValue;
+ time = std::max(time, 0.0f);
+ sum += Polynomial::EvalSegment(time, curve.segments[i].coeff) * time * time + curve.integrationCache[i] * time;
+ prevTimeValue = curve.times[i];
+ }
+}
+
+void PolynomialCurve::Integrate ()
+{
+ GenerateIntegrationCache(*this);
+ for (int i=0;i<segmentCount;i++)
+ IntegrateSegment(segments[i].coeff);
+}
+
+void PolynomialCurve::DoubleIntegrate ()
+{
+ GenerateIntegrationCache(*this);
+ for (int i=0;i<segmentCount;i++)
+ DoubleIntegrateSegment(segments[i].coeff);
+ GenerateDoubleIntegrationCache(*this);
+}
diff --git a/Runtime/Graphics/ParticleSystem/PolynomialCurve.h b/Runtime/Graphics/ParticleSystem/PolynomialCurve.h
new file mode 100644
index 0000000..d9e7270
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/PolynomialCurve.h
@@ -0,0 +1,229 @@
+#ifndef POLYONOMIAL_CURVE_H
+#define POLYONOMIAL_CURVE_H
+
+template<class T>
+class AnimationCurveTpl;
+typedef AnimationCurveTpl<float> AnimationCurve;
+class Vector2f;
+
+struct Polynomial
+{
+ static float EvalSegment (float t, const float* coeff)
+ {
+ return (t * (t * (t * coeff[0] + coeff[1]) + coeff[2])) + coeff[3];
+ }
+
+ float coeff[4];
+};
+
+// Smaller, optimized version
+struct OptimizedPolynomialCurve
+{
+ enum { kMaxPolynomialKeyframeCount = 3, kSegmentCount = kMaxPolynomialKeyframeCount-1, };
+
+ Polynomial segments[kSegmentCount];
+
+ float timeValue;
+ float velocityValue;
+
+ // Evaluate double integrated Polynomial curve.
+ // Example: position = EvaluateDoubleIntegrated (normalizedTime) * startEnergy^2
+ // Use DoubleIntegrate function to for example turn a force curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateDoubleIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+ float res0, res1;
+
+ // All segments are added together. At t = 0, the integrated curve is always zero.
+
+ // 0 segment is sampled up to the 1 keyframe
+ // First key is always assumed to be at 0 time
+ float t1 = std::min(t, timeValue);
+
+ // 1 segment is sampled from 1 key to 2 key
+ // Last key is always assumed to be at 1 time
+ float t2 = std::max(0.0F, t - timeValue);
+
+ res0 = Polynomial::EvalSegment(t1, segments[0].coeff) * t1 * t1;
+ res1 = Polynomial::EvalSegment(t2, segments[1].coeff) * t2 * t2;
+
+ float finalResult = res0 + res1;
+
+ // Add velocity of previous segments
+ finalResult += velocityValue * std::max(t - timeValue, 0.0F);
+
+ return finalResult;
+ }
+
+ // Evaluate integrated Polynomial curve.
+ // Example: position = EvaluateIntegrated (normalizedTime) * startEnergy
+ // Use Integrate function to for example turn a velocity curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+ float res0, res1;
+
+ // All segments are added together. At t = 0, the integrated curve is always zero.
+
+ // 0 segment is sampled up to the 1 keyframe
+ // First key is always assumed to be at 0 time
+ float t1 = std::min(t, timeValue);
+
+ // 1 segment is sampled from 1 key to 2 key
+ // Last key is always assumed to be at 1 time
+ float t2 = std::max(0.0F, t - timeValue);
+
+ res0 = Polynomial::EvalSegment(t1, segments[0].coeff) * t1;
+ res1 = Polynomial::EvalSegment(t2, segments[1].coeff) * t2;
+
+ return (res0 + res1);
+ }
+
+ // Evaluate the curve
+ // extects that t is in the 0...1 range
+ float Evaluate (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float res0 = Polynomial::EvalSegment(t, segments[0].coeff);
+ float res1 = Polynomial::EvalSegment(t - timeValue, segments[1].coeff);
+
+ float result;
+ if (t > timeValue)
+ result = res1;
+ else
+ result = res0;
+
+ return result;
+ }
+
+ // Find the maximum of a double integrated curve (x: min, y: max)
+ Vector2f FindMinMaxDoubleIntegrated() const;
+
+ // Find the maximum of the integrated curve (x: min, y: max)
+ Vector2f FindMinMaxIntegrated() const;
+
+ // Precalculates polynomials from the animation curve and a scale factor
+ bool BuildOptimizedCurve (const AnimationCurve& editorCurve, float scale);
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateIntegrated to evaluate the curve
+ void Integrate ();
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateDoubleIntegrated to evaluate the curve
+ void DoubleIntegrate ();
+
+ // Add a constant force to a velocity curve
+ // Assumes that you have already called Integrate on the velocity curve.
+ void AddConstantForceToVelocityCurve (float gravity)
+ {
+ for (int i=0;i<kSegmentCount;i++)
+ segments[i].coeff[1] += 0.5F * gravity;
+ }
+};
+
+// Bigger, not so optimized version
+struct PolynomialCurve
+{
+ enum{ kMaxNumSegments = 8 };
+
+ Polynomial segments[kMaxNumSegments]; // Cached polynomial coefficients
+ float integrationCache[kMaxNumSegments]; // Cache of integrated values up until start of segments
+ float doubleIntegrationCache[kMaxNumSegments]; // Cache of double integrated values up until start of segments
+ float times[kMaxNumSegments]; // Time value for end of segment
+
+ int segmentCount;
+
+ // Find the maximum of a double integrated curve (x: min, y: max)
+ Vector2f FindMinMaxDoubleIntegrated() const;
+
+ // Find the maximum of the integrated curve (x: min, y: max)
+ Vector2f FindMinMaxIntegrated() const;
+
+ // Precalculates polynomials from the animation curve and a scale factor
+ bool BuildCurve(const AnimationCurve& editorCurve, float scale);
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateIntegrated to evaluate the curve
+ void Integrate ();
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateDoubleIntegrated to evaluate the curve
+ void DoubleIntegrate ();
+
+ // Evaluates if it is possible to represent animation curve as PolynomialCurve
+ static bool IsValidCurve(const AnimationCurve& editorCurve);
+
+ // Evaluate double integrated Polynomial curve.
+ // Example: position = EvaluateDoubleIntegrated (normalizedTime) * startEnergy^2
+ // Use DoubleIntegrate function to for example turn a force curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateDoubleIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ if(t <= times[i])
+ {
+ const float time = t - prevTimeValue;
+ return doubleIntegrationCache[i] + integrationCache[i] * time + Polynomial::EvalSegment(time, segments[i].coeff) * time * time;
+ }
+ prevTimeValue = times[i];
+ }
+ DebugAssert(!"PolyCurve: Outside segment range!");
+ return 1.0f;
+ }
+
+ // Evaluate integrated Polynomial curve.
+ // Example: position = EvaluateIntegrated (normalizedTime) * startEnergy
+ // Use Integrate function to for example turn a velocity curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ if(t <= times[i])
+ {
+ const float time = t - prevTimeValue;
+ return integrationCache[i] + Polynomial::EvalSegment(time, segments[i].coeff) * time;
+ }
+ prevTimeValue = times[i];
+ }
+ DebugAssert(!"PolyCurve: Outside segment range!");
+ return 1.0f;
+ }
+
+ // Evaluate the curve
+ // extects that t is in the 0...1 range
+ float Evaluate(float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ if(t <= times[i])
+ return Polynomial::EvalSegment(t - prevTimeValue, segments[i].coeff);
+ prevTimeValue = times[i];
+ }
+ DebugAssert(!"PolyCurve: Outside segment range!");
+ return 1.0f;
+ }
+};
+
+
+void SetPolynomialCurveToValue (AnimationCurve& a, OptimizedPolynomialCurve& c, float value);
+void SetPolynomialCurveToLinear (AnimationCurve& a, OptimizedPolynomialCurve& c);
+void ConstrainToPolynomialCurve (AnimationCurve& curve);
+bool IsValidPolynomialCurve (const AnimationCurve& curve);
+void CalculateMinMax(Vector2f& minmax, float value);
+
+#endif // POLYONOMIAL_CURVE_H
diff --git a/Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp b/Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp
new file mode 100644
index 0000000..2a74710
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp
@@ -0,0 +1,259 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "Runtime/Animation/AnimationCurveUtility.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Graphics/ParticleSystem/PolynomialCurve.h"
+
+static float PhysicsSimulate (float gravity, float velocity, float time)
+{
+ return velocity * time + gravity * time * time * 0.5F;
+}
+
+SUITE (PolynomialCurveTests)
+{
+TEST (PolynomialCurve_ConstantIntegrate)
+{
+ AnimationCurve editorCurve;
+ OptimizedPolynomialCurve curve;
+ PolynomialCurve polyCurve;
+
+ SetPolynomialCurveToValue(editorCurve, curve, 1.0f);
+ curve.Integrate();
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.Integrate();
+
+ CHECK_CLOSE(0.0F, curve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.5F, curve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(1.0F, curve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_CLOSE(0.0F, polyCurve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.5F, polyCurve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(1.0F, polyCurve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 1.0f), curve.FindMinMaxIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 1.01f), polyCurve.FindMinMaxIntegrated(), 0.0001F));
+
+
+ SetPolynomialCurveToLinear(editorCurve, curve);
+ curve.Integrate();
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.Integrate();
+
+ CHECK_CLOSE(0.0F, curve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.125F, curve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(0.5F, curve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_CLOSE(0.0F, polyCurve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.125F, polyCurve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(0.5F, polyCurve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), curve.FindMinMaxIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.51005f), polyCurve.FindMinMaxIntegrated(), 0.0001F));
+}
+
+// @TODO: Add generic poly
+TEST (PolynomialCurve_ConstantDoubleIntegrate)
+{
+ AnimationCurve editorCurve;
+ OptimizedPolynomialCurve curve;
+ PolynomialCurve polyCurve;
+
+ SetPolynomialCurveToValue(editorCurve, curve, 1.0f);
+ curve.DoubleIntegrate();
+
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.DoubleIntegrate();
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), curve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), curve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), curve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), curve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), curve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), polyCurve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), polyCurve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), polyCurve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), polyCurve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), polyCurve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), curve.FindMinMaxDoubleIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), polyCurve.FindMinMaxDoubleIntegrated(), 0.0001F));
+
+ AnimationCurve::Keyframe keys[3] = { AnimationCurve::Keyframe(0.0f, 1.0f), AnimationCurve::Keyframe(0.5f, 1.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
+ editorCurve.Assign(keys, keys + 3);
+ curve.BuildOptimizedCurve(editorCurve, 1);
+ curve.DoubleIntegrate();
+
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.DoubleIntegrate();
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), curve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), curve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), curve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), curve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), curve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), polyCurve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), polyCurve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), polyCurve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), polyCurve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), polyCurve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), curve.FindMinMaxDoubleIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), polyCurve.FindMinMaxDoubleIntegrated(), 0.0001F));
+}
+
+static float EvaluateGravityIntegrated (OptimizedPolynomialCurve& gravityCurve, float time, float maximumRange)
+{
+ float res = gravityCurve.EvaluateDoubleIntegrated(time / maximumRange) * maximumRange * maximumRange;
+ return res;
+}
+
+TEST (PolynomialCurve_GravityIntegrate)
+{
+ const float kGravity = -9.81f;
+ AnimationCurve editorCurve;
+ OptimizedPolynomialCurve gravityCurve;
+ SetPolynomialCurveToValue(editorCurve, gravityCurve, kGravity);
+ gravityCurve.DoubleIntegrate();
+
+ const float kMaxRange = 5.0F;
+
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 0.0F), EvaluateGravityIntegrated(gravityCurve, 0.0F, kMaxRange), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 0.5F), EvaluateGravityIntegrated(gravityCurve, 0.5F, kMaxRange), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 1.0F), EvaluateGravityIntegrated(gravityCurve, 1.0F, kMaxRange), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 5.0F), EvaluateGravityIntegrated(gravityCurve, 5.0F, kMaxRange), 0.0001F);
+}
+
+float TriangleShapeIntegralFirstHalf (float x)
+{
+ return (2.0F / 3.0F) *x*x*x - 0.5F*x*x;
+}
+
+// Test that a simple triangle shape 0,-1 to 0.5,1 to 1,-1
+// Gives expected results during double integration
+TEST (PolynomialCurve_TriangleShapeDoubleIntegrate)
+{
+ OptimizedPolynomialCurve curve;
+ AnimationCurve::Keyframe keys[3] = { AnimationCurve::Keyframe(0.0f, -1.0f), AnimationCurve::Keyframe(0.5f, 1.0f), AnimationCurve::Keyframe(1.0f, -1.0f) };
+ AnimationCurve editorCurve;
+ editorCurve.Assign(keys, keys + 3);
+
+ RecalculateSplineSlopeLinear(editorCurve);
+
+ curve.BuildOptimizedCurve(editorCurve, 1.0F);
+
+ CHECK_CLOSE(-1, curve.Evaluate(0), 0.0001F);
+ CHECK_CLOSE(0, curve.Evaluate(0.25F), 0.0001F);
+ CHECK_CLOSE(1.0F, curve.Evaluate(0.5F), 0.0001F);
+ CHECK_CLOSE(0.0F, curve.Evaluate(0.75F), 0.0001F);
+ CHECK_CLOSE(-1, curve.Evaluate(1), 0.0001F);
+
+ curve.DoubleIntegrate();
+
+ CHECK_CLOSE(TriangleShapeIntegralFirstHalf(0), EvaluateGravityIntegrated(curve, 0.0F , 1.0F), 0.0001F);
+ CHECK_CLOSE(TriangleShapeIntegralFirstHalf(0.25F), EvaluateGravityIntegrated(curve, 0.25F, 1.0F), 0.0001F);
+ CHECK_CLOSE(TriangleShapeIntegralFirstHalf(0.5F), EvaluateGravityIntegrated(curve, 0.5F , 1.0F), 0.0001F);
+ CHECK_CLOSE(-0.0208333, EvaluateGravityIntegrated(curve, 0.75F, 1.0F), 0.0001F);
+ CHECK_CLOSE(0, EvaluateGravityIntegrated(curve, 1.0F , 1.0F), 0.0001F);
+}
+
+void CompareIntegrateCurve (const AnimationCurve& curve, const OptimizedPolynomialCurve& integratedCurve)
+{
+ CHECK_CLOSE(0, integratedCurve.EvaluateIntegrated(0), 0.0001F);
+
+ int intervals = 100;
+
+ float sum = 0.0F;
+ for (int i=1;i<intervals;i++)
+ {
+ float t = (float)i / (float)intervals;
+ float dt = 1.0F / (float)intervals;
+
+ float avgValue = curve.Evaluate(t - dt * 0.5F);
+ sum += avgValue * dt;
+
+ float integratedValue = integratedCurve.EvaluateIntegrated(t);
+ CHECK_CLOSE(sum, integratedValue, 0.0001F);
+ }
+}
+
+void CompareDoubleIntegrateCurve (const AnimationCurve& curve, const OptimizedPolynomialCurve& integratedCurve)
+{
+ CHECK_CLOSE(0, integratedCurve.EvaluateIntegrated(0), 0.0001F);
+
+ int intervals = 1000;
+
+ float integratedSum = 0.0F;
+ float sum = 0.0F;
+ for (int i=1;i<intervals;i++)
+ {
+ float t = (float)i / (float)intervals;
+ float dt = 1.0F / (float)intervals;
+
+ float avgValue = curve.Evaluate(t - dt * 0.5F);
+ sum += avgValue * dt;
+ integratedSum += sum * dt;
+
+ float integratedValue = integratedCurve.EvaluateDoubleIntegrated(t);
+ CHECK_CLOSE(integratedSum, integratedValue, 0.001F);
+ }
+}
+
+void CompareIntegrateCurve (const AnimationCurve::Keyframe* keys, int size)
+{
+ AnimationCurve sourceCurve;
+ sourceCurve.Assign(keys, keys + size);
+
+ OptimizedPolynomialCurve curve;
+ AnimationCurve editorCurve;
+ editorCurve = sourceCurve;
+ curve.BuildOptimizedCurve(editorCurve, 1);
+ curve.Integrate();
+
+ CompareIntegrateCurve(sourceCurve, curve);
+}
+
+void CompareDoubleIntegrateCurve (const AnimationCurve::Keyframe* keys, int size)
+{
+ AnimationCurve sourceCurve;
+ sourceCurve.Assign(keys, keys + size);
+
+ OptimizedPolynomialCurve curve;
+ AnimationCurve editorCurve;
+ editorCurve = sourceCurve;
+ curve.BuildOptimizedCurve(editorCurve, 1);
+ curve.DoubleIntegrate();
+
+ CompareDoubleIntegrateCurve(sourceCurve, curve);
+}
+
+TEST (PolynomialCurve_TriangleCurve)
+{
+ AnimationCurve sourceCurve;
+ AnimationCurve::Keyframe keys[3] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(0.5f, 1.0f), AnimationCurve::Keyframe(1.0f, 0.0f) };
+ sourceCurve.Assign(keys, keys + 3);
+ RecalculateSplineSlopeLinear(sourceCurve);
+
+ CompareIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+ CompareDoubleIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+}
+
+
+TEST (PolynomialCurve_LineCurve)
+{
+ AnimationCurve sourceCurve;
+ AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
+ sourceCurve.Assign(keys, keys + 2);
+ RecalculateSplineSlopeLinear(sourceCurve);
+
+ CompareIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+ CompareDoubleIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+}
+}
+
+#endif
diff --git a/Runtime/Graphics/Polygon2D.cpp b/Runtime/Graphics/Polygon2D.cpp
new file mode 100644
index 0000000..9985d65
--- /dev/null
+++ b/Runtime/Graphics/Polygon2D.cpp
@@ -0,0 +1,145 @@
+#include "UnityPrefix.h"
+#include "Polygon2D.h"
+
+#if ENABLE_SPRITES
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Graphics/SpriteFrame.h"
+
+Polygon2D::Polygon2D()
+{
+ Reset();
+}
+
+void Polygon2D::SetPoints (const Vector2f* points, size_t count)
+{
+ m_Paths.resize(1, TPath(kMemPhysics));
+ m_Paths[0].clear();
+ m_Paths[0].assign(points, points + count);
+}
+
+void Polygon2D::SetPathCount (int pathCount)
+{
+ m_Paths.resize(pathCount);
+}
+
+void Polygon2D::SetPath (int index, const TPath& path)
+{
+ if (index == 0 && GetPathCount() == 0)
+ {
+ m_Paths.resize(1);
+ }
+ else if (index >= GetPathCount())
+ {
+ ErrorString("Failed setting path. Index is out of bounds.");
+ return;
+ }
+
+ m_Paths[index] = path;
+}
+
+void Polygon2D::CopyFrom (const Polygon2D& paths)
+{
+ const int pathCount = paths.GetPathCount ();
+ if (pathCount == 0)
+ {
+ m_Paths.clear ();
+ return;
+ }
+
+ // Transfer paths.
+ m_Paths.resize (pathCount);
+ for (int index = 0; index < pathCount; ++index)
+ m_Paths[index] = paths.GetPath (index);
+}
+
+void Polygon2D::GenerateFrom(Sprite* sprite, const Vector2f& offset, float detail, unsigned char alphaTolerance, bool holeDetection)
+{
+ m_Paths.clear();
+ sprite->GenerateOutline(detail, alphaTolerance, holeDetection, m_Paths, 0);
+
+ if (offset.x != 0.0f || offset.y != 0.0f)
+ {
+ for (TPaths::iterator pit = m_Paths.begin(); pit != m_Paths.end(); ++pit)
+ {
+ TPath& path = *pit;
+ for (TPath::iterator it = path.begin(); it != path.end(); ++it)
+ {
+ Vector2f& point = *it;
+ point += offset;
+ }
+ }
+ }
+}
+
+void Polygon2D::Reset()
+{
+ m_Paths.resize(1);
+ m_Paths[0].clear();
+ m_Paths[0].reserve(4);
+ m_Paths[0].push_back(Vector2f(-1, -1));
+ m_Paths[0].push_back(Vector2f(-1, 1));
+ m_Paths[0].push_back(Vector2f( 1, 1));
+ m_Paths[0].push_back(Vector2f( 1, -1));
+}
+
+bool Polygon2D::GetNearestPoint(const Vector2f& point, int& pathIndex, int& pointIndex, float& distance) const
+{
+ bool ret = false;
+ float sqrDist = std::numeric_limits<float>::max();
+
+ const int pathCount = m_Paths.size();
+ for (int i = 0; i < pathCount; ++i)
+ {
+ const Polygon2D::TPath& path = m_Paths[i];
+ const int pointCount = path.size();
+ for (int j = 0; j < pointCount; ++j)
+ {
+ const Vector2f& testPoint = path[j];
+ float d = SqrMagnitude(testPoint - point);
+ if (d < sqrDist)
+ {
+ sqrDist = d;
+ ret = true;
+
+ pathIndex = i;
+ pointIndex = j;
+ distance = SqrtImpl(d);
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool Polygon2D::GetNearestEdge(const Vector2f& point, int& pathIndex, int& pointIndex0, int& pointIndex1, float& distance, bool loop) const
+{
+ bool ret = false;
+ float dist = std::numeric_limits<float>::max();
+
+ const int pathCount = m_Paths.size();
+ for (int i = 0; i < pathCount; ++i)
+ {
+ const Polygon2D::TPath& path = m_Paths[i];
+ const int pointCount = path.size();
+ const int edgeCount = loop ? pointCount : pointCount - 1;
+ for (int p0 = 0; p0 < edgeCount; ++p0)
+ {
+ int p1 = (p0 + 1) % pointCount;
+ float d = DistancePointLine<Vector2f>(point, path[p0], path[p1]);
+ if (d < dist)
+ {
+ dist = d;
+ ret = true;
+
+ pathIndex = i;
+ pointIndex0 = p0;
+ pointIndex1 = p1;
+ distance = d;
+ }
+ }
+ }
+
+ return ret;
+}
+
+#endif //ENABLE_SPRITES
diff --git a/Runtime/Graphics/Polygon2D.h b/Runtime/Graphics/Polygon2D.h
new file mode 100644
index 0000000..76af1cf
--- /dev/null
+++ b/Runtime/Graphics/Polygon2D.h
@@ -0,0 +1,66 @@
+#pragma once
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_SPRITES
+
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Serialize/SerializeUtility.h"
+
+class Sprite;
+
+class Polygon2D
+{
+public:
+ DECLARE_SERIALIZE_NO_PPTR (Polygon2D)
+
+ typedef dynamic_array<Vector2f> TPath;
+ typedef std::vector<TPath> TPaths;
+
+ Polygon2D();
+
+ bool IsEmpty() const { return (m_Paths.size() == 0 || m_Paths[0].size() < 3); }
+ void Reset();
+ void Clear() { m_Paths.clear(); }
+
+ // Simple API
+ void SetPoints (const Vector2f* points, size_t count);
+ const Vector2f* GetPoints() const { return (m_Paths.size() > 0) ? m_Paths[0].data() : NULL; }
+ size_t GetPointCount() const { return (m_Paths.size() > 0) ? m_Paths[0].size() : 0; }
+
+ // Advanced API
+ void SetPathCount(int pathCount);
+ int GetPathCount() const { return m_Paths.size(); }
+
+ void SetPath(int index, const TPath& path);
+ const TPath& GetPath(int index) const { return m_Paths[index]; }
+ TPath& GetPath(int index) { return m_Paths[index]; }
+
+ void CopyFrom (const Polygon2D& paths);
+
+ size_t GetTotalPointCount() const
+ {
+ size_t count = 0;
+ for (size_t i = 0; i < m_Paths.size(); ++i)
+ count += m_Paths[i].size();
+ return count;
+ }
+
+ // Generation
+ void GenerateFrom(Sprite* sprite, const Vector2f& offset, float detail, unsigned char alphaTolerance, bool holeDetection);
+
+ // Math
+ bool GetNearestPoint(const Vector2f& point, int& pathIndex, int& pointIndex, float& distance) const;
+ bool GetNearestEdge(const Vector2f& point, int& pathIndex, int& pointIndex0, int& pointIndex1, float& distance, bool loop) const;
+
+private:
+ TPaths m_Paths;
+};
+
+template<class TransferFunction>
+void Polygon2D::Transfer(TransferFunction& transfer)
+{
+ TRANSFER(m_Paths);
+}
+
+#endif //ENABLE_SPRITES
diff --git a/Runtime/Graphics/ProceduralCache.cpp b/Runtime/Graphics/ProceduralCache.cpp
new file mode 100644
index 0000000..9ae3176
--- /dev/null
+++ b/Runtime/Graphics/ProceduralCache.cpp
@@ -0,0 +1,154 @@
+#include "UnityPrefix.h"
+#include "ProceduralMaterial.h"
+#include "SubstanceSystem.h"
+#include "Runtime/Misc/CachingManager.h"
+#include "Runtime/Utilities/FileUtilities.h"
+#include "Runtime/Graphics/Image.h"
+
+#if ENABLE_SUBSTANCE && ENABLE_CACHING && !UNITY_EDITOR
+bool ProceduralMaterial::PreProcess(std::set<unsigned int>& cachedTextureIDs)
+{
+ if (m_LoadingBehavior!=ProceduralLoadingBehavior_Cache)
+ return false;
+
+ std::string cacheFolder = GetCacheFolder();
+ std::vector<string> fileNames;
+ CachingManager::ReadInfoFile(cacheFolder, NULL, &fileNames);
+ if (fileNames.size()==0)
+ return false;
+
+ bool success = true;
+ std::map<ProceduralTexture*, SubstanceTexture> cachedTextures;
+ for (PingedTextures::iterator it=m_PingedTextures.begin();it!=m_PingedTextures.end() ; ++it)
+ {
+ if (*it!=NULL)
+ {
+ std::string fileName;
+ if (ReadCachedTexture(fileName, cachedTextures, cacheFolder, **it))
+ {
+ cachedTextureIDs.insert((*it)->GetSubstanceBaseTextureUID());
+ }
+ else
+ {
+ success = false;
+ DeleteFileOrDirectory(fileName);
+ std::vector<string>::iterator it = std::find(fileNames.begin(), fileNames.end(), fileName);
+ if (it!=fileNames.end())
+ fileNames.erase(it);
+ }
+ }
+ }
+ GetSubstanceSystem().ForceSubstanceResults(cachedTextures);
+
+ if (fileNames.size()==0)
+ {
+ DeleteFileOrDirectory(cacheFolder);
+ return false;
+ }
+
+ time_t timestamp = CachingManager::GenerateTimeStamp();
+ CachingManager::WriteInfoFile(cacheFolder, fileNames, timestamp);
+ GetCachingManager().GetCurrentCache().UpdateTimestamp(cacheFolder, timestamp);
+
+ if (success)
+ m_LoadingBehavior = ProceduralLoadingBehavior_Generate;
+ return success;
+}
+
+void ProceduralMaterial::PostProcess(const std::map<ProceduralTexture*, SubstanceTexture>& textures, const std::set<unsigned int>& cachedTextureIDs)
+{
+ if (m_LoadingBehavior!=ProceduralLoadingBehavior_Cache)
+ return;
+ m_LoadingBehavior = ProceduralLoadingBehavior_Generate;
+
+ std::string cacheFolder = GetCacheFolder();
+ std::vector<string> fileNames;
+ CachingManager::ReadInfoFile(cacheFolder, NULL, &fileNames);
+ for (std::map<ProceduralTexture*, SubstanceTexture>::const_iterator it=textures.begin() ; it!=textures.end() ; ++it)
+ {
+ if (it->first!=NULL && cachedTextureIDs.find(it->first->GetSubstanceBaseTextureUID())==cachedTextureIDs.end())
+ {
+ std::string fileName;
+ bool result = WriteCachedTexture(fileName, cacheFolder, *it->first, it->second);
+ std::vector<string>::iterator it = std::find(fileNames.begin(), fileNames.end(), fileName);
+ if (result)
+ {
+ if (it==fileNames.end())
+ fileNames.push_back(fileName);
+ }
+ else
+ {
+ DeleteFileOrDirectory(fileName);
+ if (it!=fileNames.end())
+ fileNames.erase(it);
+ }
+ }
+ }
+ if (fileNames.size()==0)
+ {
+ DeleteFileOrDirectory(cacheFolder);
+ }
+ else
+ {
+ GetCachingManager().WriteInfoFile(cacheFolder, fileNames);
+ }
+}
+
+std::string ProceduralMaterial::GetCacheFolder() const
+{
+ std::string hash;
+ char hashValue[6];
+ for (int i=0 ; i<16 ; ++i)
+ {
+ snprintf(hashValue, 6, "%d", (int)m_Hash.hashData.bytes[i]);
+ hash += hashValue;
+ }
+ return GetCachingManager().GetCurrentCache().GetFolder(hash.c_str(), true);
+}
+
+std::string ProceduralMaterial::GetCacheFilename(const ProceduralTexture& texture) const
+{
+ char filename[24];
+ snprintf(filename, 24, "%u.cache", texture.GetSubstanceBaseTextureUID());
+ return filename;
+}
+
+bool ProceduralMaterial::ReadCachedTexture(string& fileName, std::map<ProceduralTexture*, SubstanceTexture>& cachedTextures, const std::string& folder, const ProceduralTexture& texture)
+{
+ fileName = folder+"/"+GetCacheFilename(texture);
+ File file;
+ if (!file.Open(fileName, File::kReadPermission, File::kSilentReturnOnOpenFail))
+ return false;
+ unsigned int infoSize = sizeof(SubstanceTexture);
+ unsigned int textureSize;
+ SubstanceTexture& data = cachedTextures[(ProceduralTexture*)&texture];
+ bool result = file.Read(&data, infoSize)
+ && file.Read(&textureSize, 4)
+ && (data.buffer=SubstanceSystem::OnMalloc(textureSize))!=NULL
+ && file.Read(data.buffer, textureSize);
+ file.Close();
+ return result;
+}
+
+bool ProceduralMaterial::WriteCachedTexture(string& fileName, const std::string& folder, const ProceduralTexture& texture, const SubstanceTexture& data)
+{
+ fileName = folder+"/"+GetCacheFilename(texture);
+ if (IsFileCreated(fileName) && texture.HasBeenUploaded())
+ return true;
+ unsigned int infoSize = sizeof(SubstanceTexture);
+ int mipLevels = data.mipmapCount;
+ if (mipLevels==0)
+ mipLevels = CalculateMipMapCount3D(data.level0Width, data.level0Height, 1);
+ unsigned int textureSize = CalculateMipMapOffset(data.level0Width, data.level0Height, texture.GetSubstanceFormat(), mipLevels+1);
+ if (!GetCachingManager().GetCurrentCache().FreeSpace(infoSize+4+textureSize))
+ return false;
+ File file;
+ if (!file.Open(fileName, File::kWritePermission, File::kSilentReturnOnOpenFail))
+ return false;
+ bool result = file.Write(&data, infoSize)
+ && file.Write(&textureSize, 4)
+ && file.Write(data.buffer, textureSize);
+ file.Close();
+ return result;
+}
+#endif
diff --git a/Runtime/Graphics/ProceduralLinker.cpp b/Runtime/Graphics/ProceduralLinker.cpp
new file mode 100644
index 0000000..402756d
--- /dev/null
+++ b/Runtime/Graphics/ProceduralLinker.cpp
@@ -0,0 +1,521 @@
+#include "UnityPrefix.h"
+#include "ProceduralMaterial.h"
+#include "SubstanceArchive.h"
+#include "SubstanceSystem.h"
+
+#if ENABLE_SUBSTANCE
+bool ProceduralShuffleOutputs (SubstanceLinkerHandle* linkerHandle, ProceduralMaterial::PingedTextures& textures, SubstanceInputs& inputs)
+{
+ for (ProceduralMaterial::PingedTextures::iterator it=textures.begin();it!=textures.end();++it)
+ {
+ unsigned int rI = 0, gI = 1, bI = 2, aI = 3;
+ ProceduralTexture* texture = *it;
+ ProceduralTexture* alpha = NULL;
+ SubstanceLinkerShuffle shuffle;
+ unsigned int baseOutputUid;
+ unsigned int shuffledOutputUid;
+
+ texture->SetSubstancePreShuffleUID(texture->GetSubstanceTextureUID());
+ texture->EnableFlag(ProceduralTexture::Flag_Binded, false);
+
+ if (texture->GetSubstanceFormat()==kTexFormatARGB32)
+ {
+ rI = 1;
+ gI = 2;
+ bI = 3;
+ aI = 0;
+ }
+
+ baseOutputUid = texture->GetSubstanceTextureUID();
+
+ // RGB normal map
+ if (texture->GetUsageMode() == kTexUsageNormalmapPlain)
+ {
+ shuffle.useLevels = 1;
+ shuffle.channels[rI].outputUID = baseOutputUid;
+ shuffle.channels[rI].channelIndex = 0;
+ shuffle.channels[rI].levelMin = 0.0f;
+ shuffle.channels[rI].levelMax = 1.0f;
+ shuffle.channels[gI].outputUID = baseOutputUid;
+ shuffle.channels[gI].channelIndex = 1;
+ shuffle.channels[gI].levelMin = 1.0f;
+ shuffle.channels[gI].levelMax = 0.0f;
+ shuffle.channels[bI].outputUID = baseOutputUid;
+ shuffle.channels[bI].channelIndex = 2;
+ shuffle.channels[bI].levelMin = 0.0f;
+ shuffle.channels[bI].levelMax = 1.0f;
+ shuffle.channels[aI].outputUID = baseOutputUid;
+ shuffle.channels[aI].channelIndex = 3;
+ shuffle.channels[aI].levelMin = 0.0f;
+ shuffle.channels[aI].levelMax = 1.0f;
+ }
+ // platform uses optimized normal map (Z reconstruction)
+ else if (texture->GetUsageMode() == kTexUsageNormalmapDXT5nm)
+ {
+ shuffle.useLevels = 1;
+ shuffle.channels[rI].outputUID = baseOutputUid;
+ shuffle.channels[rI].channelIndex = 1;
+ shuffle.channels[rI].levelMin = 1.0f;
+ shuffle.channels[rI].levelMax = 0.0f;
+ shuffle.channels[gI].outputUID = baseOutputUid;
+ shuffle.channels[gI].channelIndex = 1;
+ shuffle.channels[gI].levelMin = 1.0f;
+ shuffle.channels[gI].levelMax = 0.0f;
+ shuffle.channels[bI].outputUID = baseOutputUid;
+ shuffle.channels[bI].channelIndex = 1;
+ shuffle.channels[bI].levelMin = 1.0f;
+ shuffle.channels[bI].levelMax = 0.0f;
+ shuffle.channels[aI].outputUID = baseOutputUid;
+ shuffle.channels[aI].channelIndex = 0;
+ shuffle.channels[aI].levelMin = 0.0f;
+ shuffle.channels[aI].levelMax = 1.0f;
+ }
+ else
+ {
+ shuffle.useLevels = 0;
+ shuffle.channels[rI].outputUID = baseOutputUid;
+ shuffle.channels[rI].channelIndex = 0;
+ shuffle.channels[gI].outputUID = baseOutputUid;
+ shuffle.channels[gI].channelIndex = 1;
+ shuffle.channels[bI].outputUID = baseOutputUid;
+ shuffle.channels[bI].channelIndex = 2;
+ shuffle.channels[aI].outputUID = baseOutputUid;
+ shuffle.channels[aI].channelIndex = texture->GetType()==Substance_OType_Specular?0:3;
+
+ if (texture->GetAlphaSource()!=Substance_OType_Unknown)
+ {
+ for (ProceduralMaterial::PingedTextures::iterator i=textures.begin();i!=textures.end();++i)
+ {
+ ProceduralTexture* alphaTexture = *i;
+ if (alphaTexture->GetType() == texture->GetAlphaSource())
+ {
+ alpha = alphaTexture;
+ shuffle.channels[aI].outputUID = alphaTexture->GetSubstanceTextureUID();
+ shuffle.channels[aI].channelIndex = 0;
+ break;
+ }
+ }
+ }
+ }
+
+ unsigned int substance_format;
+ switch( texture->GetSubstanceFormat() )
+ {
+ case kTexFormatDXT5:
+ substance_format = Substance_PF_DXT5;
+ break;
+ case kTexFormatETC_RGB4:
+ substance_format = Substance_PF_ETC1;
+ break;
+ case kTexFormatPVRTC_RGBA4:
+ substance_format = Substance_PF_PVRTC4;
+ break;
+ default:
+ substance_format = Substance_PF_RGBA;
+ }
+ if (substanceLinkerHandleCreateOutput(linkerHandle, &shuffledOutputUid,
+ substance_format, 0, Substance_Linker_Flip_Vertical, &shuffle)!=0)
+ {
+ return false;
+ }
+
+ texture->SetSubstanceShuffledUID(shuffledOutputUid);
+
+ for (SubstanceInputs::iterator i=inputs.begin();i!=inputs.end();++i)
+ {
+ // look if main texture is used
+ {
+ std::set<unsigned int>::iterator ai = std::find(
+ i->alteredTexturesUID.begin(), i->alteredTexturesUID.end(), texture->GetSubstanceBaseTextureUID());
+ if (ai!=i->alteredTexturesUID.end())
+ {
+ texture->EnableFlag(ProceduralTexture::Flag_Binded, true);
+ }
+ }
+
+ // look if alpha texture is used
+ if (alpha!=NULL)
+ {
+ std::set<unsigned int>::iterator ai = std::find(
+ i->alteredTexturesUID.begin(), i->alteredTexturesUID.end(), alpha->GetSubstanceBaseTextureUID());
+ if (ai!=i->alteredTexturesUID.end())
+ {
+ i->alteredTexturesUID.insert(texture->GetSubstanceBaseTextureUID());
+ texture->EnableFlag(ProceduralTexture::Flag_Binded, true);
+ }
+ }
+ }
+ }
+ return true;
+}
+
+ProceduralMaterial* ProceduralMaterial::m_PackedSubstance = NULL;
+
+void SUBSTANCE_CALLBACK ProceduralHandleUIDConflict(SubstanceLinkerHandle* handle, SubstanceLinkerUIDCollisionType conflict, unsigned int previousUID, unsigned int newUID)
+{
+ SubstanceInputs& inputs = ProceduralMaterial::m_PackedSubstance->GetSubstanceInputs();
+ ProceduralMaterial::PingedTextures& textures = ProceduralMaterial::m_PackedSubstance->GetPingedTextures();
+ if (conflict==Substance_Linker_UIDCollision_Input)
+ {
+ for (SubstanceInputs::iterator it=inputs.begin() ; it!=inputs.end() ; ++it)
+ {
+ if (previousUID==it->internalIdentifier)
+ {
+ it->shuffledIdentifier = newUID;
+ break;
+ }
+ }
+ }
+ else if (conflict==Substance_Linker_UIDCollision_Output)
+ {
+ for (ProceduralMaterial::PingedTextures::iterator it=textures.begin() ; it!=textures.end() ; ++it)
+ {
+ ProceduralTexture* texture = *it;
+ if (texture!=NULL && previousUID==texture->GetSubstanceBaseTextureUID())
+ {
+ texture->SetSubstanceShuffledUID(newUID);
+ break;
+ }
+ }
+ }
+}
+
+void ProceduralMaterial::PackSubstances(std::vector<ProceduralMaterial*>& materials)
+{
+ if (materials.size()==0)
+ return;
+
+ // Create linker context
+ SubstanceEngineIDEnum currentEngine(GetSubstanceEngineID());
+ SubstanceLinkerContext* linkerContext;
+ SubstanceLinkerHandle* linkerHandle;
+ if (substanceLinkerContextInit(&linkerContext, SUBSTANCE_LINKER_API_VERSION, currentEngine)!=0
+ || substanceLinkerContextSetCallback(linkerContext, Substance_Linker_Callback_UIDCollision, (void*)&ProceduralHandleUIDConflict)!=0
+ || substanceLinkerHandleInit(&linkerHandle, linkerContext)!=0)
+ {
+ ErrorString("Failed to initialize substance linker");
+ for (std::vector<ProceduralMaterial*>::iterator it=materials.begin();it!=materials.end();++it)
+ (*it)->EnableFlag(Flag_Broken);
+ return;
+ }
+
+ // Make the list of materials to pack
+ std::vector<ProceduralMaterial*> packedMaterials;
+ packedMaterials.reserve(materials.size());
+
+ // Try to push all substances assembly
+ for (std::vector<ProceduralMaterial*>::iterator it=materials.begin();it!=materials.end();++it)
+ {
+ if ((*it)->IsFlagEnabled(Flag_Broken))
+ continue;
+
+ SubstanceArchive* package = (*it)->m_PingedPackage;
+ Assert(package!=NULL);
+
+ unsigned size = package->GetBufferSize();
+ UInt8* bufferData = package->GetBufferData();
+
+ m_PackedSubstance = *it;
+
+ // Prepare texture UIDs
+ {
+ for (ProceduralMaterial::PingedTextures::iterator i=m_PackedSubstance->m_PingedTextures.begin();i!=m_PackedSubstance->m_PingedTextures.end();++i)
+ (*i)->SetSubstanceShuffledUID((*i)->GetSubstanceBaseTextureUID());
+ }
+
+ // Prepare input UIDs
+ {
+ // Check if it's the old format
+ if (m_PackedSubstance->m_Inputs.size()>0
+ && m_PackedSubstance->m_Inputs[0].internalIdentifier==0)
+ {
+ SubstanceLinkerContext *context;
+ SubstanceLinkerHandle* linkHandle;
+ size_t substanceDataSize = 0;
+ UInt8* buffer = NULL;
+ if (substanceLinkerContextInit(&context, SUBSTANCE_LINKER_API_VERSION, currentEngine)==0
+ && substanceLinkerHandleInit(&linkHandle, context)==0
+ && substanceLinkerHandlePushAssemblyMemory(linkHandle, (const char*)bufferData, size)==0
+ && substanceLinkerHandleLink(linkHandle, (const unsigned char**)&buffer, &substanceDataSize)==0)
+ {
+ SubstanceHandle* substanceHandle = NULL;
+ if (substanceHandleInit( &substanceHandle, GetSubstanceSystem().GetContext(), buffer, substanceDataSize, NULL )==0)
+ {
+ for (SubstanceInputs::iterator i=m_PackedSubstance->m_Inputs.begin();i!=m_PackedSubstance->m_Inputs.end();++i)
+ {
+ SubstanceInputDesc inputDescription;
+ if (substanceHandleGetInputDesc(substanceHandle, i->internalIndex, &inputDescription)==0)
+ i->internalIdentifier = inputDescription.inputId;
+ }
+ substanceHandleRelease(substanceHandle);
+ }
+
+ substanceLinkerHandleRelease(linkHandle);
+ substanceLinkerContextRelease(context);
+ }
+ else
+ {
+ ErrorStringObject("Failed to initialize substance linker", m_PackedSubstance);
+ m_PackedSubstance->EnableFlag(Flag_Broken);
+ continue;
+ }
+ }
+
+ // Prepare shuffled ID
+ for (SubstanceInputs::iterator i=m_PackedSubstance->m_Inputs.begin();i!=m_PackedSubstance->m_Inputs.end();++i)
+ {
+ i->shuffledIdentifier = i->internalIdentifier;
+ }
+ }
+
+ // Push assembly if it has not already been pushed
+ // If we're creating another instance of a graph that has already been pushed, then we still need to push the SBSASM,
+ // otherwise we'll just get copies of the previous instance's outputs.
+ if ((!package->m_isPushed) || (package->m_generatedGraphs.count(m_PackedSubstance->m_PrototypeName) == 1))
+ {
+ if (substanceLinkerHandlePushAssemblyMemory(linkerHandle, (const char*)bufferData, size)!=0)
+ {
+ ErrorStringObject("Failed to pack substance (substanceLinkerHandlePushAssemblyMemory)", *it);
+ m_PackedSubstance->EnableFlag(Flag_Broken);
+ continue;
+ }
+ package->m_isPushed = true;
+ }
+ package->m_generatedGraphs.insert(m_PackedSubstance->m_PrototypeName);
+
+ // Set output formats & create shuffled outputs if needed
+ if (!ProceduralShuffleOutputs(linkerHandle, (*it)->m_PingedTextures, (*it)->m_Inputs))
+ {
+ ErrorStringObject("Failed to pack substance (ShuffleOutputs)", *it);
+ m_PackedSubstance->EnableFlag(Flag_Broken);
+ continue;
+ }
+
+ // Set inputs constness
+ #if !UNITY_EDITOR
+ if ((*it)->IsFlagEnabled(Flag_ConstSize))
+ {
+ SubstanceInputs& inputs = (*it)->m_Inputs;
+ bool broken=false;
+ for (SubstanceInputs::iterator i=inputs.begin();i!=inputs.end();++i)
+ {
+ if (i->name=="$outputsize" || i->name=="$randomseed")
+ {
+ SubstanceLinkerInputValue value;
+ value.integer[0] = (int)i->value.scalar[0];
+ value.integer[1] = (int)i->value.scalar[1];
+ value.integer[2] = (int)i->value.scalar[2];
+ value.integer[3] = (int)i->value.scalar[3];
+ if (substanceLinkerConstifyInput(linkerHandle, i->shuffledIdentifier, &value)!=0)
+ break;
+ }
+ }
+ if (broken)
+ {
+ ErrorStringObject("Failed to pack substance (substanceLinkerConstifyInput)", *it);
+ m_PackedSubstance->EnableFlag(Flag_Broken);
+ continue;
+ }
+ }
+ #endif
+
+ packedMaterials.push_back(*it);
+ }
+
+ // Select all used outputs
+ if (packedMaterials.size()>0 && substanceLinkerHandleSelectOutputs(linkerHandle, Substance_Linker_Select_UnselectAll, 0)!=0)
+ {
+ ErrorString("Failed to pack substances (substanceLinkerHandleSelectOutputs)");
+ for (int i=packedMaterials.size()-1;i>=0;--i)
+ packedMaterials[i]->EnableFlag(Flag_Broken);
+ return;
+ }
+ for (int i=packedMaterials.size()-1;i>=0;--i)
+ {
+ ProceduralMaterial* material = packedMaterials[i];
+ for (ProceduralMaterial::PingedTextures::iterator it=material->m_PingedTextures.begin();it!=material->m_PingedTextures.end();++it)
+ {
+ ProceduralTexture* texture = *it;
+ if (substanceLinkerHandleSelectOutputs(linkerHandle, Substance_Linker_Select_Select, texture->GetSubstancePreShuffleUID())!=0)
+ {
+ ErrorStringObject("Failed to pack substance (Substance_Linker_Select_Select)", material);
+ material->EnableFlag(Flag_Broken);
+ packedMaterials.erase(packedMaterials.begin()+i);
+ break;
+ }
+ }
+ }
+
+ // Nothing to pack ?
+ if (packedMaterials.size()==0)
+ {
+ substanceLinkerHandleRelease(linkerHandle);
+ substanceLinkerContextRelease(linkerContext);
+ return;
+ }
+
+ // Initialize substance handle
+ SubstanceData* pack = (SubstanceData*) UNITY_MALLOC_ALIGNED_NULL(kMemSubstance, sizeof(SubstanceData), 32);
+ if (!pack)
+ {
+ ErrorString("Could not allocate memory for Substance data (PackSubstances)");
+ for (int i=packedMaterials.size()-1;i>=0;--i)
+ packedMaterials[i]->EnableFlag(Flag_Broken);
+ return;
+ }
+
+ pack->substanceHandle = NULL;
+ pack->instanceCount = 0;
+ pack->memorySleepBudget = ProceduralCacheSize_None;
+ pack->memoryWorkBudget = ProceduralCacheSize_Tiny;
+
+
+ // Do we need to link the SBSASM?
+ // Only case where we DON'T need to link is when the three following conditions are met:
+ // - we are only linking a single substance
+ // - we are linking a clone
+ // - we have linked the same standalone substance clone once and cached the SBSBIN data
+ // If one of the above conditions is not met, we need to link.
+ size_t substanceDataSize = 0;
+ UInt8* buffer = NULL;
+ const ProceduralMaterial* currentMat = packedMaterials[0];
+ const UnityStr& prototypeName = currentMat->m_PrototypeName;
+ if (!((packedMaterials.size() == 1)
+ && (currentMat->IsFlagEnabled(Flag_Clone))
+ && (currentMat->m_PingedPackage->IsCloneDataAvailable(prototypeName))))
+ {
+ if (substanceLinkerHandleLink(linkerHandle, (const unsigned char**)&buffer, &substanceDataSize )!=0)
+ {
+ ErrorStringObject("Failed to pack substances (substanceLinkerHandleLink)", currentMat);
+ for (int i=packedMaterials.size()-1;i>=0;--i)
+ packedMaterials[i]->EnableFlag(Flag_Broken);
+ return;
+ }
+ }
+
+ // Handle the "cloning of a single substance" situation
+ if ((packedMaterials.size() == 1) && (currentMat->IsFlagEnabled(Flag_Clone)))
+ {
+ // In that case, we link and cache the SBSBIN inside the SubstancePackage.
+ // When the same substance is cloned again, this vanilla SBSBIN data is reused to avoid reallocating memory
+ SubstanceArchive* package = currentMat->m_PingedPackage;
+ if (package->IsCloneDataAvailable(prototypeName))
+ {
+ pack->substanceData = package->GetLinkedBinaryData(prototypeName);
+ }
+ else
+ {
+ if (package->SaveLinkedBinaryData(prototypeName, (const UInt8*) buffer, (const int) substanceDataSize))
+ {
+ pack->substanceData = package->GetLinkedBinaryData(prototypeName);
+ }
+ else
+ {
+ WarningStringMsg("Failed to save SBSBIN data for material %s", currentMat->GetName());
+ pack->substanceData = (UInt8*) UNITY_MALLOC_ALIGNED_NULL(kMemSubstance, substanceDataSize, 32);
+ if (pack->substanceData)
+ {
+ memcpy(pack->substanceData, buffer, substanceDataSize);
+ }
+ else
+ {
+ ErrorString("Could not allocate memory for Substance linked data");
+ for (int i=packedMaterials.size()-1;i>=0;--i)
+ packedMaterials[i]->EnableFlag(Flag_Broken);
+ return;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Either we're packing more than one substance, or this is not a clone, in
+ // which case the SBSBIN is not reusable because of constified inputs.
+ pack->substanceData = (UInt8*) UNITY_MALLOC_ALIGNED_NULL(kMemSubstance, substanceDataSize, 32);
+ if (pack->substanceData)
+ {
+ memcpy(pack->substanceData, buffer, substanceDataSize);
+ }
+ else
+ {
+ ErrorString("Could not allocate memory for Substance linked data");
+ for (int i=packedMaterials.size()-1;i>=0;--i)
+ packedMaterials[i]->EnableFlag(Flag_Broken);
+ return;
+ }
+ }
+
+ // Reset the isPushed flags and the list of generated graphs of the packages that have been pushed and linked
+ for (std::vector<ProceduralMaterial*>::iterator it=materials.begin();it!=materials.end();++it)
+ {
+ SubstanceArchive *package = (*it)->m_PingedPackage;
+ package->m_isPushed = false;
+ package->m_generatedGraphs.clear();
+ }
+
+ // Release linker
+ substanceLinkerHandleRelease(linkerHandle);
+ substanceLinkerContextRelease(linkerContext);
+
+ // Create substance handle
+ if (substanceHandleInit(&pack->substanceHandle, GetSubstanceSystem().GetContext(), pack->substanceData, substanceDataSize, NULL)!=0)
+ {
+ ErrorStringObject("Failed to pack substances (substanceHandleInit)", packedMaterials[0]);
+ for (int i=packedMaterials.size()-1;i>=0;--i)
+ packedMaterials[i]->EnableFlag(Flag_Broken);
+ return;
+ }
+
+ // Bind all substances
+ for (std::vector<ProceduralMaterial*>::iterator it=packedMaterials.begin();it!=packedMaterials.end();++it)
+ {
+ bool broken(false);
+
+ // Update input identifiers (todo: improve this if doable)
+ for (SubstanceInputs::iterator i=(*it)->m_Inputs.begin();i!=(*it)->m_Inputs.end();++i)
+ {
+ i->internalIndex = -1;
+ int id=0;
+ SubstanceInputDesc inputDescription;
+ while (substanceHandleGetInputDesc(pack->substanceHandle, id, &inputDescription)==0)
+ {
+ if (i->shuffledIdentifier==inputDescription.inputId)
+ {
+ i->internalIndex = id;
+ break;
+ }
+ ++id;
+ }
+ if (i->internalIndex==-1)
+ {
+#if !UNITY_EDITOR
+ // Don't break since the input may be removed
+ if ((*it)->IsFlagEnabled(Flag_ConstSize) && (i->name=="$outputsize" || i->name=="$randomseed"))
+ {
+ continue;
+ }
+#endif
+ broken = true;
+ }
+ }
+
+ if (broken)
+ {
+ ErrorStringObject("Failed to pack substances (SubstanceInputDesc)", *it);
+ (*it)->EnableFlag(Flag_Broken);
+ continue;
+ }
+
+ (*it)->m_SubstanceData = pack;
+ ++pack->instanceCount;
+ }
+
+ if (pack->instanceCount==0)
+ {
+ substanceHandleRelease(pack->substanceHandle);
+ UNITY_FREE(kMemSubstance, pack->substanceData);
+ UNITY_FREE(kMemSubstance, pack);
+ }
+}
+
+#endif // ENABLE_SUBSTANCE
diff --git a/Runtime/Graphics/ProceduralMaterial.cpp b/Runtime/Graphics/ProceduralMaterial.cpp
new file mode 100644
index 0000000..69b616a
--- /dev/null
+++ b/Runtime/Graphics/ProceduralMaterial.cpp
@@ -0,0 +1,1339 @@
+#include "UnityPrefix.h"
+#include "ProceduralMaterial.h"
+#include "SubstanceArchive.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Input/TimeManager.h"
+#include "SubstanceSystem.h"
+#include "Runtime/Graphics/Image.h"
+
+#if ENABLE_SUBSTANCE
+#include "Runtime/BaseClasses/IsPlaying.h"
+#endif
+
+#if UNITY_EDITOR
+#include "Editor/Src/AssetPipeline/SubstanceImporter.h"
+#include "Editor/Src/EditorUserBuildSettings.h"
+#endif
+
+size_t GetProceduralMemoryBudget(ProceduralCacheSize budget)
+{
+ switch(budget)
+ {
+ case ProceduralCacheSize_NoLimit:
+ return 0;
+ case ProceduralCacheSize_Heavy:
+ return 512 * 1024 * 1024;
+ case ProceduralCacheSize_Medium:
+ return 256 * 1024 * 1024;
+ case ProceduralCacheSize_Tiny:
+ return 128 * 1024 * 1024;
+ case ProceduralCacheSize_None:
+ default:
+ return 1;
+ }
+}
+
+Mutex ProceduralMaterial::m_InputMutex;
+
+IMPLEMENT_CLASS( ProceduralMaterial )
+IMPLEMENT_OBJECT_SERIALIZE( ProceduralMaterial )
+
+ProceduralMaterial::ProceduralMaterial( MemLabelId label, ObjectCreationMode mode ) :
+ Super( label, mode ),
+ m_SubstanceData( NULL ),
+ m_Flags( 0 ),
+ m_LoadingBehavior( ProceduralLoadingBehavior_Generate ),
+ m_Width( 9 ),
+ m_Height( 9 ),
+#if UNITY_EDITOR
+ m_isAlreadyLoadedInCurrentScene( false ),
+#endif
+ m_AnimationUpdateRate( 42 ), // 24fps
+ m_AnimationTime( 0.0f ),
+ m_PingedPackage( NULL ),
+ m_PrototypeName( "" )
+{
+#if ENABLE_SUBSTANCE
+ integrationTimeStamp = GetSubstanceSystem().integrationTimeStamp;
+#endif
+}
+
+ProceduralMaterial::~ProceduralMaterial()
+{
+#if ENABLE_SUBSTANCE
+
+ /////@TODO: This is an incredible hack. Waiting for another process to complete in the destructor is a big no no, especially when it involves a Thread::Sleep ()...
+ UnlockObjectCreation();
+ GetSubstanceSystem().NotifySubstanceDestruction(this);
+ LockObjectCreation();
+
+ Clean();
+
+ // Delete texture inputs (TODO: try to remove std::vector<Image*>)
+ for (std::vector<TextureInput>::iterator it=m_TextureInputs.begin();it!=m_TextureInputs.end();++it)
+ {
+ delete it->inputParameters;
+ delete it->image;
+
+ if (it->buffer)
+ {
+ UNITY_FREE(kMemSubstance, it->buffer);
+ }
+ }
+#endif
+}
+
+void ProceduralMaterial::Clean()
+{
+#if ENABLE_SUBSTANCE
+ if (m_SubstanceData!=NULL)
+ {
+ if (m_SubstanceData->instanceCount==1)
+ {
+ substanceHandleRelease(m_SubstanceData->substanceHandle);
+ // Clones do not delete their SBSBIN data, this is done in the destructor of the SubstanceArchive
+ // the clone was created from.
+ if (!IsFlagEnabled(Flag_Clone))
+ {
+ UNITY_FREE( kMemSubstance, m_SubstanceData->substanceData );
+ }
+ UNITY_FREE( kMemSubstance, m_SubstanceData );
+ }
+ else
+ {
+ --m_SubstanceData->instanceCount;
+ }
+ }
+ m_SubstanceData = NULL;
+ // Awake inputs
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i != m_Inputs.end();i++)
+ {
+ i->EnableFlag(SubstanceInput::Flag_Awake, true);
+ }
+#endif
+}
+
+ProceduralMaterial* ProceduralMaterial::Clone()
+{
+ ProceduralMaterial* clone = CreateObjectFromCode<ProceduralMaterial>();
+ clone->m_SubstancePackage = m_SubstancePackage;
+ clone->m_PrototypeName = m_PrototypeName;
+ clone->m_Width = m_Width;
+ clone->m_Height = m_Height;
+ clone->m_Textures = m_Textures;
+ // Rename cloned textures
+ for (int idx=0 ; idx<m_Textures.size() ; ++idx)
+ {
+ clone->m_Textures[idx]->SetName(m_Textures[idx]->GetName());
+ }
+ clone->m_AnimationUpdateRate = m_AnimationUpdateRate;
+ clone->m_Inputs = m_Inputs;
+ clone->m_Flags = ((m_Flags | Flag_Clone | Flag_AwakeClone) & (~Flag_ConstSize));
+ clone->m_LoadingBehavior = m_LoadingBehavior;
+ clone->AwakeDependencies(false);
+ return clone;
+}
+
+void ProceduralMaterial::RebuildClone()
+{
+ EnableFlag(Flag_AwakeClone, false);
+
+ // Clone textures
+#if ENABLE_SUBSTANCE
+ if (!IsWorldPlaying() || m_LoadingBehavior!=ProceduralLoadingBehavior_BakeAndDiscard)
+ {
+ for (Textures::iterator it=m_Textures.begin();it!=m_Textures.end();++it)
+ {
+ *it = (*it)->Clone(this);
+ }
+
+ // Awake inputs
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i != m_Inputs.end();i++)
+ {
+ i->EnableFlag(SubstanceInput::Flag_Awake, true);
+ }
+
+ AwakeDependencies(false);
+
+ // Force generation if required
+ if (m_LoadingBehavior==ProceduralLoadingBehavior_None)
+ EnableFlag(ProceduralMaterial::Flag_ForceGenerate);
+
+ GetSubstanceSystem().QueueLoading(this);
+ }
+#endif
+}
+
+#if UNITY_EDITOR
+void ProceduralMaterial::Init( SubstanceArchive& substancePackage, const UnityStr& prototypeName, const SubstanceInputs& inputs, const Textures& textures )
+{
+ Assert(m_SubstancePackage.GetInstanceID () == 0);
+
+ m_SubstancePackage = &substancePackage;
+ m_PrototypeName = prototypeName;
+ m_Inputs = inputs;
+ m_Textures = textures;
+
+ EnableFlag(Flag_ConstSize);
+
+ // Update Flag_Animated
+ EnableFlag(Flag_Animated, HasSubstanceProperty("$time"));
+}
+
+const char* ProceduralMaterial::GetSubstancePackageName()
+{
+ if (m_PingedPackage)
+ {
+ return m_PingedPackage->GetName();
+ }
+ else
+ {
+ return m_SubstancePackage->GetName();
+ }
+}
+
+#endif
+
+SubstanceHandle* ProceduralMaterial::GetSubstanceHandle()
+{
+ if (m_SubstanceData!=NULL)
+ return m_SubstanceData->substanceHandle;
+ return NULL;
+}
+
+void ProceduralMaterial::SetSize(int width, int height)
+{
+ m_Width = width;
+ m_Height = height;
+
+ Mutex::AutoLock locker(m_InputMutex);
+ SubstanceInput* input = FindSubstanceInput("$outputsize");
+ if (input!=NULL)
+ {
+ input->value.scalar[0] = (float)m_Width;
+ input->value.scalar[1] = (float)m_Height;
+ }
+}
+
+void ProceduralMaterial::AwakeFromLoadThreaded()
+{
+ Super::AwakeFromLoadThreaded();
+ AwakeDependencies(true);
+#if ENABLE_SUBSTANCE
+#if UNITY_EDITOR
+ EnableFlag(Flag_Awake);
+#endif
+ // Neither Baked (keep or discard) nor DoNothing substances must be generated at this time
+ ProceduralLoadingBehavior behavior = GetLoadingBehavior();
+ if (behavior != ProceduralLoadingBehavior_BakeAndKeep &&
+ behavior != ProceduralLoadingBehavior_BakeAndDiscard &&
+ behavior != ProceduralLoadingBehavior_None)
+ {
+ GetSubstanceSystem().QueueLoading(this);
+ }
+#endif
+}
+
+void ProceduralMaterial::AwakeFromLoad( AwakeFromLoadMode awakeMode )
+{
+ Super::AwakeFromLoad( awakeMode );
+
+ if ((awakeMode & kDidLoadThreaded)==0)
+ {
+ AwakeDependencies(false);
+
+#if ENABLE_SUBSTANCE
+
+#if UNITY_EDITOR
+ EnableFlag(Flag_Awake);
+ if (awakeMode!=kInstantiateOrCreateFromCodeAwakeFromLoad
+ && SubstanceImporter::OnLoadSubstance(*this)
+ && !(IsWorldPlaying() && m_LoadingBehavior==ProceduralLoadingBehavior_None))
+#else
+ // Don't link and render the DoNothing substances when loading them
+ // This is done when calling RebuildTextures instead
+ if (m_LoadingBehavior!=ProceduralLoadingBehavior_None)
+#endif
+ {
+ GetSubstanceSystem().QueueSubstance(this);
+ }
+
+#endif
+ }
+
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().NotifySubstanceCreation(this);
+#endif
+}
+
+#if ENABLE_SUBSTANCE
+void ProceduralMaterial::ApplyInputs (bool& it_has_changed, bool asHint, std::set<unsigned int>& modifiedOutputsUID)
+{
+ int textureInputIndex(0);
+
+ // Handle input alteration
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i != m_Inputs.end();i++)
+ {
+ SubstanceInput& input = *i;
+
+ // Skip const inputs at runtime
+#if !UNITY_EDITOR
+ if (IsFlagEnabled(Flag_ConstSize)
+ && (input.name=="$outputsize" || input.name=="$randomseed"))
+ {
+ continue;
+ }
+#endif
+
+ // Check texture instance hasn't changed, in the editor in case of texture changes
+#if UNITY_EDITOR
+ if (!input.IsFlagEnabled(SubstanceInput::Flag_Modified) && (input.internalType==Substance_IType_Image)
+ && textureInputIndex<m_TextureInputs.size())
+ {
+ Texture2D* checkTexture = dynamic_pptr_cast<Texture2D*>(
+ InstanceIDToObjectThreadSafe(input.value.texture.GetInstanceID()));
+ if (m_TextureInputs[textureInputIndex].texture!=checkTexture)
+ m_TextureInputs[textureInputIndex].texture = checkTexture;
+ }
+#endif
+
+ if (asHint)
+ {
+ // Push hint if needed
+ if (input.IsFlagEnabled(SubstanceInput::Flag_Modified) || input.IsFlagEnabled(SubstanceInput::Flag_Cached))
+ {
+ if (!input.IsFlagEnabled(SubstanceInput::Flag_SkipHint))
+ {
+ if (substanceHandlePushSetInput( m_SubstanceData->substanceHandle, Substance_PushOpt_HintOnly, input.internalIndex, input.internalType, 0, 0 )!=0)
+ ErrorStringObject("Failed to apply substance input as hint", this);
+ }
+ it_has_changed = true;
+ input.EnableFlag(SubstanceInput::Flag_Modified, false);
+ }
+
+ if (input.IsFlagEnabled(SubstanceInput::Flag_Awake))
+ {
+ it_has_changed = true;
+ input.EnableFlag(SubstanceInput::Flag_Awake, false);
+ }
+ }
+ else if (input.IsFlagEnabled(SubstanceInput::Flag_Modified) || input.IsFlagEnabled(SubstanceInput::Flag_Awake))
+ {
+ int error = 0;
+
+ // Apply Float values
+ if (IsSubstanceAnyFloatType(input.internalType))
+ {
+ error = substanceHandlePushSetInput( m_SubstanceData->substanceHandle, Substance_PushOpt_NotAHint, input.internalIndex, input.internalType, input.value.scalar, 0 );
+ }
+ // Apply integer values
+ else if (IsSubstanceAnyIntType(input.internalType))
+ {
+ int intValue[4];
+ intValue[0] = (int)input.value.scalar[0];
+ intValue[1] = (int)input.value.scalar[1];
+ intValue[2] = (int)input.value.scalar[2];
+ intValue[3] = (int)input.value.scalar[3];
+ error = substanceHandlePushSetInput( m_SubstanceData->substanceHandle, Substance_PushOpt_NotAHint, input.internalIndex, input.internalType, intValue, 0 );
+ }
+ // Apply image values
+ else if (input.internalType == Substance_IType_Image)
+ {
+ if (textureInputIndex>=m_TextureInputs.size())
+ {
+ ErrorStringObject("failed to apply substance input image", this);
+ }
+ else
+ {
+ error = substanceHandlePushSetInput( m_SubstanceData->substanceHandle, Substance_PushOpt_NotAHint, input.internalIndex, input.internalType, m_TextureInputs[textureInputIndex].inputParameters, 0 );
+ }
+ }
+ else
+ {
+ ErrorStringObject("unsupported substance input type", this);
+ }
+ if (error != 0)
+ ErrorStringObject("Failed to apply substance input", this);
+
+ // Add modified output
+ modifiedOutputsUID.insert(input.alteredTexturesUID.begin(), input.alteredTexturesUID.end());
+ }
+
+ // Keep the texture input index up to date
+ if (input.internalType == Substance_IType_Image)
+ {
+ ++textureInputIndex;
+ }
+ }
+}
+
+void ProceduralMaterial::ApplyOutputs (bool& it_has_changed, bool asHint, std::set<unsigned int>& modifiedOutputsUID, const std::set<unsigned int>& cachedTextureIDs)
+{
+ // Add the invalid outputs
+ if (!asHint)
+ {
+ for (PingedTextures::iterator it=m_PingedTextures.begin();it!=m_PingedTextures.end();++it)
+ {
+ if (*it!=NULL && !(*it)->IsValid())
+ {
+ modifiedOutputsUID.insert((*it)->GetSubstanceBaseTextureUID());
+ it_has_changed = true;
+ }
+ }
+ }
+
+ // Push outputs
+ unsigned int flags = Substance_OutOpt_TextureId | Substance_OutOpt_CopyNeeded | (asHint?Substance_PushOpt_HintOnly:0);
+ GetSubstanceSystem ().processedTextures.clear();
+ std::vector<unsigned int> textureIDs;
+ for (PingedTextures::iterator i=m_PingedTextures.begin();i!=m_PingedTextures.end();++i)
+ {
+ ProceduralTexture* texture = *i;
+ if (texture!=NULL && modifiedOutputsUID.find(texture->GetSubstanceBaseTextureUID())!=modifiedOutputsUID.end()
+ && cachedTextureIDs.find(texture->GetSubstanceBaseTextureUID())==cachedTextureIDs.end())
+ {
+ GetSubstanceSystem ().processedTextures[texture->GetSubstanceTextureUID()] = texture;
+ textureIDs.push_back(texture->GetSubstanceTextureUID());
+ }
+ }
+
+ if (textureIDs.size()==0)
+ {
+ modifiedOutputsUID.clear();
+ it_has_changed = false;
+ }
+
+ if (textureIDs.size()>0 && substanceHandlePushOutputs( m_SubstanceData->substanceHandle, flags, &textureIDs[0], textureIDs.size(), 0 )!=0)
+ {
+ ErrorStringObject("Failed to apply substance texture outputs", this);
+ }
+}
+#endif
+
+void ProceduralMaterial::RebuildTextures()
+{
+ if (IsFlagEnabled(Flag_AwakeClone))
+ {
+ RebuildClone();
+ }
+#if ENABLE_SUBSTANCE
+ else if (!IsWorldPlaying() || m_LoadingBehavior!=ProceduralLoadingBehavior_BakeAndDiscard)
+ {
+ GetSubstanceSystem().QueueSubstance(this);
+ }
+#endif
+}
+
+void ProceduralMaterial::RebuildTexturesImmediately()
+{
+ ASSERT_RUNNING_ON_MAIN_THREAD;
+ RebuildTextures();
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().WaitFinished(this);
+#endif
+}
+
+void ProceduralMaterial::ReloadAll (bool unload, bool load)
+{
+#if ENABLE_SUBSTANCE
+ std::vector<SInt32> objects;
+ Object::FindAllDerivedObjects (ClassID (ProceduralMaterial), &objects);
+ std::sort(objects.begin(), objects.end());
+
+ if (objects.empty())
+ return;
+
+ GetSubstanceSystem().WaitFinished();
+ for (int i=0;i<objects.size ();i++)
+ {
+ ProceduralMaterial& mat = *PPtr<ProceduralMaterial> (objects[i]);
+ Textures& textures = mat.GetTextures();
+ for (Textures::iterator it=textures.begin() ; it!=textures.end() ; ++it)
+ {
+#if UNITY_EDITOR
+ (*it)->EnableFlag(ProceduralTexture::Flag_Cached, false);
+#endif
+ (*it)->Invalidate();
+ }
+#if UNITY_EDITOR
+ mat.EnableFlag(ProceduralMaterial::Flag_Awake);
+#endif
+ if (mat.m_LoadingBehavior==ProceduralLoadingBehavior_BakeAndDiscard || (unload && !load))
+ {
+ for (Textures::iterator it=textures.begin() ; it!=textures.end() ; ++it)
+ {
+ if (unload)
+ (*it)->UnloadFromGfxDevice(false);
+ if (load)
+ (*it)->UploadToGfxDevice();
+ }
+ }
+ else
+ {
+ mat.RebuildTextures ();
+ }
+ }
+ SubstanceSystem::Context context(ProceduralProcessorUsage_All);
+ GetSubstanceSystem().WaitFinished();
+#endif
+}
+
+template<class _class_>
+void AwakeProceduralObject(PPtr<_class_>& pptr, _class_*& object, bool awakeThreaded)
+{
+ if (awakeThreaded)
+ {
+ #if SUPPORT_THREADS
+ Assert (!Thread::CurrentThreadIsMainThread());
+ #endif
+
+ // Awake the object
+ object = dynamic_pptr_cast<_class_*> (InstanceIDToObjectThreadSafe(pptr.GetInstanceID()));
+ }
+ else
+ {
+ #if SUPPORT_THREADS
+ Assert (Thread::CurrentThreadIsMainThread());
+ #endif
+
+ // We can safely load it synchroneously
+ object = pptr;
+ }
+
+ // Clear the PPtr if the object no more exist (removed/deprecated)
+ if (object==NULL)
+ pptr.SetInstanceID(0);
+}
+
+void ProceduralMaterial::AwakeDependencies(bool awakeThreaded)
+{
+ // Simplest validity check
+ if (m_Textures.size()==0)
+ {
+ EnableFlag(Flag_Broken);
+ return;
+ }
+
+#if ENABLE_SUBSTANCE
+ // Awake package
+ AwakeProceduralObject(m_SubstancePackage, m_PingedPackage, awakeThreaded);
+ if (m_PingedPackage==NULL && m_LoadingBehavior!=ProceduralLoadingBehavior_BakeAndDiscard)
+ {
+ EnableFlag(Flag_Broken);
+ return;
+ }
+
+ // Awake texture inputs
+ unsigned int input_count(0);
+ for (SubstanceInputs::iterator it=m_Inputs.begin();it!=m_Inputs.end();++it)
+ {
+ if (it->internalType==Substance_IType_Image)
+ {
+ if (input_count>=m_TextureInputs.size())
+ {
+ m_TextureInputs.push_back( TextureInput() );
+ m_TextureInputs.back().inputParameters = new SubstanceTextureInput();
+ memset(m_TextureInputs.back().inputParameters, 0, sizeof(SubstanceTextureInput));
+ m_TextureInputs.back().image = new Image();
+ m_TextureInputs.back().buffer = NULL;
+ }
+
+ AwakeProceduralObject(it->value.texture, m_TextureInputs[input_count].texture, awakeThreaded);
+ ++input_count;
+ }
+ }
+#endif
+
+ // Awake textures
+ if (m_PingedTextures.size()!=m_Textures.size())
+ {
+ int size = m_PingedTextures.size();
+ m_PingedTextures.resize(m_Textures.size());
+ if (size<m_PingedTextures.size())
+ memset(&m_PingedTextures[size], 0, sizeof(ProceduralTexture*)*(m_Textures.size()-size));
+ }
+
+ int texture_index(0);
+ for (ProceduralMaterial::Textures::iterator i=m_Textures.begin();i!=m_Textures.end();++i,++texture_index)
+ {
+ AwakeProceduralObject(*i, m_PingedTextures[texture_index], awakeThreaded);
+ if (m_PingedTextures[texture_index]==NULL)
+ {
+ EnableFlag(Flag_Broken);
+ return;
+ }
+ m_PingedTextures[texture_index]->SetOwner(this);
+ }
+}
+
+#if ENABLE_SUBSTANCE
+
+bool ProceduralMaterial::ProcessTexturesThreaded(const std::map<ProceduralTexture*, SubstanceTexture>& textures)
+{
+ if (IsFlagEnabled(Flag_Broken))
+ return true;
+
+ GetSubstanceSystem().UpdateMemoryBudget();
+
+ std::set<unsigned int> cachedTextureIDs;
+#if ENABLE_CACHING && !UNITY_EDITOR
+ if (PreProcess(cachedTextureIDs))
+ {
+ return true;
+ }
+#else
+ // Force rebuild if it's cached and no input is modified
+ InvalidateIfCachedOrInvalidTextures();
+#endif
+
+ // Apply substance parameters (this is the exact ordering the engine needs)
+ std::set<unsigned int> modifiedOutputsUID;
+ bool it_has_changed(false);
+ // Apply inputs values
+ ApplyInputs (it_has_changed, false, modifiedOutputsUID);
+
+ // For readable textures, force the rebuild by pushing all output UIDs,
+ // even if no input was changed (Bnecessary for a clean workflow for case 538383 / GetPixels32())
+ if (IsFlagEnabled(Flag_Readable))
+ {
+ for (PingedTextures::iterator i=m_PingedTextures.begin() ; i!=m_PingedTextures.end() ; ++i)
+ {
+ ProceduralTexture* texture = *i;
+ modifiedOutputsUID.insert(texture->GetSubstanceBaseTextureUID());
+ }
+ }
+
+ // Apply outputs uid
+ ApplyOutputs(it_has_changed, false, modifiedOutputsUID, cachedTextureIDs);
+ // Apply input hints
+ ApplyInputs (it_has_changed, true, modifiedOutputsUID);
+ // Apply outputs hints
+ ApplyOutputs(it_has_changed, true, modifiedOutputsUID, cachedTextureIDs);
+
+ if (!IsFlagEnabled(Flag_Readable) && !it_has_changed)
+ {
+ // Flush render list
+ substanceHandleFlush( m_SubstanceData->substanceHandle );
+ return false;
+ }
+
+ if (substanceHandleStart( m_SubstanceData->substanceHandle, Substance_Sync_Synchronous )!=0)
+ ErrorStringObject("Failed to start substance computation", this);
+
+ // Flush render list
+ substanceHandleFlush( m_SubstanceData->substanceHandle );
+
+#if ENABLE_CACHING && !UNITY_EDITOR
+ PostProcess(textures, cachedTextureIDs);
+#endif
+ return true;
+}
+
+void ProceduralMaterial::ApplyTextureInput (int substanceInputIndex, const SubstanceTextureInput& requiredTextureInput)
+{
+ // Find which input needs update
+ bool found(false);
+ int textureInputIndex(0);
+ SubstanceInput* input;
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i != m_Inputs.end();i++)
+ {
+ input = &*i;
+ if (input->internalIndex==substanceInputIndex)
+ {
+ found = true;
+ break;
+ }
+ if (input->internalType==Substance_IType_Image)
+ {
+ ++textureInputIndex;
+ }
+ }
+ if (!found || textureInputIndex>=m_TextureInputs.size())
+ {
+ ErrorStringObject("Failed to push Substance texture input", this);
+ return;
+ }
+
+ // Check format
+ TextureFormat format;
+ bool need16bitsConvert(false);
+ if (requiredTextureInput.pixelFormat==Substance_PF_RGBA)
+ format = kTexFormatRGBA32;
+ else if (requiredTextureInput.pixelFormat==Substance_PF_RGB)
+ format = kTexFormatRGB24;
+ else if (requiredTextureInput.pixelFormat==(Substance_PF_16b | Substance_PF_RGBA))
+ {
+ format = kTexFormatRGBA32;
+ need16bitsConvert = true;
+ }
+ else if (requiredTextureInput.pixelFormat==(Substance_PF_16b | Substance_PF_RGB))
+ {
+ format = kTexFormatRGB24;
+ need16bitsConvert = true;
+ }
+ else if (requiredTextureInput.pixelFormat==Substance_PF_DXT1)
+ format = kTexFormatDXT1;
+ else if (requiredTextureInput.pixelFormat==Substance_PF_DXT3)
+ format = kTexFormatDXT3;
+ else if (requiredTextureInput.pixelFormat==Substance_PF_DXT5)
+ format = kTexFormatDXT5;
+ else if (requiredTextureInput.pixelFormat==Substance_PF_L)
+ format = kTexFormatAlpha8;
+ else
+ {
+ ErrorStringObject("Failed to push Substance texture input : unsupported format", this);
+ return;
+ }
+
+ if (m_TextureInputs.size()<=textureInputIndex)
+ {
+ ErrorStringObject("Failed to push Substance texture input : unexpected error", this);
+ return;
+ }
+
+ // Initialize the texture input if required
+ TextureInput& textureInput = m_TextureInputs[textureInputIndex];
+ if (textureInput.inputParameters->level0Width!=requiredTextureInput.level0Width
+ || textureInput.inputParameters->level0Height!=requiredTextureInput.level0Height
+ || textureInput.inputParameters->pixelFormat!=requiredTextureInput.pixelFormat)
+ {
+ // Fill format description
+ memcpy(textureInput.inputParameters, &requiredTextureInput, sizeof(SubstanceTextureInput));
+ textureInput.inputParameters->mTexture.level0Width = textureInput.inputParameters->level0Width;
+ textureInput.inputParameters->mTexture.level0Height = textureInput.inputParameters->level0Height;
+ textureInput.inputParameters->mTexture.mipmapCount = textureInput.inputParameters->mipmapCount;
+ textureInput.inputParameters->mTexture.pixelFormat = textureInput.inputParameters->pixelFormat;
+ textureInput.inputParameters->mTexture.channelsOrder = 0;
+ textureInput.image->SetImage(textureInput.inputParameters->level0Width, textureInput.inputParameters->level0Height, format, true);
+
+ if (textureInput.buffer!=NULL)
+ {
+ UNITY_FREE(kMemSubstance, textureInput.buffer);
+ textureInput.buffer = NULL;
+ }
+
+ if (need16bitsConvert)
+ {
+ size_t pitch = ((requiredTextureInput.pixelFormat | Substance_PF_RGBA)?4:3)*sizeof(unsigned short);
+ textureInput.buffer = UNITY_MALLOC_ALIGNED_NULL(kMemSubstance, pitch*textureInput.inputParameters->level0Width*textureInput.inputParameters->level0Height, 16);
+ if (!textureInput.buffer)
+ {
+ ErrorString("Could not allocate memory for textureInput.buffer (ApplyTextureInput)");
+ return;
+ }
+ textureInput.inputParameters->mTexture.buffer = textureInput.buffer;
+ }
+ else
+ {
+ textureInput.inputParameters->mTexture.buffer = textureInput.image->GetImageData();
+ }
+ }
+
+ Texture2D* texture = textureInput.texture;
+ if (texture==NULL)
+ {
+ // Default to white texture
+ textureInput.image->ClearImage(ColorRGBAf(1.0f, 1.0f, 1.0f, 1.0f));
+ }
+ else
+ {
+ // Try to retrieve current texture
+ if (texture->ExtractImage(textureInput.image))
+ {
+ textureInput.image->FlipImageY();
+ }
+ else
+ {
+ ErrorStringObject("Incorrect ProceduralMaterial input", this);
+ if (texture->GetRawImageData()==NULL)
+ {
+ ErrorStringObject("ProceduralMaterial: Unexpected error (Texture input is not in RAM), try a reimport", texture);
+ }
+ else
+ {
+ ErrorStringObject("ProceduralMaterial: Texture input is compressed in undecompressable format, you should switch it to RAW, then reimport the material", texture);
+ }
+
+ textureInput.image->ClearImage(ColorRGBAf(1.0f, 0.0f, 0.0f, 1.0f));
+ }
+ }
+
+ // Actually we can't force the input texture format using the Substance API,
+ // so here it requires 8bits -> 16bits conversion.
+ if (need16bitsConvert)
+ {
+ size_t pitch = (requiredTextureInput.pixelFormat | Substance_PF_RGBA)?4:3;
+ unsigned short * output = (unsigned short*)textureInput.buffer;
+ unsigned char * input = textureInput.image->GetImageData();
+ unsigned char * inputEnd = input + pitch*textureInput.image->GetWidth()*textureInput.image->GetHeight();
+ while(input!=inputEnd)
+ {
+ *(output++) = (unsigned short)*(input++)*257;
+ }
+ }
+}
+#endif
+
+void ProceduralMaterial::UpdateAnimation(float time)
+{
+ if (m_AnimationUpdateRate>0)
+ {
+ if (time<m_AnimationTime
+ || time>m_AnimationTime+m_AnimationUpdateRate/1000.0f)
+ {
+ m_AnimationTime = time;
+ SetSubstanceFloat("$time", time);
+ RebuildTextures();
+ }
+ }
+}
+
+std::vector<std::string> ProceduralMaterial::GetSubstanceProperties() const
+{
+ std::vector<std::string> properties;
+ for (SubstanceInputs::const_iterator i=m_Inputs.begin();i!=m_Inputs.end();++i)
+ {
+ properties.push_back(i->name);
+ }
+ return properties;
+}
+
+bool ProceduralMaterial::HasSubstanceProperty( const std::string& inputName ) const
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ return FindSubstanceInput(inputName)!=NULL;
+}
+
+bool ProceduralMaterial::GetSubstanceBoolean( const std::string& inputName ) const
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ const SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ {
+ return input->value.scalar[0]>0.5f;
+ }
+ return false;
+}
+
+void ProceduralMaterial::SetSubstanceBoolean( const std::string& inputName, bool value )
+{
+ SetSubstanceFloat( inputName, value?1.0f:0.0f );
+}
+
+float ProceduralMaterial::GetSubstanceFloat( const std::string& inputName ) const
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ const SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ return input->value.scalar[0];
+ return 0.0F;
+}
+
+void ProceduralMaterial::SetSubstanceFloat( const std::string& inputName, float value )
+{
+ SetSubstanceVector(inputName, Vector4f(value, 0.0f, 0.0f, 0.0f));
+}
+
+Vector4f ProceduralMaterial::GetSubstanceVector( const std::string& inputName ) const
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ const SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ {
+ Vector4f value;
+ value.Set(input->value.scalar);
+ return value;
+ }
+ return Vector4f(0.0F, 0.0F, 0.0F, 0.0F);
+}
+
+void ProceduralMaterial::SetSubstanceVector( const std::string& inputName, const Vector4f& value )
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ const SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ {
+ SubstanceValue inputValue;
+ memcpy(inputValue.scalar, value.GetPtr(), sizeof(float)*4);
+ SetDirty();
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().QueueInput(this, inputName, inputValue);
+#endif
+ }
+}
+
+ColorRGBAf ProceduralMaterial::GetSubstanceColor( const std::string& inputName ) const
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ const SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ {
+ ColorRGBAf value;
+ memcpy(value.GetPtr(), input->value.scalar, sizeof(float)*4);
+ return value;
+ }
+
+ return ColorRGBAf(0.0F, 0.0F, 0.0F, 0.0F);
+}
+
+void ProceduralMaterial::SetSubstanceColor( const std::string& inputName, const ColorRGBAf& value )
+{
+ SetSubstanceVector(inputName, Vector4f(value.r, value.g, value.b, value.a));
+}
+
+int ProceduralMaterial::GetSubstanceEnum( const string& inputName )
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ {
+ int index(0);
+ for (std::vector<SubstanceEnumItem>::iterator it=input->enumValues.begin();it!=input->enumValues.end();++it)
+ {
+ if ((int)input->value.scalar[0]==it->value)
+ {
+ return index;
+ }
+
+ ++index;
+ }
+ }
+ return -1;
+}
+
+void ProceduralMaterial::SetSubstanceEnum( const string& inputName, int value )
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL && value>=0 && value<input->enumValues.size())
+ {
+ SubstanceEnumItem& item = input->enumValues[value];
+ SubstanceValue inputValue;
+ inputValue.scalar[0] = (float)item.value;
+ SetDirty();
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().QueueInput(this, inputName, inputValue);
+#endif
+ }
+}
+
+Texture2D* ProceduralMaterial::GetSubstanceTexture( const string& inputName ) const
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ const SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL && input->internalType == Substance_IType_Image)
+ return input->value.texture;
+ return NULL;
+}
+
+void ProceduralMaterial::SetSubstanceTexture( const string& inputName, Texture2D* value )
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ const SubstanceInput* input(NULL);
+ int texture_index(0);
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i!=m_Inputs.end();++i)
+ {
+ if (i->name==inputName)
+ {
+ input = &*i;
+ break;
+ }
+ if (i->type==ProceduralPropertyType_Texture)
+ ++texture_index;
+ }
+
+ if (input!=NULL && input->type==ProceduralPropertyType_Texture
+ && texture_index<m_TextureInputs.size())
+ {
+ m_TextureInputs[texture_index].texture = value;
+ SubstanceValue inputValue;
+ inputValue.texture = value;
+ SetDirty();
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().QueueInput(this, inputName, inputValue);
+#endif
+ }
+}
+
+#if ENABLE_SUBSTANCE
+void ProceduralMaterial::Callback_SetSubstanceInput( const string& inputName, SubstanceValue& inputValue)
+{
+ Mutex::AutoLock locker(m_InputMutex);
+ SubstanceInput* input = FindSubstanceInput(inputName);
+
+ //AreSubstanceInputValuesEqual(input->internalType, input->value, inputValue))
+ // Its up to the user to set value if it hasn't changed, he may want to really set the value
+ // even if it hasn't changed, to cache it for instance.
+ if (input==NULL)
+ return;
+
+ ClampSubstanceInputValues(*input, inputValue);
+
+ if (input->type==ProceduralPropertyType_Texture)
+ {
+ input->value.texture = inputValue.texture;
+ }
+ else
+ {
+ memcpy(input->value.scalar, inputValue.scalar,
+ sizeof(float)*GetRequiredInputComponentCount(input->internalType));
+ }
+
+ input->EnableFlag(SubstanceInput::Flag_Modified);
+
+#if !UNITY_EDITOR
+ if (IsFlagEnabled(Flag_ConstSize) && (input->name=="$outputsize" || input->name=="$randomseed"))
+ {
+ EnableFlag(Flag_ConstSize, false);
+ Clean();
+
+ // Initialize fresh substance data
+ std::vector<ProceduralMaterial*> materials;
+ materials.push_back(this);
+ PackSubstances(materials);
+ }
+#endif
+}
+#endif
+
+const SubstanceInput* ProceduralMaterial::FindSubstanceInput( const string& inputName ) const
+{
+ for (SubstanceInputs::const_iterator i=m_Inputs.begin();i!=m_Inputs.end();++i)
+ {
+ if (i->name==inputName)
+ return &*i;
+ }
+ return NULL;
+}
+
+SubstanceInput* ProceduralMaterial::FindSubstanceInput( const string& inputName )
+{
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i!=m_Inputs.end();++i)
+ {
+ if (i->name==inputName)
+ return &*i;
+ }
+ return NULL;
+}
+
+bool ProceduralMaterial::IsSubstancePropertyCached( const string& inputName ) const
+{
+ const SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ return input->IsFlagEnabled(SubstanceInput::Flag_Cached);
+ return false;
+}
+
+void ProceduralMaterial::CacheSubstanceProperty( const string& inputName, bool value )
+{
+ SubstanceInput* input = FindSubstanceInput(inputName);
+ if (input!=NULL)
+ input->EnableFlag(SubstanceInput::Flag_Cached, value);
+}
+
+void ProceduralMaterial::ClearCache()
+{
+#if ENABLE_SUBSTANCE
+ for (SubstanceInputs::iterator it=m_Inputs.begin() ; it!=m_Inputs.end() ; ++it)
+ it->EnableFlag(SubstanceInput::Flag_Cached, false);
+ GetSubstanceSystem().QueryClearCache(this);
+#endif
+}
+
+void ProceduralMaterial::SetProceduralMemoryBudget(ProceduralCacheSize budget)
+{
+ SetProceduralMemorySleepBudget(budget);
+
+ switch(budget)
+ {
+ case ProceduralCacheSize_Tiny: SetProceduralMemoryWorkBudget(ProceduralCacheSize_Medium); break;
+ case ProceduralCacheSize_Medium: SetProceduralMemoryWorkBudget(ProceduralCacheSize_Heavy); break;
+ case ProceduralCacheSize_Heavy: SetProceduralMemoryWorkBudget(ProceduralCacheSize_NoLimit); break;
+ case ProceduralCacheSize_NoLimit: SetProceduralMemoryWorkBudget(ProceduralCacheSize_NoLimit); break;
+ default:
+ case ProceduralCacheSize_None: SetProceduralMemoryWorkBudget(ProceduralCacheSize_Tiny); break;
+ }
+}
+
+ProceduralCacheSize ProceduralMaterial::GetProceduralMemoryBudget() const
+{
+ if (m_SubstanceData==NULL)
+ return ProceduralCacheSize_None;
+ return m_SubstanceData->memoryWorkBudget;
+}
+
+void ProceduralMaterial::SetProceduralMemoryWorkBudget(ProceduralCacheSize budget)
+{
+ if (m_SubstanceData!=NULL)
+ m_SubstanceData->memoryWorkBudget = budget;
+}
+
+ProceduralCacheSize ProceduralMaterial::GetProceduralMemoryWorkBudget() const
+{
+ if (m_SubstanceData==NULL)
+ return ProceduralCacheSize_None;
+ return m_SubstanceData->memoryWorkBudget;
+}
+
+void ProceduralMaterial::SetProceduralMemorySleepBudget(ProceduralCacheSize budget)
+{
+ if (m_SubstanceData!=NULL)
+ m_SubstanceData->memorySleepBudget = budget;
+}
+
+ProceduralCacheSize ProceduralMaterial::GetProceduralMemorySleepBudget() const
+{
+ if (m_SubstanceData==NULL)
+ return ProceduralCacheSize_None;
+ return m_SubstanceData->memorySleepBudget;
+}
+
+#if UNITY_EDITOR
+// Strips substance data during serialization when building a player
+struct TemporarilyStripSubstanceData
+{
+ ProceduralMaterial* material;
+ PPtr<SubstanceArchive> substancePackage;
+ SubstanceInputs inputs;
+
+ TemporarilyStripSubstanceData (ProceduralMaterial& mat, bool isBuildingPlayer)
+ {
+ bool shouldDiscardSubstanceData;
+ shouldDiscardSubstanceData = !IsSubstanceSupportedOnPlatform(GetEditorUserBuildSettings().GetActiveBuildTarget());
+ shouldDiscardSubstanceData |= mat.GetLoadingBehavior() == ProceduralLoadingBehavior_BakeAndDiscard;
+
+ // Should we discard the substance data?
+ if (isBuildingPlayer && shouldDiscardSubstanceData)
+ {
+ material = &mat;
+
+ // Clear m_SubstancePackage & m_Inputs and back it up so we can revert them after serialization
+ swap(substancePackage, mat.m_SubstancePackage);
+ swap(inputs, mat.m_Inputs);
+ }
+ else
+ {
+ material = NULL;
+ }
+ }
+
+ ~TemporarilyStripSubstanceData ()
+ {
+ if (material != NULL)
+ {
+ swap(material->m_Inputs, inputs);
+ swap(material->m_SubstancePackage, substancePackage);
+ }
+ }
+};
+#endif
+
+
+
+template<class T> void ProceduralMaterial::Transfer( T& transfer )
+{
+ Super::Transfer( transfer );
+
+ // Serialize maximum sizes
+ if (transfer.IsVersionSmallerOrEqual (2))
+ {
+ int m_MaximumSize;
+ TRANSFER( m_MaximumSize );
+ m_Width = m_MaximumSize;
+ m_Height = m_MaximumSize;
+ }
+ else
+ {
+ TRANSFER( m_Width );
+ TRANSFER( m_Height );
+ }
+
+ TRANSFER( m_Textures );
+ TRANSFER( m_Flags );
+
+ // Serialize load behavior
+ if (transfer.IsReading ())
+ {
+ // Handle deprecated GenerateAtLoad flag, replaced by LoadingBehavior
+ m_LoadingBehavior = IsFlagEnabled(Flag_DeprecatedGenerateAtLoad)?ProceduralLoadingBehavior_Generate:ProceduralLoadingBehavior_None;
+ EnableFlag(Flag_DeprecatedGenerateAtLoad, false);
+ }
+ transfer.Transfer(reinterpret_cast<int&> (m_LoadingBehavior), "m_LoadingBehavior");
+
+ #if UNITY_EDITOR
+ // Strip unused data when building/collecting assets
+ TemporarilyStripSubstanceData stripData (*this, transfer.GetFlags () & kBuildPlayerOnlySerializeBuildProperties);
+ #endif
+
+ TRANSFER( m_SubstancePackage );
+ TRANSFER( m_Inputs );
+
+ TRANSFER( m_PrototypeName );
+ if (m_PrototypeName=="")
+ {
+ m_PrototypeName = GetName();
+ }
+
+ TRANSFER( m_AnimationUpdateRate );
+
+ TRANSFER(m_Hash);
+}
+
+bool ProceduralMaterial::IsProcessing() const
+{
+#if ENABLE_SUBSTANCE
+ return GetSubstanceSystem().IsSubstanceProcessing(this);
+#else
+ return false;
+#endif
+}
+
+void ProceduralMaterial::SetProceduralProcessorUsage(ProceduralProcessorUsage processorUsage)
+{
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().SetProcessorUsage(processorUsage);
+#endif
+}
+
+ProceduralProcessorUsage ProceduralMaterial::GetProceduralProcessorUsage()
+{
+#if ENABLE_SUBSTANCE
+ return GetSubstanceSystem().GetProcessorUsage();
+#else
+ return ProceduralProcessorUsage_Unsupported;
+#endif
+}
+
+void ProceduralMaterial::StopProcessing()
+{
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().ClearProcessingQueue();
+#endif
+}
+
+#if UNITY_EDITOR
+
+void ProceduralMaterial::InvalidateIfCachedOrInvalidTextures()
+{
+ // Check if some textures are cached
+ bool cachedOrInvalid=false;
+ for (PingedTextures::iterator i=m_PingedTextures.begin();i!=m_PingedTextures.end();++i)
+ {
+ ProceduralTexture* texture = *i;
+ if (texture!=NULL
+ && (texture->IsFlagEnabled(ProceduralTexture::Flag_Cached)
+ || !texture->IsValid()))
+ {
+ cachedOrInvalid = true;
+ break;
+ }
+ }
+
+ if (cachedOrInvalid)
+ {
+ // Check if an input has been modified
+ bool modified = false;
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i != m_Inputs.end();++i)
+ {
+ SubstanceInput& input = *i;
+ if (input.IsFlagEnabled(SubstanceInput::Flag_Modified))
+ {
+ modified = true;
+ break;
+ }
+ }
+ // Force rebuild since no input is modified
+ if (!modified)
+ {
+ for (SubstanceInputs::iterator i=m_Inputs.begin();i != m_Inputs.end();++i)
+ {
+ SubstanceInput& input = *i;
+ input.EnableFlag(SubstanceInput::Flag_Awake);
+ }
+ }
+ }
+}
+
+bool IsSubstanceSupportedOnPlatform(BuildTargetPlatform platform)
+{
+return (platform == kBuildWebPlayerLZMA
+ || platform == kBuildWebPlayerLZMAStreamed
+ || platform == kBuildStandaloneOSXIntel
+ || platform == kBuildStandaloneOSXIntel64
+ || platform == kBuildStandaloneOSXUniversal
+ || platform == kBuildStandaloneWinPlayer
+ || platform == kBuildStandaloneWin64Player
+ || platform == kBuildStandaloneLinux
+ || platform == kBuildStandaloneLinux64
+ || platform == kBuildStandaloneLinuxUniversal
+ || platform == kBuild_Android
+ || platform == kBuild_iPhone
+ || platform == kBuildNaCl
+ );
+}
+#endif
+
+bool IsSubstanceSupported()
+{
+#if UNITY_EDITOR
+ BuildTargetPlatform platform = GetEditorUserBuildSettings().GetActiveBuildTarget();
+ return IsSubstanceSupportedOnPlatform(platform);
+#endif
+
+#if ENABLE_SUBSTANCE
+ return true;
+#else
+ return false;
+#endif
+}
+
+TextureFormat GetSubstanceTextureFormat(SubstanceOutputFormat outputFormat, bool requireCompressed)
+{
+#if ENABLE_SUBSTANCE
+#if UNITY_EDITOR
+ BuildTargetPlatform platform = GetEditorUserBuildSettings().GetActiveBuildTarget();
+ if (!requireCompressed || IsSubstanceSupportedOnPlatform(platform))
+#endif
+ {
+ TextureFormat format(kTexFormatRGBA32);
+
+ if (outputFormat==Substance_OFormat_Compressed)
+ {
+#if UNITY_IPHONE
+ format = kTexFormatPVRTC_RGBA4;
+#elif UNITY_ANDROID
+ if (gGraphicsCaps.supportsTextureFormat[kTexFormatDXT5])
+ {
+ format = kTexFormatDXT5;
+ }
+ else if (gGraphicsCaps.supportsTextureFormat[kTexFormatPVRTC_RGBA4])
+ {
+ format = kTexFormatPVRTC_RGBA4;
+ }
+ else
+ {
+ // Lowest common denominator = ETC
+ // But this will cancel the alpha, need to think of something else for non-DXT non-PVR platforms
+ // format = kTexFormatETC_RGB4;
+ }
+#else
+ format = kTexFormatDXT5;
+#endif
+ }
+
+ return format;
+ }
+#endif
+
+ return kTexFormatRGBA32;
+}
+
+SubstanceEngineIDEnum GetSubstanceEngineID()
+{
+#if defined(__ppc__)
+ return Substance_EngineID_xenos;
+#endif
+ return Substance_EngineID_sse2;
+}
diff --git a/Runtime/Graphics/ProceduralMaterial.h b/Runtime/Graphics/ProceduralMaterial.h
new file mode 100644
index 0000000..e008566
--- /dev/null
+++ b/Runtime/Graphics/ProceduralMaterial.h
@@ -0,0 +1,289 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Threads/Mutex.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Utilities/Hash128.h"
+#include "ProceduralTexture.h"
+#include "SubstanceInput.h"
+#include "External/Allegorithmic/builds/Engines/include/substance/handle.h"
+#include "External/Allegorithmic/builds/Engines/include/substance/linker/linker.h"
+
+class SubstanceArchive;
+class Texture2D;
+class Image;
+
+#if UNITY_EDITOR
+bool IsSubstanceSupportedOnPlatform(BuildTargetPlatform platform);
+#endif
+
+bool IsSubstanceSupported(); // return true if Substance is supported on current platform
+TextureFormat GetSubstanceTextureFormat(SubstanceOutputFormat outputFormat, bool requireCompressed=false); // return the required Substance texture format
+SubstanceEngineIDEnum GetSubstanceEngineID(); // return the required Substance engine ID
+
+// Global Substance processor usage
+enum ProceduralProcessorUsage
+{
+ ProceduralProcessorUsage_Unsupported = 0, // Substance isn't supported
+ ProceduralProcessorUsage_One, // uses only one CPU
+ ProceduralProcessorUsage_Half, // uses half of the CPU count (and not the first one if applicable)
+ ProceduralProcessorUsage_All // uses all the CPU
+};
+
+// Memory budget
+enum ProceduralCacheSize
+{
+ ProceduralCacheSize_Tiny = 0, // 128 Mb
+ ProceduralCacheSize_Medium, // 256 Mb
+ ProceduralCacheSize_Heavy, // 512 Mb
+ ProceduralCacheSize_NoLimit, // unlimited
+ ProceduralCacheSize_None // 1 byte
+};
+
+size_t GetProceduralMemoryBudget(ProceduralCacheSize budget);
+
+// Loading behavior
+enum ProceduralLoadingBehavior
+{
+ ProceduralLoadingBehavior_None = 0, // do nothing
+ ProceduralLoadingBehavior_Generate, // generate textures
+ ProceduralLoadingBehavior_BakeAndKeep, // use baked textures, the substance may be generated later on
+ ProceduralLoadingBehavior_BakeAndDiscard, // use baked textures, the substance data is removed from runtime
+ ProceduralLoadingBehavior_Cache // generate textures and cache it, then use it at runtime to speed-up the loading
+};
+
+/* A ProceduralMaterial is a dynamic material that use the Substance engine.
+ */
+
+class ProceduralMaterial : public Material
+{
+public: // NESTED TYPES
+
+ typedef std::vector<PPtr<ProceduralTexture> > Textures;
+ typedef std::vector<ProceduralTexture*> PingedTextures;
+
+public: // METHODS
+
+ REGISTER_DERIVED_CLASS( ProceduralMaterial, Material )
+ DECLARE_OBJECT_SERIALIZE( ProceduralMaterial )
+
+ ProceduralMaterial( MemLabelId label, ObjectCreationMode mode );
+
+ void Clean();
+
+ ProceduralMaterial* Clone();
+private:
+ void RebuildClone();
+
+public:
+#if UNITY_EDITOR
+ // Creates the material (one time call only from the importer)
+ void Init( SubstanceArchive& substancePackage, const UnityStr& prototypeName, const SubstanceInputs& inputs, const Textures& textures );
+ SubstanceArchive* GetSubstancePackage() { return m_PingedPackage; }
+ const char* GetSubstancePackageName();
+#endif
+ const Textures& GetTextures() const { return m_Textures; }
+ Textures& GetTextures() { return m_Textures; }
+ PingedTextures& GetPingedTextures() { return m_PingedTextures; }
+ SubstanceInputs& GetSubstanceInputs () { return m_Inputs; }
+ const SubstanceInputs& GetSubstanceInputs () const { return m_Inputs; }
+ const SubstanceArchive* GetSubstancePackage() const { return m_PingedPackage; }
+ SubstanceHandle* GetSubstanceHandle();
+
+ // Generation sizes accessors
+ void SetSize(int width, int height);
+ int GetWidth() const { return m_Width; }
+ int GetHeight() const { return m_Height; }
+
+ // Threaded loading, launch the generation if all data is available
+ void AwakeFromLoadThreaded();
+
+ // Used in the editor to generate at reimport time
+ void AwakeFromLoad( AwakeFromLoadMode awakeMode );
+
+ // Call the rebuild of all the textures and updates the SBS texture assets
+ // generationType, the type of requested texture generation
+ // forceTextureGeneration, a boolean to force textures generation even though they may be up to date from the engine's point of view
+ void RebuildTextures();
+
+ // Rebuilds all texture immediately in a synchronous manner
+ // When that function returns, all textures should have been generated
+ void RebuildTexturesImmediately();
+
+ // Call the rebuild of all the textures in all of the Procedural Materials
+ static void ReloadAll (bool unload = true, bool load = true);
+
+ // Awake dependent objects
+ void AwakeDependencies(bool awakeThreaded);
+
+ // Process rebuild of textures
+#if ENABLE_SUBSTANCE
+ static ProceduralMaterial* m_PackedSubstance;
+ static void PackSubstances(std::vector<ProceduralMaterial*>& materials);
+ bool ProcessTexturesThreaded(const std::map<ProceduralTexture*, SubstanceTexture>& textures);
+#if !UNITY_EDITOR
+ bool PreProcess(std::set<unsigned int>& cachedTextureIDs);
+ void PostProcess(const std::map<ProceduralTexture*, SubstanceTexture>& textures, const std::set<unsigned int>& cachedTextureIDs);
+#endif
+#endif
+
+ // Scriptable input accessors
+ std::vector<std::string> GetSubstanceProperties() const;
+ bool HasSubstanceProperty( const std::string& inputName ) const;
+ bool GetSubstanceBoolean( const std::string& inputName ) const;
+ void SetSubstanceBoolean( const std::string& inputName, bool value );
+ float GetSubstanceFloat( const std::string& inputName ) const;
+ void SetSubstanceFloat( const std::string& inputName, float value );
+ Vector4f GetSubstanceVector( const std::string& inputName ) const;
+ void SetSubstanceVector( const std::string& inputName, const Vector4f& value );
+ ColorRGBAf GetSubstanceColor( const std::string& inputName ) const;
+ void SetSubstanceColor( const std::string& inputName, const ColorRGBAf& value );
+ int GetSubstanceEnum( const string& inputName );
+ void SetSubstanceEnum( const string& inputName, int value );
+ Texture2D* GetSubstanceTexture( const string& inputName ) const;
+ void SetSubstanceTexture( const string& inputName, Texture2D* value );
+
+#if ENABLE_SUBSTANCE
+ // Called by the SubstanceSystem when he process a "SetInput" command
+ void Callback_SetSubstanceInput( const string& inputName, SubstanceValue& inputValue );
+#endif
+
+ const SubstanceInput* FindSubstanceInput( const string& inputName ) const;
+ SubstanceInput* FindSubstanceInput( const string& inputName );
+
+ // Property caching
+ bool IsSubstancePropertyCached( const string& inputName ) const;
+ void CacheSubstanceProperty( const string& inputName, bool value );
+ void ClearCache();
+
+ // Memory budget
+ void SetProceduralMemoryBudget(ProceduralCacheSize budget);
+ ProceduralCacheSize GetProceduralMemoryBudget() const;
+ void SetProceduralMemoryWorkBudget(ProceduralCacheSize budget);
+ ProceduralCacheSize GetProceduralMemoryWorkBudget() const;
+ void SetProceduralMemorySleepBudget(ProceduralCacheSize budget);
+ ProceduralCacheSize GetProceduralMemorySleepBudget() const;
+
+protected:
+
+#if ENABLE_SUBSTANCE
+ void ApplyInputs (bool& it_has_changed, bool asHint, std::set<unsigned int>& modifiedOutputsUID);
+ void ApplyOutputs (bool& it_has_changed, bool asHint, std::set<unsigned int>& modifiedOutputsUID, const std::set<unsigned int>& cachedTextureIDs);
+#endif
+
+private:
+
+ // Shared substance data
+ struct SubstanceData
+ {
+ UInt8* substanceData; // Platform dependent linked binary content
+ SubstanceHandle* substanceHandle; // Substance engine handle, shared by instances
+ ProceduralCacheSize memoryWorkBudget; // 'Work' cache size, shared by instances
+ ProceduralCacheSize memorySleepBudget; // 'Sleep' cache size, shared by instances
+ int instanceCount; // Count of substances using the same handle
+ };
+
+ PPtr<SubstanceArchive> m_SubstancePackage; // The parent SBS package from which we get generated
+ SubstanceArchive* m_PingedPackage; // The pinged package
+ SubstanceData* m_SubstanceData; // Shared substance data
+ UnityStr m_PrototypeName; // The name of the original graph in the package
+ int m_Width; // Width
+ int m_Height; // Height
+ Textures m_Textures; // The list of persistent output textures for that material
+ PingedTextures m_PingedTextures; // The list of pinged output textures
+ SubstanceInputs m_Inputs; // Substance inputs
+ static Mutex m_InputMutex; // Input accessors mutex
+ Hash128 m_Hash; // Hash used for cache status checking
+
+public:
+ // Texture inputs
+ struct TextureInput
+ {
+ Texture2D* texture;
+ Image* image;
+ SubstanceTextureInput* inputParameters;
+ void* buffer;
+ };
+ std::vector<TextureInput> m_TextureInputs;
+#if UNITY_EDITOR
+ std::vector<TextureInput>& GetTextureInputs() { return m_TextureInputs; }
+#endif
+
+ void ApplyTextureInput (int substanceInputIndex, const SubstanceTextureInput& requiredTextureInput);
+
+ // Animated substances
+private:
+ int m_AnimationUpdateRate;
+ float m_AnimationTime;
+public:
+ void SetAnimationUpdateRate(int rate) { m_AnimationUpdateRate = rate; }
+ int GetAnimationUpdateRate() const { return m_AnimationUpdateRate; }
+ void UpdateAnimation(float time);
+
+ // Flags
+public:
+ enum Flag
+ {
+ Flag_DeprecatedGenerateAtLoad = 1<<0, // deprecated
+ Flag_Animated = 1<<2, // the material has animated textures
+ Flag_AwakeClone = 1<<3, // the material is a clone which require to be awaken
+ Flag_GenerateAll = 1<<4, // we force the generation of all outputs
+ Flag_ConstSize = 1<<5, // the size and seed don't change at runtime
+ Flag_ForceGenerate = 1<<6, // force the generation
+ Flag_Clone = 1<<7, // the material is a clone
+ Flag_Import = 1<<8, // the material is being imported
+ Flag_Awake = 1<<9, // the material is awakening
+ Flag_Uncompressed = 1<<10, // the import is forced uncompressed
+ Flag_Broken = 1<<11, // some dependencies are lacking, the substance can't be generated
+ Flag_Readable = 1<<12 // generated textures are readable, provided it's in RAW format
+ };
+ void EnableFlag(const Flag& flag, bool enabled=true) { if (enabled) m_Flags |= (unsigned int)flag; else m_Flags &= ~(unsigned int)flag; }
+ bool IsFlagEnabled(const Flag& flag) const { return m_Flags & (unsigned int)flag; }
+private:
+ unsigned int m_Flags;
+
+ // Loading mode
+public:
+ void SetLoadingBehavior(ProceduralLoadingBehavior behavior) { m_LoadingBehavior = behavior; }
+ ProceduralLoadingBehavior GetLoadingBehavior() const { return m_LoadingBehavior; }
+private:
+ ProceduralLoadingBehavior m_LoadingBehavior;
+
+// Substance processing
+public:
+ bool IsProcessing() const;
+ static void SetProceduralProcessorUsage(ProceduralProcessorUsage processorUsage);
+ static ProceduralProcessorUsage GetProceduralProcessorUsage();
+ static void StopProcessing();
+ SubstanceData* GetSubstanceData() { return m_SubstanceData; }
+
+// Presets handling
+ bool SetPreset(const std::string& presetContent);
+ std::string GetPreset() const;
+
+// Textures accessors
+ ProceduralTexture* GetGeneratedTexture(const std::string& textureName);
+
+// Integration
+ unsigned int integrationTimeStamp;
+
+#if UNITY_EDITOR
+ UInt8* GetHashPtr() { return m_Hash.hashData.bytes; }
+ void InvalidateIfCachedOrInvalidTextures();
+
+ friend struct TemporarilyStripSubstanceData;
+#endif
+
+// Enter/Leave PlayMode
+#if UNITY_EDITOR
+ bool m_isAlreadyLoadedInCurrentScene;
+#endif
+
+// Caching
+#if ENABLE_SUBSTANCE && !UNITY_EDITOR
+ std::string GetCacheFolder() const;
+ std::string GetCacheFilename(const ProceduralTexture& texture) const;
+ bool ReadCachedTexture(string& fileName, std::map<ProceduralTexture*, SubstanceTexture>& cachedTextures, const std::string& folder, const ProceduralTexture& texture);
+ bool WriteCachedTexture(string& fileName, const std::string& folder, const ProceduralTexture& texture, const SubstanceTexture& data);
+#endif
+};
diff --git a/Runtime/Graphics/ProceduralPreset.cpp b/Runtime/Graphics/ProceduralPreset.cpp
new file mode 100644
index 0000000..154cdd3
--- /dev/null
+++ b/Runtime/Graphics/ProceduralPreset.cpp
@@ -0,0 +1,143 @@
+#include "UnityPrefix.h"
+#include "ProceduralMaterial.h"
+
+int ProceduralPreset_parseValues(std::vector<std::string>& values, std::string name, std::string line)
+{
+ values.clear();
+ int start = line.find(name);
+ if (start==std::string::npos)
+ {
+ return 0;
+ }
+
+ line = line.substr(start+name.size());
+ if (line.size()<3 || line[0]!='=' || line[1]!='\"')
+ {
+ return 0;
+ }
+
+ line = line.substr(2);
+ int p=0;
+ string val;
+ while (p<line.size() && line[p]!='\"')
+ {
+ if (line[p]==',')
+ {
+ values.push_back(val); val = "";
+ }
+ else
+ {
+ val += line[p];
+ }
+ ++p;
+ }
+ if (val.size()>0)
+ {
+ values.push_back(val);
+ }
+ return values.size();
+}
+
+inline std::string ProceduralPreset_getLine(std::string::const_iterator& pos, const std::string& preset)
+{
+ std::string::const_iterator i = pos;
+ std::string::const_iterator it = std::find(i, preset.end(), '\n');
+ pos = it;
+ if (it!=preset.end()) ++pos;
+ return std::string(i, it);
+}
+
+bool ProceduralMaterial::SetPreset(const std::string& preset)
+{
+ std::string::const_iterator pos = preset.begin();
+ std::string line;
+ line = ProceduralPreset_getLine(pos, preset);
+ if (line.find("formatversion=\"1.0\"")==std::string::npos)
+ {
+ return false;
+ }
+
+ while (pos!=preset.end() && line.find("/sbspreset")==std::string::npos)
+ {
+ line = ProceduralPreset_getLine(pos, preset);
+ if (line.find("presetinput")!=std::string::npos)
+ {
+ std::vector<std::string> identifier;
+ if (ProceduralPreset_parseValues(identifier, "identifier", line)!=1)
+ continue;
+
+ SubstanceInput* input = FindSubstanceInput(identifier[0]);
+ if (input==NULL || identifier[0]=="$normalformat" || identifier[0]=="$outputsize")
+ continue;
+
+ std::vector<std::string> type;
+ if (ProceduralPreset_parseValues(type, "type", line)!=1)
+ continue;
+
+ unsigned int internalType = atoi(type[0].c_str());
+ if (internalType!=input->internalType || internalType==Substance_IType_Image)
+ continue;
+
+ std::vector<std::string> value;
+ ProceduralPreset_parseValues(value, "value", line);
+ float f[4];
+ int count = GetRequiredInputComponentCount((SubstanceInputType)internalType);
+ if (value.size()!=count)
+ continue;
+ for (int i=0 ; i<count ; ++i)
+ {
+ f[i] = (float)atof(value[i].c_str());
+ }
+ SetSubstanceVector(identifier[0], Vector4f(f));
+ }
+ }
+ RebuildTextures();
+ return true;
+}
+
+std::string ProceduralMaterial::GetPreset() const
+{
+ std::string preset = "<sbspresets formatversion=\"1.0\" count=\"1\">\n";
+ preset += " <sbspreset pkgurl=\"\" description=\"\" label=\"\">\n";
+ char tmp[256];
+ for (SubstanceInputs::const_iterator i=m_Inputs.begin() ; i!=m_Inputs.end() ; ++i)
+ {
+ preset += " <presetinput identifier=\"";
+ preset += i->name;
+ preset += "\" uid=\"";
+ snprintf(tmp, 256, "%d", i->internalIdentifier);
+ preset += tmp;
+ preset += "\" type=\"";
+ snprintf(tmp, 256, "%d", (int)i->internalType);
+ preset += tmp;
+ preset += "\" value=\"";
+ if (i->internalType!=Substance_IType_Image)
+ {
+ bool isInteger = IsSubstanceAnyIntType(i->internalType);
+ int count = GetRequiredInputComponentCount(i->internalType);
+ for (int j=0 ; j<count ; ++j)
+ {
+ if (j>0) preset += ",";
+ if (isInteger) snprintf(tmp, 256, "%d", (int)i->value.scalar[j]);
+ else snprintf(tmp, 256, "%f", i->value.scalar[j]);
+ preset += tmp;
+ }
+ }
+ preset += "\"/>\n";
+ }
+ preset += " </sbspreset>\n";
+ preset += "</sbspresets>\n";
+ return preset;
+}
+
+ProceduralTexture* ProceduralMaterial::GetGeneratedTexture(const std::string& textureName)
+{
+ for (Textures::iterator it=m_Textures.begin() ; it!=m_Textures.end() ; ++it)
+ {
+ if (it->IsValid() && (*it)->GetName()==textureName)
+ {
+ return &**it;
+ }
+ }
+ return NULL;
+}
diff --git a/Runtime/Graphics/ProceduralTexture.cpp b/Runtime/Graphics/ProceduralTexture.cpp
new file mode 100644
index 0000000..c4ea5ea
--- /dev/null
+++ b/Runtime/Graphics/ProceduralTexture.cpp
@@ -0,0 +1,372 @@
+#include "UnityPrefix.h"
+#include "ProceduralTexture.h"
+#include "SubstanceArchive.h"
+#include "ProceduralMaterial.h"
+#include "Image.h"
+#include "SubstanceSystem.h"
+#include "Texture2D.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Serialize/PersistentManager.h"
+#include "Runtime/Graphics/S3Decompression.h"
+#include "Runtime/File/ApplicationSpecificPersistentDataPath.h"
+#include "External/shaderlab/Library/shaderlab.h"
+#include "External/shaderlab/Library/properties.h"
+
+using namespace std;
+
+ProceduralTexture::TextureParameters::TextureParameters() :
+ width (0),
+ height(0),
+ mipLevels(0),
+ textureFormat(kTexFormatTotalCount)
+{
+}
+
+ProceduralTexture::TextureParameters::TextureParameters(int inWidth, int inHeight, int inMipLevels, TextureFormat inFormat) :
+ width (inWidth),
+ height(inHeight),
+ mipLevels(inMipLevels),
+ textureFormat(inFormat)
+{
+}
+
+IMPLEMENT_CLASS( ProceduralTexture )
+IMPLEMENT_OBJECT_SERIALIZE( ProceduralTexture )
+
+ProceduralTexture::ProceduralTexture( MemLabelId label, ObjectCreationMode mode ) :
+ Super( label, mode ),
+ m_SubstanceTextureUID( 0 ),
+ m_Type(Substance_OType_Unknown),
+ m_AlphaSource(Substance_OType_Unknown),
+ m_Flags(0),
+ m_UploadState(UploadState_None),
+ m_Format(Substance_OFormat_Compressed),
+ m_PingedMaterial(NULL)
+{
+}
+
+ProceduralTexture::~ProceduralTexture()
+{
+ // Update pointer
+ if (m_PingedMaterial==NULL)
+ m_PingedMaterial = dynamic_pptr_cast<ProceduralMaterial*>(Object::IDToPointer(m_SubstanceMaterial.GetInstanceID()));
+
+#if ENABLE_SUBSTANCE
+ /////@TODO: This is an incredible hack. Waiting for another process to complete in the destructor is a big no no, especially when it involves a Thread::Sleep ()...
+ UnlockObjectCreation();
+ GetSubstanceSystem ().NotifyTextureDestruction(this);
+ LockObjectCreation();
+#endif
+
+ RemoveTexture ();
+}
+
+#if ENABLE_SUBSTANCE
+ProceduralTexture* ProceduralTexture::Clone(ProceduralMaterial* owner)
+{
+ ProceduralTexture* clone = CreateObjectFromCode<ProceduralTexture>();
+ clone->m_SubstanceMaterial = owner;
+ clone->m_PingedMaterial = owner;
+ clone->m_SubstanceTextureUID = m_SubstanceTextureUID;
+ clone->m_Type = m_Type;
+ clone->m_AlphaSource = m_AlphaSource;
+ clone->m_Format = m_Format;
+ clone->m_SubstanceFormat = m_SubstanceFormat;
+ clone->m_ClonedID = GetTextureID();
+ clone->SetName(GetName());
+ clone->EnableFlag(Flag_AwakeClone, true);
+ clone->SetUsageMode(GetUsageMode());
+ return clone;
+}
+
+void ProceduralTexture::AwakeClone()
+{
+ // Update material texenvs
+ const ShaderLab::PropertySheet::TexEnvs& textureProperties = GetSubstanceMaterial()->GetProperties().GetTexEnvsMap();
+ for (ShaderLab::PropertySheet::TexEnvs::const_iterator i=textureProperties.begin();i!=textureProperties.end();++i)
+ {
+ if (i->second.texEnv->GetAssignedTextureID()==m_ClonedID)
+ {
+ GetSubstanceMaterial()->SetTexture(i->first, this);
+ }
+ }
+ EnableFlag(Flag_AwakeClone, false);
+}
+#endif
+
+#if UNITY_EDITOR
+void ProceduralTexture::Init( ProceduralMaterial& _Parent, int substanceTextureUID, ProceduralOutputType type, SubstanceOutputFormat format, ProceduralOutputType alphaSource, bool requireCompressed )
+{
+ m_SubstanceMaterial = &_Parent;
+ m_SubstanceTextureUID = ((UInt64)substanceTextureUID)<<32;
+ m_Type = type;
+ m_Format = format;
+ m_TextureParameters.textureFormat = GetSubstanceTextureFormat(format, requireCompressed);
+ m_AlphaSource = alphaSource;
+ m_Flags = 0;
+
+ AwakeFromLoad(kDefaultAwakeFromLoad);
+}
+#endif
+
+bool ProceduralTexture::IsBaked() const
+{
+ return m_BakedParameters.IsValid() && m_BakedData.size()>0;
+}
+
+bool ProceduralTexture::GetPixels32(int x, int y, int width, int height, ColorRGBA32* data)
+{
+ if (m_Format != Substance_OFormat_Raw)
+ {
+ WarningStringMsg("Substance %s should be set to RAW in order to use GetPixels32 on its texture outputs.", m_PingedMaterial->GetName());
+ return false;
+ }
+ if (! m_PingedMaterial->IsFlagEnabled(ProceduralMaterial::Flag_Readable))
+ {
+ WarningStringMsg("The isReadable property of Substance %s should be set to true in order to use GetPixels32 on its texture outputs.", m_PingedMaterial->GetName());
+ return false;
+ }
+ const ProceduralTexture::TextureParameters& parameters = GetBakedParameters();
+ if (GetBakedData().size()==0 || (parameters.textureFormat!=kTexFormatRGBA32 && parameters.textureFormat!=kTexFormatARGB32))
+ return false;
+ ImageReference rawTexture(parameters.width, parameters.height, GetRowBytesFromWidthAndFormat(parameters.width, parameters.textureFormat), parameters.textureFormat, &GetBakedData()[0]);
+ ImageReference resultImage(parameters.width, parameters.height, GetRowBytesFromWidthAndFormat(parameters.width, kTexFormatRGBA32), kTexFormatRGBA32, data);
+ resultImage.BlitImage(rawTexture, ImageReference::BLIT_COPY);
+ return true;
+}
+
+void ProceduralTexture::Invalidate()
+{
+ m_TextureParameters.Invalidate();
+}
+
+void ProceduralTexture::UnloadFromGfxDevice (bool forceUnloadAll)
+{
+ RemoveTexture();
+}
+
+void ProceduralTexture::UploadToGfxDevice ()
+{
+ if (!m_BakedParameters.IsValid())
+ return;
+ if (m_BakedData.size()==0)
+ {
+ GetPersistentManager().ReloadFromDisk(this);
+ }
+ else
+ {
+ UploadBakedTexture();
+ }
+}
+
+void ProceduralTexture::RemoveTexture ()
+{
+ if (IsFlagEnabled (Flag_Uploaded))
+ {
+ GetGfxDevice().DeleteTexture (GetTextureID());
+ EnableFlag(Flag_Uploaded, false);
+ m_UploadState = UploadState_None;
+ }
+}
+
+void ProceduralTexture::UploadWaitingTexture ()
+{
+ RemoveTexture ();
+ // Upload blue texture to show the substance is waiting generation
+ UInt8 bluePixel[] = { 255, 0, 0, 255 };
+ GetGfxDevice().UploadTexture2D( GetTextureID(), GetDimension(), bluePixel, sizeof(bluePixel),
+ 1, 1, kTexFormatARGB32, 1, true, 0 /* \note We upload only one level so can't skip any */,
+ GetUsageMode(), kTexColorSpaceLinear);
+ Texture::s_TextureIDMap.insert (std::make_pair(GetTextureID(),this));
+ EnableFlag(Flag_Uploaded, true);
+ m_UploadState = UploadState_Waiting;
+ m_TextureSettings.Apply( GetTextureID(), GetDimension(), false, kTexColorSpaceLinear );
+}
+
+void ProceduralTexture::UploadBakedTexture ()
+{
+ RemoveTexture ();
+ Assert(m_BakedData.size()>0);
+ GetGfxDevice().UploadTexture2D( GetTextureID(), GetDimension(), &m_BakedData[0], m_BakedData.size(),
+ m_BakedParameters.width, m_BakedParameters.height, m_BakedParameters.textureFormat,
+ m_BakedParameters.mipLevels, true, 0 /* \note We upload only one level so can't skip any */,
+ GetUsageMode(), GetActiveTextureColorSpace());
+ Texture::s_TextureIDMap.insert (std::make_pair(GetTextureID(),this));
+ EnableFlag(Flag_Uploaded, true);
+ m_UploadState = UploadState_Baked;
+ m_TextureSettings.Apply( GetTextureID(), GetDimension(), m_BakedParameters.mipLevels != 1, GetActiveTextureColorSpace() );
+ m_TextureParameters = m_BakedParameters;
+
+#if !UNITY_EDITOR
+ m_BakedData.clear();
+#endif
+}
+
+void ProceduralTexture::SetSubstanceShuffledUID(unsigned int textureUID)
+{
+ m_SubstanceTextureUID &= ((UInt64)0xffffffff) << 32;
+ m_SubstanceTextureUID |= (UInt64)textureUID;
+}
+
+void ProceduralTexture::AwakeFromLoadThreaded()
+{
+ Super::AwakeFromLoadThreaded();
+
+ // Update format before it gets packed & generated
+ m_SubstanceFormat = GetSubstanceTextureFormat(m_Format);
+
+#if UNITY_ANDROID || UNITY_IPHONE
+ // It is more practical to do this check at runtime since for Substances we control
+ // the way we generate the normal maps. Doing this here instead of in the editor avoids
+ // the reimports that would be associated with platform switches (non-mobile <-> mobile).
+ if (m_UsageMode == kTexUsageNormalmapDXT5nm)
+ {
+ // Override normal format
+ m_UsageMode = kTexUsageNormalmapPlain;
+ }
+#endif
+}
+
+void ProceduralTexture::AwakeFromLoad( AwakeFromLoadMode awakeMode )
+{
+ Super::AwakeFromLoad( awakeMode );
+
+ // Force loading of material
+ if ((awakeMode & kDidLoadThreaded) == 0)
+ {
+ ProceduralMaterial* material = m_SubstanceMaterial;
+ if (material==NULL)
+ {
+ // This happens
+ }
+ }
+
+ if (IsBaked())
+ {
+ if (m_UploadState<UploadState_Valid)
+ UploadBakedTexture();
+ }
+ else
+ {
+ if (m_UploadState==UploadState_None)
+ UploadWaitingTexture();
+ }
+
+ m_SubstanceFormat = GetSubstanceTextureFormat(m_Format);
+
+#if UNITY_ANDROID || UNITY_IPHONE
+ // It is more practical to do this check at runtime since for Substances we control
+ // the way we generate the normal maps. Doing this here instead of in the editor avoids
+ // the reimports that would be associated with platform switches (non-mobile <-> mobile).
+ if (m_UsageMode == kTexUsageNormalmapDXT5nm)
+ {
+ // Override normal format
+ m_UsageMode = kTexUsageNormalmapPlain;
+ }
+#endif
+}
+
+void ProceduralTexture::UploadSubstanceTexture(SubstanceTexture& outputTexture)
+{
+#if ENABLE_SUBSTANCE
+ // Check if it's full pyramid
+ if (outputTexture.mipmapCount==0)
+ {
+ outputTexture.mipmapCount = CalculateMipMapCount3D(outputTexture.level0Width, outputTexture.level0Height, 1);
+ }
+
+ // Check if we can replace the existing texture data
+ TextureParameters state (outputTexture.level0Width, outputTexture.level0Height, outputTexture.mipmapCount, m_SubstanceFormat);
+
+ bool reuseTextureMemory = (state==m_TextureParameters);
+ if (!reuseTextureMemory)
+ RemoveTexture ();
+
+ int bufferSize = CalculateMipMapOffset(state.width, state.height, state.textureFormat, state.mipLevels+1);
+ GetGfxDevice().UploadTexture2D( GetTextureID(), GetDimension(), reinterpret_cast<UInt8*> (outputTexture.buffer), bufferSize,
+ state.width, state.height, state.textureFormat, state.mipLevels, !reuseTextureMemory, min(state.mipLevels-1, Texture::GetMasterTextureLimit()),
+ GetUsageMode(), GetActiveTextureColorSpace());
+ Texture::s_TextureIDMap.insert (std::make_pair(GetTextureID(),this));
+ EnableFlag(Flag_Uploaded, true);
+ m_TextureParameters = state;
+
+ // Handle readable flag
+ if (GetSubstanceMaterial()!=NULL && GetSubstanceMaterial()->IsFlagEnabled(ProceduralMaterial::Flag_Readable)
+ && (state.textureFormat==kTexFormatRGBA32 || state.textureFormat==kTexFormatARGB32))
+ {
+ size_t size = state.width*state.height*4;
+ m_BakedData.resize(size);
+ memcpy(&m_BakedData[0], outputTexture.buffer, size);
+ m_BakedParameters = state;
+ }
+
+ if (IsFlagEnabled(Flag_AwakeClone))
+ AwakeClone();
+
+ m_TextureSettings.Apply( GetTextureID(), GetDimension(), outputTexture.mipmapCount != 1, GetActiveTextureColorSpace() );
+ m_UploadState = UploadState_Generated;
+#endif
+}
+
+void ProceduralTexture::SetOwner(ProceduralMaterial* material)
+{
+ Assert (material->IsFlagEnabled(ProceduralMaterial::Flag_Clone)
+ || m_PingedMaterial==NULL || m_PingedMaterial==material);
+ if (m_PingedMaterial==NULL)
+ m_PingedMaterial = material;
+}
+
+template<class TransferFunction>
+void ProceduralTexture::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ TRANSFER( m_SubstanceMaterial );
+ TRANSFER( m_SubstanceTextureUID );
+ transfer.Transfer(reinterpret_cast<int&> (m_Type), "Type");
+ transfer.Transfer(reinterpret_cast<int&> (m_AlphaSource), "AlphaSource");
+ transfer.Transfer(reinterpret_cast<int&> (m_Format), "Format");
+
+ if (m_Format<0 || m_Format>1)
+ m_Format = Substance_OFormat_Compressed;
+
+ TRANSFER( m_TextureSettings );
+
+ if (transfer.IsBuildingTargetPlatform(kBuildXBOX360))
+ {
+ const size_t size = m_BakedData.size();
+ std::vector<UInt8> swappedData;
+ swappedData.resize(size);
+ if (m_Format == Substance_OFormat_Compressed)
+ {
+ // Compressed Substance textures are DXT : 16b byte-swap needed
+ for (int i=0 ; i<size/2 ; ++i)
+ {
+ swappedData[2*i+0] = m_BakedData[2*i+1];
+ swappedData[2*i+1] = m_BakedData[2*i+0];
+ }
+ }
+ else
+ {
+ // RAW Substance textures = 4Bpp : 32b byte-swap needed
+ for (int i=0 ; i<size/4 ; ++i)
+ {
+ swappedData[4*i+0] = m_BakedData[4*i+3];
+ swappedData[4*i+1] = m_BakedData[4*i+2];
+ swappedData[4*i+2] = m_BakedData[4*i+1];
+ swappedData[4*i+3] = m_BakedData[4*i+0];
+ }
+ }
+ TRANSFER( swappedData );
+ }
+ else
+ {
+ TRANSFER( m_BakedData );
+ }
+
+ TRANSFER( m_BakedParameters );
+ transfer.Transfer( m_UsageMode, "m_LightmapFormat");
+ transfer.Transfer( m_ColorSpace, "m_ColorSpace");
+}
diff --git a/Runtime/Graphics/ProceduralTexture.h b/Runtime/Graphics/ProceduralTexture.h
new file mode 100644
index 0000000..94740a7
--- /dev/null
+++ b/Runtime/Graphics/ProceduralTexture.h
@@ -0,0 +1,190 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Texture.h"
+#include "External/Allegorithmic/builds/Engines/include/substance/handle.h"
+
+class ColorRGBAf;
+class ColorRGBA32;
+class ProceduralMaterial;
+
+enum ProceduralOutputType
+{
+ Substance_OType_Unknown = 0,
+ Substance_OType_Diffuse,
+ Substance_OType_Normal,
+ Substance_OType_Height,
+ Substance_OType_Emissive,
+ Substance_OType_Specular,
+ Substance_OType_Opacity,
+ ProceduralOutputType_Count
+};
+
+enum SubstanceOutputFormat
+{
+ Substance_OFormat_Compressed = 0,
+ Substance_OFormat_Raw
+};
+
+/* A ProceduralTexture is a dynamic texture generated by the Substance engine.
+ * It's part of a Substance graph hierarchy, compound of ProceduralMaterial, SubstanceArchive.
+ */
+
+
+class ProceduralTexture : public Texture
+{
+public:
+
+ // Uploaded texture parameters
+ struct TextureParameters
+ {
+ DECLARE_SERIALIZE(TextureParameters)
+
+ int width;
+ int height;
+ int mipLevels;
+ TextureFormat textureFormat;
+
+ bool IsValid () const { return width != 0; }
+ friend bool operator == (const TextureParameters& lhs, const TextureParameters& rhs)
+ {
+ return lhs.width == rhs.width && lhs.height == rhs.height && lhs.mipLevels == rhs.mipLevels && lhs.textureFormat == rhs.textureFormat;
+ }
+
+ TextureParameters ();
+ TextureParameters (int inWidth, int inHeight, int inMipLevels, TextureFormat inFormat);
+
+ void Invalidate() { *this = TextureParameters(); }
+ };
+
+ // Flags
+ enum Flag
+ {
+ Flag_Binded = 1<<0, // the output is altered by an input
+ Flag_Uploaded = 1<<1, // the data has been uploaded
+ Flag_AwakeClone = 1<<2, // the texture need awake from clone
+ Flag_Cached = 1<<3 // the texture has been cached, editor flag only
+ };
+
+ // Upload State
+ enum UploadState
+ {
+ UploadState_None, // no data has been uploaded
+ UploadState_Waiting, // blue waiting texture
+ UploadState_Valid, // lower states indicate the current uploaded data isn't valid
+ UploadState_Baked, // baked texture
+ UploadState_Generated // generated texture
+ };
+
+protected: // FIELDS
+
+ PPtr<ProceduralMaterial> m_SubstanceMaterial; // The parent procedural material from which we get generated
+ ProceduralMaterial* m_PingedMaterial; // The pinged procedural material
+ UInt64 m_SubstanceTextureUID; // The index of this texture (i.e. Substance output) WORD[shuffledID,baseID]
+ unsigned int m_SubstancePreShuffleUID; // This ID is required to use substanceLinkerHandleSelectOutputs
+ ProceduralOutputType m_Type;
+ ProceduralOutputType m_AlphaSource;
+ unsigned int m_Flags;
+ UploadState m_UploadState;
+ SubstanceOutputFormat m_Format;
+ TextureFormat m_SubstanceFormat; // Platform dependant format required for substance generation
+ TextureParameters m_TextureParameters;
+ std::vector<UInt8> m_BakedData; // Baked texture if substance isn't supported
+ TextureParameters m_BakedParameters;
+ TextureID m_ClonedID; // Link to the source ID when cloned and not generated
+
+public: // METHODS
+
+ REGISTER_DERIVED_CLASS( ProceduralTexture, Texture )
+ DECLARE_OBJECT_SERIALIZE( ProceduralTexture )
+
+ ProceduralTexture( MemLabelId label, ObjectCreationMode mode );
+
+#if ENABLE_SUBSTANCE
+ ProceduralTexture* Clone(ProceduralMaterial* owner);
+ void AwakeClone();
+#endif
+
+ virtual int GetDataWidth() const { return m_TextureParameters.width; }
+ virtual int GetDataHeight() const { return m_TextureParameters.height; }
+ TextureFormat GetDataFormat() const { return m_TextureParameters.textureFormat; }
+ int GetDataMipLevels() const { return m_TextureParameters.mipLevels; }
+ virtual TextureDimension GetDimension () const { return kTexDim2D; }
+ virtual bool HasMipMap () const { return m_TextureParameters.mipLevels != 1; }
+ virtual int CountMipmaps() const { return m_TextureParameters.mipLevels; }
+
+ const ProceduralOutputType& GetType() const { return m_Type; }
+ const ProceduralOutputType& GetAlphaSource() const { return m_AlphaSource; }
+ bool IsFlagEnabled(const Flag& flag) const { return m_Flags & (unsigned int)flag; }
+ void EnableFlag(const Flag& flag, bool enabled=true) { if (enabled) m_Flags |= (unsigned int)flag; else m_Flags &= ~(unsigned int)flag; }
+
+#if ENABLE_PROFILER
+ virtual int GetStorageMemorySize() const { return m_TextureParameters.width * m_TextureParameters.height; }
+#endif
+ virtual bool ExtractImage (ImageReference* image, int imageIndex = 0) const { return false; }
+
+ virtual void UnloadFromGfxDevice(bool forceUnloadAll);
+ virtual void UploadToGfxDevice();
+ void RemoveTexture ();
+ void UploadWaitingTexture ();
+ void UploadBakedTexture ();
+
+ // Gets the output texture UID (i.e. output UID for the Substance engine)
+ unsigned int GetSubstanceTextureUID() const { return m_SubstanceTextureUID & 0xffffffff; }
+ unsigned int GetSubstanceBaseTextureUID() const { return (m_SubstanceTextureUID >> 32); }
+
+ void SetSubstancePreShuffleUID(unsigned int id) { m_SubstancePreShuffleUID = id; }
+ unsigned int GetSubstancePreShuffleUID() const { return m_SubstancePreShuffleUID; }
+
+ // Set the UIDs
+ // (Don't call externally though !)
+ void SetSubstanceShuffledUID(unsigned int textureUID);
+ void SetSubstanceFormat(TextureFormat format) { m_SubstanceFormat = format; }
+ TextureFormat GetSubstanceFormat() const { return m_SubstanceFormat; }
+
+ virtual void AwakeFromLoadThreaded();
+ virtual void AwakeFromLoad( AwakeFromLoadMode awakeMode );
+
+ virtual bool ShouldIgnoreInGarbageDependencyTracking () { return false; }
+
+ // (Don't call externally though !)
+ void UploadSubstanceTexture(SubstanceTexture& outputTexture);
+
+ bool HasBeenGenerated() const { return m_UploadState==UploadState_Generated; }
+ bool HasBeenUploaded() const { return (m_UploadState==UploadState_Generated) || (m_UploadState==UploadState_Baked); }
+
+#if UNITY_EDITOR
+ // Creates the texture (one time call only by importer)
+ void Init( ProceduralMaterial& _Parent, int _TextureIndex, ProceduralOutputType type, SubstanceOutputFormat format, ProceduralOutputType alphaSource, bool requireCompressed );
+ const TextureParameters& GetTextureParameters() const { return m_TextureParameters; }
+ TextureParameters& GetTextureParameters() { return m_TextureParameters; }
+
+ virtual TextureFormat GetEditorUITextureFormat () const { return m_TextureParameters.textureFormat; }
+#endif
+
+ bool IsBaked() const;
+ std::vector<UInt8>& GetBakedData() { return m_BakedData; }
+ const TextureParameters& GetBakedParameters() const { return m_BakedParameters; }
+ TextureParameters& GetBakedParameters() { return m_BakedParameters; }
+
+ bool GetPixels32(int x, int y, int width, int height, ColorRGBA32* data);
+
+ bool IsValid() { return m_TextureParameters.IsValid(); }
+ void Invalidate();
+
+ void SetOwner(ProceduralMaterial* material);
+ ProceduralMaterial* GetSubstanceMaterial() { return m_PingedMaterial; }
+#if UNITY_EDITOR
+ PPtr<ProceduralMaterial>& GetSubstanceMaterialPtr() { return m_SubstanceMaterial; }
+#endif
+};
+
+template<class T>
+void ProceduralTexture::TextureParameters::Transfer(T& transfer)
+{
+ TRANSFER(width);
+ TRANSFER(height);
+ TRANSFER(mipLevels);
+ transfer.Transfer(reinterpret_cast<int&> (textureFormat), "textureFormat");
+}
diff --git a/Runtime/Graphics/RenderBufferManager.cpp b/Runtime/Graphics/RenderBufferManager.cpp
new file mode 100644
index 0000000..2b0f25a
--- /dev/null
+++ b/Runtime/Graphics/RenderBufferManager.cpp
@@ -0,0 +1,251 @@
+#include "UnityPrefix.h"
+#include "RenderBufferManager.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "RenderTexture.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Profiler/Profiler.h"
+#if UNITY_XENON
+ #include "PlatformDependent/Xbox360/Source/GfxDevice/TexturesXenon.h"
+#elif UNITY_WII
+ #include "Runtime/Graphics/ScreenManager.h"
+#endif
+
+#ifndef DEBUG_RB_MANAGER
+#define DEBUG_RB_MANAGER 0
+#endif
+
+using namespace std;
+
+static RenderBufferManager* gRenderBufferManager = NULL;
+
+void RenderBufferManager::InitRenderBufferManager ()
+{
+ Assert(gRenderBufferManager == NULL);
+ gRenderBufferManager = new RenderBufferManager();
+}
+
+void RenderBufferManager::CleanupRenderBufferManager ()
+{
+ Assert(gRenderBufferManager != NULL);
+ delete gRenderBufferManager;
+ gRenderBufferManager = NULL;
+}
+
+RenderBufferManager& GetRenderBufferManager ()
+{
+ Assert(gRenderBufferManager != NULL);
+ return *gRenderBufferManager;
+}
+
+RenderBufferManager* GetRenderBufferManagerPtr ()
+{
+ return gRenderBufferManager;
+}
+
+
+static int CalcSize( int size, int parentSize )
+{
+ switch (size) {
+ case RenderBufferManager::kFullSize:
+ return parentSize;
+ default:
+ #if DEBUGMODE
+ if( size <= 0 ) {
+ AssertString ("Invalid Temp Buffer size");
+ return 128;
+ }
+ #endif
+ return size;
+ }
+}
+
+
+RenderTexture *RenderBufferManager::GetTempBuffer (int width, int height, DepthBufferFormat depthFormat, RenderTextureFormat colorFormat, UInt32 flags, RenderTextureReadWrite colorSpace, int antiAliasing)
+{
+ if( colorFormat == kRTFormatDefault )
+ colorFormat = GetGfxDevice().GetDefaultRTFormat();
+
+ if( colorFormat == kRTFormatDefaultHDR )
+ colorFormat = GetGfxDevice().GetDefaultHDRRTFormat();
+
+ bool sRGB = colorSpace == kRTReadWriteSRGB;
+ const bool createdFromScript = flags & kRBCreatedFromScript;
+ const bool sampleOnlyDepth = flags & kRBSampleOnlyDepth;
+ const TextureDimension dim = (flags & kRBCubemap) ? kTexDimCUBE : kTexDim2D;
+
+ if (colorSpace == kRTReadWriteDefault)
+ sRGB = GetActiveColorSpace() == kLinearColorSpace;
+
+ // only SRGB where it makes sense
+ sRGB = sRGB && (colorFormat != GetGfxDevice().GetDefaultHDRRTFormat());
+
+ if( width <= 0 || height <= 0 )
+ {
+ if (dim != kTexDim2D) {
+ AssertString( "Trying to get a relatively sized RenderBuffer cubemap" );
+ return NULL;
+ }
+ Camera *cam = GetCurrentCameraPtr();
+ if (cam == NULL) {
+ AssertString ("Trying to get a relatively sized RenderBuffer without an active camera.");
+ return NULL;
+ }
+ Rectf r = cam->GetScreenViewportRect();
+#if UNITY_WII
+ GetScreenManager().ScaleViewportToFrameBuffer(r);
+#endif
+ // Figure out pixel size. Get screen extents as ints so we do the rounding correctly.
+ int viewport[4];
+ RectfToViewport(r, viewport);
+ width = viewport[2];
+ height = viewport[3];
+ }
+
+ if (dim == kTexDimCUBE && (!IsPowerOfTwo(width) || width != height))
+ {
+ AssertString( "Trying to get a non square or non power of two RenderBuffer cubemap" );
+ return NULL;
+ }
+
+ if (antiAliasing < 1 || antiAliasing > 8 || !IsPowerOfTwo(antiAliasing))
+ {
+ AssertString( "Trying to get RenderBuffer with invalid antiAliasing (must be 1, 2, 4 or 8)" );
+ return NULL;
+ }
+
+ // TODO: actually set & check depth
+
+ // Go over free textures & find the one that matches in parameters.
+ // The main point is: If we used a texture of same dims last frame we'll get that.
+ FreeTextures::iterator found = m_FreeTextures.end();
+ for( FreeTextures::iterator i = m_FreeTextures.begin(); i != m_FreeTextures.end(); ++i )
+ {
+ RenderTexture* rt = i->second;
+ if( !rt
+ || rt->GetDepthFormat() != depthFormat
+ || rt->GetColorFormat() != colorFormat
+ || rt->GetDimension() != dim
+ //@TODO: Only matters on OSX as DX can just set sampler state...
+ || rt->GetSRGBReadWrite() != sRGB
+ || rt->GetAntiAliasing() != antiAliasing
+ || rt->GetSampleOnlyDepth() != sampleOnlyDepth )
+ continue;
+ int tw = rt->GetWidth();
+ int th = rt->GetHeight();
+
+ if (tw == width && th == height) { // If the texture is same size
+ found = i;
+ break;
+ }
+ }
+
+ // We didn't find any.
+ if (found == m_FreeTextures.end() || !found->second)
+ {
+ m_TempBuffers++;
+ RenderTexture *tex = NEW_OBJECT (RenderTexture);
+ tex->Reset();
+
+ tex->SetHideFlags(Object::kDontSave);
+ tex->SetName (Format ("TempBuffer %d", m_TempBuffers).c_str());
+ tex->SetWidth(width);
+ tex->SetHeight(height);
+ tex->SetColorFormat( colorFormat );
+ tex->SetDepthFormat( depthFormat );
+ tex->SetDimension (dim);
+ tex->SetSRGBReadWrite (sRGB);
+ tex->SetAntiAliasing (antiAliasing);
+ tex->SetSampleOnlyDepth (sampleOnlyDepth);
+ tex->AwakeFromLoad(kInstantiateOrCreateFromCodeAwakeFromLoad);
+ m_TakenTextures.insert (tex);
+ #if DEBUG_RB_MANAGER
+ printf_console ("RBM: new texture %ix%i fmt=%i\n", width, height, colorFormat);
+ #endif
+ return tex;
+ }
+
+ // We found one. Move it from free to taken
+ RenderTexture *tex = found->second;
+ Assert (tex->GetWidth() == width && tex->GetHeight() == height);
+ m_TakenTextures.insert (tex);
+ m_FreeTextures.erase (found);
+
+ // Set it's parameters (filtering etc.) as if it was newly created
+ tex->GetSettings().Reset();
+ tex->GetSettings().m_WrapMode = kTexWrapClamp;
+ tex->ApplySettings();
+
+ tex->SetCreatedFromScript (createdFromScript);
+
+ // Automatically DiscardContents when createdFromScript - user can't expect any valid content.
+ if (createdFromScript)
+ tex->DiscardContents();
+
+ return tex;
+}
+
+PROFILER_INFORMATION(gRenderBufferCollect, "RenderTexture.GarbageCollectTemporary", kProfilerRender)
+
+void RenderBufferManager::GarbageCollect (int framesDelay)
+{
+ ++m_CurrentRBMFrame;
+
+ for (FreeTextures::iterator i = m_FreeTextures.begin(); i != m_FreeTextures.end();)
+ {
+ // Should only ever compare the difference since frame wraps around
+ int frameDiff = m_CurrentRBMFrame - i->first;
+ if( frameDiff > framesDelay || frameDiff < 0 )
+ {
+ PROFILER_AUTO(gRenderBufferCollect, NULL);
+ #if DEBUG_RB_MANAGER
+ printf_console ("RBM: kill unused texture (currframe=%i usedframe=%i)\n", m_CurrentRBMFrame, i->first);
+ #endif
+
+ FreeTextures::iterator j = i;
+ i++;
+ DestroySingleObject(j->second);
+ m_FreeTextures.erase (j);
+ }
+ else
+ {
+ i++;
+ }
+ }
+}
+
+
+void RenderBufferManager::Cleanup ()
+{
+ for (TakenTextures::iterator i=m_TakenTextures.begin();i != m_TakenTextures.end();i++)
+ {
+ DestroySingleObject(*i);
+ }
+ m_TakenTextures.clear();
+
+ for (FreeTextures::iterator i=m_FreeTextures.begin();i != m_FreeTextures.end();i++)
+ {
+ DestroySingleObject(i->second);
+ }
+ m_FreeTextures.clear();
+ #if DEBUG_RB_MANAGER
+ printf_console( "RBM: destroy all textures\n" );
+ #endif
+}
+
+void RenderBufferManager::ReleaseTempBuffer (RenderTexture *rTex)
+{
+ if (!rTex)
+ return;
+
+ if (!m_TakenTextures.count (PPtr<RenderTexture> (rTex)))
+ {
+ ErrorStringObject ("Attempting to release RenderTexture that were not gotten as a temp buffer", rTex);
+ return;
+ }
+
+ m_TakenTextures.erase (PPtr<RenderTexture> (rTex));
+ m_FreeTextures.push_back (make_pair (m_CurrentRBMFrame, PPtr<RenderTexture> (rTex)));
+}
diff --git a/Runtime/Graphics/RenderBufferManager.h b/Runtime/Graphics/RenderBufferManager.h
new file mode 100644
index 0000000..5496acf
--- /dev/null
+++ b/Runtime/Graphics/RenderBufferManager.h
@@ -0,0 +1,58 @@
+#ifndef RENDERBUFFERMANAGER_H
+#define RENDERBUFFERMANAGER_H
+
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/Utilities/MemoryPool.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+
+class RenderTexture;
+
+
+/* Manager for getting temporary render buffers.
+ * Use this instead of creating RenderTextures if you need a quick buffer.
+ * This is a low-overhead class that recycles render textures.
+ */
+class RenderBufferManager
+{
+ RenderBufferManager () { m_CurrentRBMFrame = m_TempBuffers = 0; }
+
+public:
+
+ enum { kFullSize = -1 };
+ enum {
+ kRBCubemap = (1<<0),
+ kRBCreatedFromScript = (1<<1),
+ kRBSampleOnlyDepth = (1<<2),
+ };
+ enum { kKillFrames = 15 };
+
+ static void InitRenderBufferManager ();
+ static void CleanupRenderBufferManager ();
+
+ // Get a RenderTexture with the specific sizes
+ // If the width & height parameters uses autosizing, viewport size is taken from the current camera.
+ RenderTexture *GetTempBuffer (int width, int height, DepthBufferFormat depthFormat, RenderTextureFormat colorFormat, UInt32 flags, RenderTextureReadWrite colorSpace, int antiAliasing = 1);
+
+ // Release the temporary buffer.
+ void ReleaseTempBuffer (RenderTexture *rTex);
+
+ void GarbageCollect (int framesDelay = kKillFrames);
+
+ void Cleanup ();
+
+private:
+
+ typedef std::set<PPtr<RenderTexture>, std::less< PPtr<RenderTexture> > , memory_pool<PPtr<RenderTexture> > > TakenTextures;
+ typedef std::pair <int, PPtr<RenderTexture> > IntPPtrPair;
+ typedef std::list<IntPPtrPair, memory_pool<IntPPtrPair > > FreeTextures;
+
+ FreeTextures m_FreeTextures;
+ TakenTextures m_TakenTextures;
+ int m_TempBuffers;
+ int m_CurrentRBMFrame;
+};
+
+RenderBufferManager& GetRenderBufferManager ();
+RenderBufferManager* GetRenderBufferManagerPtr ();
+
+#endif
diff --git a/Runtime/Graphics/RenderSurface.h b/Runtime/Graphics/RenderSurface.h
new file mode 100644
index 0000000..c856a39
--- /dev/null
+++ b/Runtime/Graphics/RenderSurface.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+
+struct
+RenderSurfaceBase
+{
+ TextureID textureID;
+ int width;
+ int height;
+ int samples;
+ UInt32 flags;
+ bool colorSurface;
+ bool backBuffer;
+ bool shouldDiscard;
+ bool shouldClear;
+};
+
+// we dont want to enforce ctor, so lets do it as simple function
+inline void RenderSurfaceBase_Init(RenderSurfaceBase& rs)
+{
+ rs.textureID.m_ID = 0;
+ rs.width = rs.height = 0;
+ rs.samples = 1;
+ rs.flags = 0;
+ rs.shouldDiscard = rs.shouldClear = false;
+ rs.backBuffer = false;
+}
+
+inline void RenderSurfaceBase_InitColor(RenderSurfaceBase& rs)
+{
+ RenderSurfaceBase_Init(rs);
+ rs.colorSurface = true;
+}
+
+inline void RenderSurfaceBase_InitDepth(RenderSurfaceBase& rs)
+{
+ RenderSurfaceBase_Init(rs);
+ rs.colorSurface = false;
+}
diff --git a/Runtime/Graphics/RenderTexture.cpp b/Runtime/Graphics/RenderTexture.cpp
new file mode 100644
index 0000000..b47cf0f
--- /dev/null
+++ b/Runtime/Graphics/RenderTexture.cpp
@@ -0,0 +1,889 @@
+#include "UnityPrefix.h"
+#include "RenderTexture.h"
+#if UNITY_XENON
+#include "PlatformDependent/Xbox360/Source/GfxDevice/TexturesXenon.h"
+#endif
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "External/shaderlab/Library/properties.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Graphics/RenderSurface.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/Camera/CameraUtil.h"
+#include "Runtime/Utilities/MemoryPool.h"
+#include "Runtime/Misc/PlayerSettings.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Profiler/Profiler.h"
+
+PROFILER_INFORMATION(gGrabPixels, "RenderTexture.GrabPixels", kProfilerRender);
+PROFILER_INFORMATION(gSetRenderTargets, "RenderTexture.SetActive", kProfilerRender);
+
+static const int kRenderTextureFormatBPP[kRTFormatCount] = {
+ 4, // ARGB32
+ 4, // Depth various amounts on various HW, but we'll assume 4
+ 8, // ARGBHalf
+ 4, // Shadowmap various amounts on various HW, but we'll assume 4
+ 2, // RGB565
+ 2, // ARGB4444
+ 2, // ARGB1555
+ 4, // Default
+ 4, // A2R10G10B10
+ 8, // DefaultHDR
+ 8, // ARGB64
+ 16,// ARGBFloat
+ 8, // RGFloat
+ 4, // RGHalf
+ 4, // RFloat
+ 2, // RHalf
+ 1, // R8
+ 16,// ARGBInt
+ 8, // RGInt
+ 4, // RInt
+ 4, // BGRA32
+};
+
+static int kDepthFormatBPP[kDepthFormatCount] = {
+ 0, // None
+ 2, // 16
+ 4, // 24
+};
+
+
+typedef List< ListNode<RenderTexture> > RenderTextureList;
+RenderTextureList gRenderTextures;
+
+static bool gIsRenderTexEnabled = true;
+int gTemporarilyAllowIndieRenderTextures = 0;
+
+void RenderTexture::SetTemporarilyAllowIndieRenderTexture (bool allow)
+{
+ if (allow)
+ gTemporarilyAllowIndieRenderTextures++;
+ else
+ gTemporarilyAllowIndieRenderTextures--;
+}
+
+void RenderTexture::FindAndSetSRGBWrite( RenderTexture* newActive )
+{
+ bool wantLinear = GetActiveColorSpace() == kLinearColorSpace;
+ GetGfxDevice().SetSRGBWrite(newActive ? newActive->GetSRGBReadWrite() && wantLinear : wantLinear);
+}
+
+void RenderTexture::SetActive (RenderTexture* newActive, int mipLevel, CubemapFace face, UInt32 flags)
+{
+ newActive = EnsureRenderTextureIsCreated(newActive);
+
+ RenderSurfaceHandle newColorSurface = newActive ? newActive->m_ColorHandle : GetGfxDevice().GetBackBufferColorSurface();
+ RenderSurfaceHandle newDepthSurface = newActive ? newActive->m_DepthHandle : GetGfxDevice().GetBackBufferDepthSurface();
+ int mips = newActive && newActive->HasMipMap() ? mipLevel : 0;
+
+ SetActive (1, &newColorSurface, newDepthSurface, newActive, mips, face, flags);
+}
+
+bool RenderTexture::SetActive (int count, RenderSurfaceHandle* newColorSurfaces, RenderSurfaceHandle newDepthSurface, RenderTexture* rt, int mipLevel, CubemapFace face, UInt32 flags)
+{
+ DebugAssert(count > 0);
+ if( !IsEnabled() )
+ {
+ for (int i = 0; i < count; ++i)
+ newColorSurfaces[i].Reset();
+
+ // ??? should we keep them invalid just in case?
+ newColorSurfaces[0] = GetGfxDevice().GetBackBufferColorSurface();
+ newDepthSurface = GetGfxDevice().GetBackBufferDepthSurface();
+ }
+
+ int width = 1, height = 1;
+ if(newColorSurfaces[0].IsValid() && !newColorSurfaces[0].object->backBuffer)
+ {
+ width = newColorSurfaces[0].object->width;
+ height= newColorSurfaces[0].object->height;
+ }
+
+ // Clamp mip level to valid range
+ const int mipCount = CalculateMipMapCount3D(width, height, 1);
+ mipLevel = clamp(mipLevel, 0, mipCount-1);
+
+ GfxDevice& device = GetGfxDevice();
+ RenderSurfaceHandle oldColorSurface = device.GetActiveRenderColorSurface(0);
+ RenderSurfaceHandle oldDepthSurface = device.GetActiveRenderDepthSurface();
+
+ Assert(newColorSurfaces[0].IsValid() == newDepthSurface.IsValid());
+ bool renderTarget = newColorSurfaces[0].IsValid() && !newColorSurfaces[0].object->backBuffer;
+
+ if( oldColorSurface != newColorSurfaces[0] || (flags & kFlagForceResolve) )
+ {
+ // MSAA surface has to be resolved
+ RenderTexture* oldRt = GetActive();
+ if( oldRt && oldRt->IsAntiAliased() )
+ oldRt->ResolveAntiAliasedSurface();
+ }
+
+ UInt32 rtFlags = 0;
+ rtFlags |= (flags & kFlagDontRestoreColor) ? GfxDevice::kFlagDontRestoreColor : 0;
+ rtFlags |= (flags & kFlagDontRestoreDepth) ? GfxDevice::kFlagDontRestoreDepth : 0;
+ rtFlags |= (flags & kFlagForceResolve) ? GfxDevice::kFlagForceResolve : 0;
+
+ PROFILER_AUTO(gSetRenderTargets, rt);
+ device.SetRenderTargets (count, newColorSurfaces, newDepthSurface, mipLevel, face, rtFlags);
+ GPU_TIMESTAMP();
+
+ // we can call RenderTexture::SetActive before device init (d3d11)
+ if( oldColorSurface == newColorSurfaces[0] && oldDepthSurface == newDepthSurface
+ && newColorSurfaces[0].IsValid() && !newColorSurfaces[0].object->backBuffer
+ )
+ {
+ return false;
+ }
+
+
+ // NOTE: important to set ActiveRenderTexture to null/non-null value before setting up viewport (as that might involve
+ // flipping vertically based on whether there is a RT or not).
+ GetGfxDevice().SetActiveRenderTexture(rt);
+
+ // Setup the viewport for the render texture
+ if( !(flags & kFlagDontSetViewport) )
+ {
+ if (renderTarget)
+ {
+ width >>= mipLevel;
+ height >>= mipLevel;
+ device.SetViewport (0, 0, width, height);
+ }
+ else
+ {
+ // When switching to main window, restore the viewport to the one of the current
+ // camera.
+ const int* cameraViewPort = GetRenderManager().GetCurrentViewPort();
+ int viewcoord[4];
+ viewcoord[0] = cameraViewPort[0];
+ viewcoord[1] = cameraViewPort[1];
+ viewcoord[2] = cameraViewPort[2];
+ viewcoord[3] = cameraViewPort[3];
+
+ // Flip viewport just before applying to device, but don't store it flipped in the render manager
+ FlipScreenRectIfNeeded( device, viewcoord );
+ device.SetViewport( viewcoord[0], viewcoord[1], viewcoord[2], viewcoord[3] );
+ }
+ }
+
+ if (renderTarget)
+ {
+ // If texture coordinates go from top to bottom, then we need to
+ // invert projection matrix when rendering into a texture.
+ #if UNITY_WII
+ device.SetInvertProjectionMatrix( true );
+ #else
+ device.SetInvertProjectionMatrix( !device.UsesOpenGLTextureCoords() );
+ #endif
+ }
+ else
+ {
+ device.SetInvertProjectionMatrix( false );
+ }
+
+ return true;
+}
+
+
+bool RenderTexture::GetSupportedMipMapFlag(bool mipMap) const
+{
+ if (!gGraphicsCaps.hasAutoMipMapGeneration)
+ mipMap = false;
+ if (m_Dimension == kTexDimCUBE && gGraphicsCaps.buggyMipmappedCubemaps)
+ mipMap = false;
+ if (m_Dimension == kTexDim3D && gGraphicsCaps.buggyMipmapped3DTextures)
+ mipMap = false;
+ return mipMap;
+}
+
+
+bool RenderTexture::Create ()
+{
+ if( !IsEnabled() )
+ return false;
+
+ DestroySurfaces();
+ GfxDevice& device = GetGfxDevice();
+
+ if (m_Width <= 0 || m_Height <= 0)
+ {
+ ErrorString("RenderTexture.Create failed: width & height must be larger than 0");
+ return false;
+ }
+
+ if (m_Dimension == kTexDimCUBE && (!GetIsPowerOfTwo() || m_Width != m_Height))
+ {
+ ErrorString("RenderTexture.Create failed: cube maps must be power of two and width must match height");
+ return false;
+ }
+
+ if( !device.IsRenderTargetConfigValid(m_Width, m_Height, static_cast<RenderTextureFormat>(m_ColorFormat), static_cast<DepthBufferFormat>(m_DepthFormat)) )
+ {
+ if( GetIsPowerOfTwo() )
+ {
+ if (gGraphicsCaps.maxRenderTextureSize < 4)
+ {
+ ErrorString("RenderTexture.Create failed: maxRenderTextureSize is too small");
+ return false;
+ }
+
+ // Decrease too large render textures while maintaining aspect ratio
+ do
+ {
+ m_Width = std::max( m_Width / 2, 4 );
+ m_Height = std::max( m_Height / 2, 4 );
+ }
+ while ( !device.IsRenderTargetConfigValid(m_Width, m_Height, static_cast<RenderTextureFormat>(m_ColorFormat), static_cast<DepthBufferFormat>(m_DepthFormat)) );
+ }
+ else
+ {
+ ErrorString("RenderTexture.Create failed: requested size is too large.");
+ return false;
+ }
+ }
+
+ if( !gGraphicsCaps.supportsRenderTextureFormat[m_ColorFormat] )
+ {
+ ErrorString("RenderTexture.Create failed: format unsupported.");
+ return false;
+ }
+
+ if (!GetIsPowerOfTwo() && (gGraphicsCaps.npotRT == kNPOTNone))
+ {
+ ErrorString("RenderTexture.Create failed: non-power-of-two sizes not supported.");
+ return false;
+ }
+
+ if (m_Dimension == kTexDimCUBE && (!gGraphicsCaps.hasRenderToCubemap || IsDepthRTFormat((RenderTextureFormat)m_ColorFormat)))
+ {
+ ErrorString("RenderTexture.Create failed: cubemap not supported.");
+ return false;
+ }
+
+ if (m_Dimension == kTexDim3D && (!gGraphicsCaps.has3DTexture || !gGraphicsCaps.hasRenderTo3D))
+ {
+ ErrorString("RenderTexture.Create failed: volume texture not supported.");
+ return false;
+ }
+
+
+ bool mipMap = GetSupportedMipMapFlag(m_MipMap);
+ if (!GetIsPowerOfTwo())
+ mipMap = false;
+
+ int samples = m_AntiAliasing;
+ samples = clamp<int>(samples, 1, 8);
+ if (!gGraphicsCaps.hasMultiSample)
+ samples = 1;
+ if (m_Dimension != kTexDim2D)
+ samples = 1;
+
+ if (samples > 1)
+ mipMap = false;
+
+ // If we have native depth textures, we should use our regular textureID for the depth
+ // surface.
+ TextureID colorTID, resolvedColorTID, depthTID;
+ if ((m_ColorFormat == kRTFormatDepth && gGraphicsCaps.hasNativeDepthTexture) ||
+ (m_ColorFormat == kRTFormatShadowMap && gGraphicsCaps.hasNativeShadowMap))
+ {
+ depthTID = m_TexID;
+ m_SecondaryTexIDUsed = false;
+ }
+ else
+ {
+ // Use resolved surface as texture if MSAA enabled
+ if (samples > 1 && !gGraphicsCaps.hasMultiSampleAutoResolve)
+ resolvedColorTID = m_TexID;
+ else
+ colorTID = m_TexID;
+ // For regular non-MSAA render textures, also provide capability to treat depth buffer as a texture.
+ // Only do this if HW supports native depth textures AND texture is 2D AND
+ // we actually have a depth buffer AND driver is not broken for this case.
+ bool useDepthAsTexture = false;
+ if (m_Dimension == kTexDim2D && m_DepthFormat != kDepthFormatNone && samples <= 1)
+ useDepthAsTexture = gGraphicsCaps.hasStencilInDepthTexture && !gGraphicsCaps.buggyTextureBothColorAndDepth;
+ if (useDepthAsTexture)
+ {
+ depthTID = m_SecondaryTexID;
+ m_SecondaryTexIDUsed = true;
+ }
+ else
+ m_SecondaryTexIDUsed = false;
+ }
+
+ UInt32 colorFlags = 0;
+ if (mipMap) colorFlags |= kSurfaceCreateMipmap;
+ if (m_GenerateMips) colorFlags |= kSurfaceCreateAutoGenMips;
+ if (m_SRGB) colorFlags |= kSurfaceCreateSRGB;
+ if (m_EnableRandomWrite) colorFlags |= kSurfaceCreateRandomWrite;
+ if (colorTID == TextureID() && samples <= 1) colorFlags |= kSurfaceCreateNeverUsed; // never sampled or resolved
+ m_ColorHandle = device.CreateRenderColorSurface (colorTID,
+ m_Width, m_Height, samples, m_VolumeDepth, m_Dimension, static_cast<RenderTextureFormat>(m_ColorFormat), colorFlags);
+
+ if (samples > 1 && !gGraphicsCaps.hasMultiSampleAutoResolve)
+ {
+ m_ResolvedColorHandle = device.CreateRenderColorSurface (resolvedColorTID,
+ m_Width, m_Height, 1, m_VolumeDepth, m_Dimension, static_cast<RenderTextureFormat>(m_ColorFormat), colorFlags);
+ }
+
+ UInt32 depthFlags = 0;
+ if (m_ColorFormat==kRTFormatShadowMap) depthFlags |= kSurfaceCreateShadowmap;
+ if (m_SampleOnlyDepth) depthFlags |= kSurfaceCreateSampleOnly;
+ m_DepthHandle = device.CreateRenderDepthSurface (depthTID,
+ m_Width, m_Height, samples, m_Dimension, static_cast<DepthBufferFormat>(m_DepthFormat), depthFlags);
+
+ if (!(m_ColorHandle.IsValid() || m_DepthHandle.IsValid()))
+ {
+ ErrorString("RenderTexture.Create failed");
+ return false;
+ }
+
+ if (IsCreated())
+ {
+ Assert(m_RegisteredSizeForStats == 0);
+ m_RegisteredSizeForStats = GetRuntimeMemorySize();
+ device.GetFrameStats().ChangeRenderTextureBytes (m_RegisteredSizeForStats);
+ }
+
+ if (m_CreatedFromScript)
+ {
+ // We can't currently read the minds of users, so let's always restore their render targets.
+ // TODO: extend RenderTexture API
+ device.SetSurfaceFlags(m_ColorHandle, GfxDevice::kSurfaceAlwaysRestore, ~GfxDevice::kSurfaceRestoreMask);
+ device.SetSurfaceFlags(m_DepthHandle, GfxDevice::kSurfaceAlwaysRestore, ~GfxDevice::kSurfaceRestoreMask);
+ }
+
+ SetStoredColorSpaceNoDirtyNoApply(m_SRGB ? kTexColorSpaceSRGB : kTexColorSpaceLinear);
+
+ // Set filtering, etc. mode
+ ApplySettings();
+
+ UpdateTexelSize();
+
+ return true;
+}
+
+void RenderTexture::UpdateTexelSize()
+{
+ SetUVScale( 1.0f, 1.0f );
+ if (m_Width != 0 && m_Height != 0)
+ {
+ int width = m_Width;
+ int height = m_Height;
+ #if GFX_EMULATES_NPOT_RENDERTEXTURES
+ width = NextPowerOfTwo (width);
+ height = NextPowerOfTwo (height);
+ #endif
+ SetTexelSize( 1.0f / width, 1.0f / height );
+ }
+}
+
+
+void RenderTexture::ApplySettings()
+{
+ TextureDimension dim = GetDimension();
+ bool mip = HasMipMap();
+ // Don't ever use Aniso on depth textures or textures where we
+ // sample depth buffer.
+ bool depth = IsDepthRTFormat((RenderTextureFormat)m_ColorFormat);
+ if (depth || m_SecondaryTexIDUsed)
+ m_TextureSettings.m_Aniso = 0;
+
+ m_TextureSettings.Apply (GetTextureID(), dim, mip, GetActiveTextureColorSpace());
+ if (m_SecondaryTexIDUsed)
+ m_TextureSettings.Apply (m_SecondaryTexID, dim, mip, GetActiveTextureColorSpace());
+ NotifyMipBiasChanged();
+}
+
+
+void RenderTexture::Release()
+{
+ if (GetGfxDevice().GetActiveRenderTexture() == this)
+ {
+ ErrorString("Releasing render texture that is set to be RenderTexture.active!");
+ GetGfxDevice().SetActiveRenderTexture(NULL);
+ }
+ DestroySurfaces();
+}
+
+void RenderTexture::DestroySurfaces()
+{
+ if(!IsCreated())
+ return;
+
+ GfxDevice& device = GetGfxDevice();
+ // Different graphics caps can change the calculated runtime size (case 555046)
+ // so we store the size we added and make sure to subtract the same amount.
+ // Graphics caps can change during an editor session due to emulation.
+ device.GetFrameStats().ChangeRenderTextureBytes (-m_RegisteredSizeForStats);
+ m_RegisteredSizeForStats = 0;
+ if( m_ColorHandle.IsValid() )
+ device.DestroyRenderSurface (m_ColorHandle);
+ if( m_ResolvedColorHandle.IsValid() )
+ device.DestroyRenderSurface (m_ResolvedColorHandle);
+ if( m_DepthHandle.IsValid() )
+ device.DestroyRenderSurface (m_DepthHandle);
+}
+
+void RenderTexture::DiscardContents()
+{
+ if(IsCreated())
+ RenderTextureDiscardContents(this, true, true);
+
+}
+void RenderTexture::DiscardContents(bool discardColor, bool discardDepth)
+{
+ if(IsCreated())
+ RenderTextureDiscardContents(this, discardColor, discardDepth);
+}
+
+
+void RenderTexture::MarkRestoreExpected()
+{
+ GfxDevice& device = GetGfxDevice();
+ device.IgnoreNextUnresolveOnRS (m_ColorHandle);
+ device.IgnoreNextUnresolveOnRS (m_DepthHandle);
+ device.IgnoreNextUnresolveOnRS (m_ResolvedColorHandle);
+}
+
+
+void RenderTexture::GrabPixels (int left, int bottom, int width, int height)
+{
+ if (!IsCreated())
+ Create();
+
+ const RenderSurfaceHandle& handle = IsAntiAliased() ? m_ResolvedColorHandle : m_ColorHandle;
+
+ if (!handle.IsValid())
+ return;
+
+ if (left < 0) {
+ width += left;
+ left = 0;
+ }
+ if (bottom < 0) {
+ height += bottom;
+ bottom = 0;
+ }
+ if (width > m_Width)
+ width = m_Width;
+ if (height > m_Height)
+ height = m_Height;
+
+ PROFILER_AUTO(gGrabPixels, NULL);
+ GfxDevice& device = GetGfxDevice();
+ device.GrabIntoRenderTexture (handle, m_DepthHandle, left, bottom, width, height);
+ GPU_TIMESTAMP();
+ device.GetFrameStats().AddRenderTextureChange(); // treat resolve as RT change
+}
+void RenderTexture::CorrectVerticalTexelSize(bool shouldBePositive)
+{
+ if (!GetGfxDevice().UsesOpenGLTextureCoords())
+ {
+ if (m_TexelSizeY < 0.0f && shouldBePositive) m_TexelSizeY = -m_TexelSizeY;
+ else if (m_TexelSizeY > 0.0f && !shouldBePositive) m_TexelSizeY = -m_TexelSizeY;
+ }
+}
+RenderTexture::RenderTexture (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_DepthFormat(kDepthFormat24)
+, m_ColorFormat(kRTFormatARGB32)
+, m_Dimension(kTexDim2D)
+, m_MipMap(false)
+, m_GenerateMips(true)
+, m_SRGB(false)
+, m_EnableRandomWrite(false)
+, m_CreatedFromScript(false)
+, m_SampleOnlyDepth(false)
+, m_RegisteredSizeForStats(0)
+, m_RenderTexturesNode(this)
+{
+ m_Width = 256;
+ m_Height = 256;
+ m_VolumeDepth = 1;
+ m_AntiAliasing = 1;
+
+ GetSettings().m_WrapMode = kTexWrapClamp;
+
+ // We use unchecked version since we may not be on the main thread
+ // This means CreateTextureID() implementation must be thread safe!
+ m_SecondaryTexID = GetUncheckedGfxDevice().CreateTextureID();
+ m_SecondaryTexIDUsed = false;
+}
+
+RenderTexture::~RenderTexture ()
+{
+ Release();
+ m_RenderTexturesNode.RemoveFromList();
+ Texture::s_TextureIDMap.erase (m_SecondaryTexID);
+ // FreeTextureID() implementation must be thread safe!
+ GetUncheckedGfxDevice().FreeTextureID(m_SecondaryTexID);
+}
+
+void RenderTexture::SetDimension (TextureDimension dim)
+{
+ if (m_Dimension == dim)
+ return;
+
+ if (!IsCreated ())
+ m_Dimension = dim;
+ else
+ ErrorString ("Setting dimension of already created render texture is not supported!");
+}
+
+void RenderTexture::SetVolumeDepth (int v)
+{
+ if (m_VolumeDepth == v)
+ return;
+
+ if (!IsCreated ()) {
+ m_VolumeDepth = v;
+ } else
+ ErrorString ("Setting volume depth of already created render texture is not supported!");
+}
+
+void RenderTexture::SetAntiAliasing (int aa)
+{
+ if (m_AntiAliasing == aa)
+ return;
+
+ if (aa < 1 || aa > 8 || !IsPowerOfTwo(aa))
+ {
+ ErrorString( "Invalid antiAliasing value (must be 1, 2, 4 or 8)" );
+ return;
+ }
+
+ if (!IsCreated ()) {
+ m_AntiAliasing = aa;
+ } else
+ ErrorString ("Setting anti-aliasing of already created render texture is not supported!");
+}
+
+
+void RenderTexture::SetMipMap( bool mipMap )
+{
+ mipMap = GetSupportedMipMapFlag(mipMap);
+
+ if (mipMap == m_MipMap)
+ return;
+
+ if (!IsCreated ()) {
+ m_MipMap = mipMap;
+ } else {
+ ErrorString ("Setting mipmap mode of render texture that is loaded not supported!");
+ }
+}
+
+void RenderTexture::SetGenerateMips (bool v)
+{
+ if (v == m_GenerateMips)
+ return;
+
+ if (!IsCreated ())
+ {
+ if (m_MipMap && m_DepthFormat != kDepthFormatNone && !v)
+ {
+ WarningStringObject ("Mipmapped RenderTextures with manual mip generation can't have depth buffer", this);
+ v = true;
+ }
+ m_GenerateMips = v;
+ }
+ else
+ ErrorString ("Can't change mipmap generation of already created RenderTexture");
+}
+
+void RenderTexture::SetSRGBReadWrite ( bool sRGB )
+{
+ if (!IsCreated ()) {
+ bool setSRGB = sRGB ? (GetActiveColorSpace() == kLinearColorSpace) : false;
+ setSRGB = setSRGB && (m_ColorFormat != GetGfxDevice().GetDefaultHDRRTFormat());
+ m_SRGB = setSRGB;
+ } else
+ ErrorString ("Changing srgb mode of render texture that is loaded not supported!");
+}
+
+void RenderTexture::SetEnableRandomWrite (bool v)
+{
+ if (v == m_EnableRandomWrite)
+ return;
+
+ if (!IsCreated ()) {
+ m_EnableRandomWrite = v;
+ } else
+ ErrorString ("Can't change random write mode of already created render texture!");
+}
+
+void RenderTexture::AwakeFromLoad(AwakeFromLoadMode awakeMode)
+{
+ m_Width = std::max (1, m_Width);
+ m_Height = std::max (1, m_Height);
+ m_VolumeDepth = std::max (1, m_VolumeDepth);
+ m_AntiAliasing = clamp<int> (m_AntiAliasing, 1, 8);
+
+ if( IsDepthRTFormat( (RenderTextureFormat)m_ColorFormat ) )
+ m_MipMap = false;
+ if (m_Dimension==kTexDimCUBE)
+ m_Height = m_Width;
+
+ if( !GetIsPowerOfTwo() && GetSettings().m_WrapMode == kTexWrapRepeat )
+ GetSettings().m_WrapMode = kTexWrapClamp;
+
+ if( IsDepthRTFormat((RenderTextureFormat)m_ColorFormat) ) // always clamp depth textures
+ GetSettings().m_WrapMode = kTexWrapClamp;
+
+#if GFX_OPENGLESxx_ONLY
+ // currently i have no idea about possibility of linear filter for fp rts (seems like gl do support it)
+ if( IsHalfRTFormat((RenderTextureFormat)m_ColorFormat) && !gGraphicsCaps.gles20.hasHalfLinearFilter )
+ GetSettings().m_FilterMode = kTexFilterNearest;
+#endif
+
+ gRenderTextures.push_back(m_RenderTexturesNode);
+
+ UpdateTexelSize();
+
+ Super::AwakeFromLoad (awakeMode);
+}
+
+void RenderTexture::SetWidth (int width)
+{
+ if (!IsCreated ()) {
+ m_Width = width;
+ UpdateTexelSize();
+ SetDirty();
+ } else
+ ErrorString ("Resizing of render texture that is loaded not supported!");
+}
+
+void RenderTexture::SetHeight (int height)
+{
+ if (!IsCreated ()) {
+ m_Height = height;
+ UpdateTexelSize();
+ SetDirty();
+ } else
+ ErrorString ("Resizing of render texture that is loaded not supported!");
+}
+
+void RenderTexture::SetDepthFormat( DepthBufferFormat depth )
+{
+ if (!IsCreated ())
+ m_DepthFormat = depth;
+ else
+ ErrorString ("Setting depth of render texture that is loaded not supported!");
+}
+
+void RenderTexture::SetColorFormat( RenderTextureFormat format )
+{
+ if (!IsCreated ())
+ {
+ if( format == kRTFormatDefault )
+ format = GetGfxDevice().GetDefaultRTFormat();
+
+ m_ColorFormat = format;
+ if (IsDepthRTFormat(format)) m_TextureSettings.m_Aniso = 0; // never use Anisotropic on depth textures
+ } else
+ ErrorString ("Changing format of render texture that is loaded not supported!");
+}
+
+
+RenderTexture* RenderTexture::GetActive ()
+{
+ return GetGfxDevice().GetActiveRenderTexture();
+}
+
+ShaderLab::TexEnv *RenderTexture::SetGlobalProperty (const ShaderLab::FastPropertyName& name)
+{
+ ShaderLab::TexEnv *te = ShaderLab::g_GlobalProperties->SetTexture (name, this);
+ te->ClearMatrix();
+ return te;
+}
+
+bool RenderTexture::IsEnabled ()
+{
+ if (gGraphicsCaps.hasRenderToTexture)
+ return gIsRenderTexEnabled && (GetBuildSettings().hasRenderTexture || gTemporarilyAllowIndieRenderTextures);
+ else
+ return false;
+}
+
+void RenderTexture::SetEnabled (bool enable)
+{
+ if (!enable)
+ ReleaseAll();
+
+ gIsRenderTexEnabled = enable;
+}
+
+void RenderTexture::ReleaseAll ()
+{
+ SetActive (NULL);
+
+ for (RenderTextureList::iterator i=gRenderTextures.begin ();i != gRenderTextures.end ();i++)
+ (**i).Release ();
+}
+
+
+#if ENABLE_PROFILER || UNITY_EDITOR
+
+int RenderTexture::GetCreatedRenderTextureCount ()
+{
+ int count = 0;
+ for (RenderTextureList::iterator i=gRenderTextures.begin ();i != gRenderTextures.end ();i++)
+ {
+ if ((**i).IsCreated ())
+ count++;
+ }
+
+ return count;
+}
+
+int RenderTexture::GetCreatedRenderTextureBytes ()
+{
+ int count = 0;
+ for (RenderTextureList::iterator i=gRenderTextures.begin ();i != gRenderTextures.end ();i++)
+ {
+ if ((**i).IsCreated ())
+ count += (**i).GetRuntimeMemorySize();
+ }
+
+ return count;
+}
+
+#endif
+
+
+
+int EstimateRenderTextureSize (int width, int height, int depth, RenderTextureFormat format, DepthBufferFormat depthFormat, TextureDimension dim, bool mipMap)
+{
+ // color buffer
+ int colorBPP;
+ if (format == kRTFormatDepth && gGraphicsCaps.hasNativeDepthTexture)
+ colorBPP = 0;
+ else if (format == kRTFormatShadowMap && gGraphicsCaps.hasNativeShadowMap)
+ colorBPP = 0;
+ else
+ colorBPP = kRenderTextureFormatBPP[format];
+ int size = width * height * colorBPP;
+
+ if (dim == kTexDim3D)
+ size *= depth;
+ else if (dim == kTexDimCUBE)
+ size *= 6;
+ if (mipMap && gGraphicsCaps.hasAutoMipMapGeneration)
+ size += size / 3;
+
+ // depth buffer
+ size += width * height * kDepthFormatBPP[depthFormat];
+ return size;
+}
+
+
+
+int RenderTexture::GetRuntimeMemorySize() const
+{
+ int bytes = EstimateRenderTextureSize (m_Width, m_Height, m_VolumeDepth, (RenderTextureFormat)m_ColorFormat, (DepthBufferFormat)m_DepthFormat, m_Dimension, m_MipMap);
+ bytes *= m_AntiAliasing;
+ return bytes;// + Super::GetRuntimeMemorySize(); Since the name is assigned after this value is used for gfxstats accumulate, the string will add to the mem usage later - Causes Assert
+}
+
+void RenderTexture::ResolveAntiAliasedSurface()
+{
+ if (!m_ResolvedColorHandle.IsValid())
+ return;
+
+ GfxDevice& device = GetGfxDevice();
+ device.ResolveColorSurface(m_ColorHandle, m_ResolvedColorHandle);
+}
+
+RenderTexture* EnsureRenderTextureIsCreated(RenderTexture* tex)
+{
+ RenderTexture* ret = tex;
+
+ if(!RenderTexture::IsEnabled())
+ ret = 0;
+
+ if(ret && !ret->IsCreated())
+ ret->Create();
+
+ // check if we could create
+ if(ret && !ret->IsCreated())
+ ret = 0;
+
+ return ret;
+}
+
+template<class TransferFunction>
+void RenderTexture::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ TRANSFER (m_Width);
+ TRANSFER (m_Height);
+ TRANSFER (m_AntiAliasing);
+ TRANSFER (m_DepthFormat);
+ TRANSFER (m_ColorFormat);
+ TRANSFER (m_MipMap);
+ TRANSFER (m_GenerateMips);
+ TRANSFER (m_SRGB);
+ transfer.Align();
+ TRANSFER (m_TextureSettings);
+}
+
+IMPLEMENT_CLASS (RenderTexture)
+IMPLEMENT_OBJECT_SERIALIZE (RenderTexture)
+
+bool RenderTextureSupportsStencil (RenderTexture* rt)
+{
+ if(!(gGraphicsCaps.hasStencil && GetBuildSettings ().hasAdvancedVersion))
+ return false;
+
+ if (rt)
+ return rt->GetDepthFormat() >= kDepthFormat24;
+
+ return GetGfxDevice().GetFramebufferDepthFormat() >= kDepthFormat24;
+}
+
+void RenderTextureDiscardContents(RenderTexture* rt, bool discardColor, bool discardDepth)
+{
+ GfxDevice& device = GetGfxDevice();
+
+ RenderSurfaceHandle color = rt ? rt->GetColorSurfaceHandle() : device.GetBackBufferColorSurface();
+ RenderSurfaceHandle rcolor = rt ? rt->GetResolvedColorSurfaceHandle() : RenderSurfaceHandle();
+ RenderSurfaceHandle depth = rt ? rt->GetDepthSurfaceHandle() : device.GetBackBufferDepthSurface();
+
+ if(discardColor && color.IsValid())
+ device.DiscardContents(color);
+ if(discardColor && rcolor.IsValid())
+ device.DiscardContents(rcolor);
+ if(discardDepth && depth.IsValid())
+ device.DiscardContents(depth);
+}
+
+
+// --------------------------------------------------------------------------
+
+
+#if ENABLE_UNIT_TESTS
+#include "External/UnitTest++/src/UnitTest++.h"
+SUITE (RenderTextureTests)
+{
+TEST(RenderTextureTests_BPPTableCorrect)
+{
+ // checks that you did not forget to update BPP counts when you added a new format :)
+ for (int i = 0; i < kRTFormatCount; ++i)
+ {
+ CHECK(kRenderTextureFormatBPP[i] != 0);
+ }
+ for (int i = 1; i < kDepthFormatCount; ++i) // skip DepthFormatNone, since it is expected to have zero
+ {
+ CHECK(kDepthFormatBPP[i] != 0);
+ }
+}
+}
+
+#endif
diff --git a/Runtime/Graphics/RenderTexture.h b/Runtime/Graphics/RenderTexture.h
new file mode 100644
index 0000000..44e7281
--- /dev/null
+++ b/Runtime/Graphics/RenderTexture.h
@@ -0,0 +1,210 @@
+#pragma once
+
+#include "Texture.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/GfxDevice/GfxDeviceObjects.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Runtime/Modules/ExportModules.h"
+
+namespace ShaderLab {
+ struct FastPropertyName;
+ class TexEnv;
+}
+
+enum {
+ kScreenOffscreen = -1,
+};
+
+
+class EXPORT_COREMODULE RenderTexture : public Texture
+{
+public:
+
+ DECLARE_OBJECT_SERIALIZE (RenderTexture)
+ REGISTER_DERIVED_CLASS (RenderTexture, Texture)
+
+ RenderTexture (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~RenderTexture (); declared-by-macro
+
+ void SetWidth (int width);
+ int GetWidth () const { return m_Width; }
+
+ void SetHeight (int height);
+ int GetHeight () const { return m_Height; }
+
+ void SetDepthFormat( DepthBufferFormat depth );
+ DepthBufferFormat GetDepthFormat() const { return static_cast<DepthBufferFormat>(m_DepthFormat); }
+
+ bool GetIsPowerOfTwo () const { return IsPowerOfTwo(m_Width) && IsPowerOfTwo(m_Height); }
+
+ void SetDimension (TextureDimension dim);
+ virtual TextureDimension GetDimension() const { return m_Dimension; }
+
+ void SetVolumeDepth(int v);
+ int GetVolumeDepth() const { return m_VolumeDepth; }
+
+ void SetAntiAliasing(int aa);
+ int GetAntiAliasing() const { return m_AntiAliasing; }
+ bool IsAntiAliased() const { return m_AntiAliasing > 1; }
+
+ void SetMipMap( bool mipmap );
+ bool GetMipMap() const { return m_MipMap; }
+
+ void SetGenerateMips (bool v);
+ bool GetGenerateMips() const { return m_GenerateMips; }
+
+ void SetSRGBReadWrite(bool sRGB);
+ bool GetSRGBReadWrite() const { return m_SRGB; }
+
+ void SetEnableRandomWrite(bool v);
+ bool GetEnableRandomWrite() const { return m_EnableRandomWrite; }
+
+ void SetCreatedFromScript( bool fromScript ) { m_CreatedFromScript = fromScript; }
+ bool GetCreatedFromScript() const { return m_CreatedFromScript; }
+
+ void SetSampleOnlyDepth( bool sampleOnly ) { m_SampleOnlyDepth = sampleOnly; }
+ bool GetSampleOnlyDepth() const { return m_SampleOnlyDepth; }
+
+ RenderTextureFormat GetColorFormat() const { return static_cast<RenderTextureFormat>(m_ColorFormat); }
+ void SetColorFormat( RenderTextureFormat format );
+
+ virtual bool HasMipMap () const { return m_MipMap; }
+ virtual int CountMipmaps() const { return 1; } //@TODO ?
+
+ enum {
+ kFlagDontSetViewport = (1<<0),
+ kFlagForceResolve = (1<<1), // Force resolve to texture: Used by MSAA targets and Xbox 360
+ kFlagDontRestoreColor = (1<<2), // Xbox 360 specific: do not restore old contents to EDRAM
+ kFlagDontRestoreDepth = (1<<3), // Xbox 360 specific: do not restore old contents to EDRAM
+ kFlagDontRestore = kFlagDontRestoreColor | kFlagDontRestoreDepth,
+ };
+
+ static void FindAndSetSRGBWrite( RenderTexture* newActive );
+
+ // Makes the render texture the current render target. If texture is NULL the back buffer is activated.
+ static void SetActive( RenderTexture* texture, int mipLevel = 0, CubemapFace face = kCubeFaceUnknown, UInt32 flags = 0 );
+ static bool SetActive(int count, RenderSurfaceHandle* colors, RenderSurfaceHandle depth, RenderTexture* rt, int mipLevel = 0, CubemapFace face=kCubeFaceUnknown, UInt32 flags=0);
+
+ // Returns the active render texture.
+ // NULL means the main window is active.
+ static RenderTexture* GetActive();
+
+ // Does card support RTs, and built with Unity Pro, and not manually disabled?
+ static bool IsEnabled ();
+ // this should not be used... it's there only to support RenderTexture.enabled in scripts, in case anyone uses it.
+ static void SetEnabled (bool enable);
+
+ // Destroys all render textures created
+ static void ReleaseAll ();
+
+ // Creates the render texture.
+ // Create is automatically called inside Activate the first time.
+ // Create can fail if RenderTexture::IsEnabled is false
+ bool Create ();
+
+ // Destroys the render texture
+ void Release ();
+
+ // Is the render texture created?
+ bool IsCreated() const { return m_ColorHandle.IsValid() || m_DepthHandle.IsValid(); }
+
+ // Discards the contents of this render texture
+ // Xbox 360: will not restore contents to EDRAM the next time this RenderTexture is set.
+ // GLES: will use combo of glDiscardFramebufferEXT and glClear to avoid both resolve and restore
+ // Other platforms: currently no effect.
+ // NB: we have both because no-arg one had been used directly from scripts, so we keep it for b/c
+ void DiscardContents();
+ void DiscardContents(bool discardColor, bool discardDepth);
+ void MarkRestoreExpected();
+
+ virtual bool ExtractImage (ImageReference* /*image*/, int /*imageIndex*/ = 0) const { return false; }
+ virtual void ApplySettings();
+
+ virtual int GetRuntimeMemorySize() const;
+
+
+ #if UNITY_EDITOR
+ TextureFormat GetEditorUITextureFormat () const { return -1; }
+ #endif
+
+ #if ENABLE_PROFILER || UNITY_EDITOR
+ virtual int GetStorageMemorySize() const { return 0; }
+ static int GetCreatedRenderTextureCount ();
+ static int GetCreatedRenderTextureBytes ();
+ #endif
+
+
+ RenderSurfaceHandle GetColorSurfaceHandle() { return m_ColorHandle; }
+ RenderSurfaceHandle GetResolvedColorSurfaceHandle() { return m_ResolvedColorHandle; }
+ RenderSurfaceHandle GetDepthSurfaceHandle() { return m_DepthHandle; }
+
+
+ virtual int GetDataWidth() const { return m_Width; }
+ virtual int GetDataHeight() const { return m_Height; }
+
+ void AwakeFromLoad(AwakeFromLoadMode mode);
+
+ virtual void UnloadFromGfxDevice(bool /*forceUnloadAll*/) { }
+ virtual void UploadToGfxDevice() { }
+
+ ShaderLab::TexEnv *SetGlobalProperty (const ShaderLab::FastPropertyName& name);
+
+ void GrabPixels (int left, int bottom, int width, int height);
+
+ // Flips vertical texel size to positive or negative if we're not using OpenGL coords
+ void CorrectVerticalTexelSize(bool shouldBePositive);
+
+ const TextureID& GetSecondaryTextureID() { return m_SecondaryTexID; }
+
+ static void SetTemporarilyAllowIndieRenderTexture (bool allow);
+
+private:
+ void DestroySurfaces();
+ void ResolveAntiAliasedSurface();
+ void UpdateTexelSize();
+ bool GetSupportedMipMapFlag(bool mipMap) const;
+
+private:
+ int m_Width; ///< range {1, 20000}
+ int m_Height;///< range {1, 20000}
+ int m_AntiAliasing;///< enum { None = 1, 2xMSAA = 2, 4xMSAA = 4, 8xMSAA = 8 } Anti-aliasing
+ int m_VolumeDepth;///< range {1, 20000}
+ int m_ColorFormat; ///< enum { RGBA32 = 0, Depth texture = 1 } Color buffer format
+ int m_DepthFormat; ///< enum { No depth buffer = 0, 16 bit depth = 1, 24 bit depth = 2 } Depth buffer format
+ TextureDimension m_Dimension;
+ bool m_MipMap;
+ bool m_GenerateMips;
+ bool m_SRGB;
+ bool m_EnableRandomWrite;
+ bool m_CreatedFromScript;
+ bool m_SampleOnlyDepth;
+
+ TextureID m_SecondaryTexID;
+
+ RenderSurfaceHandle m_ColorHandle;
+ RenderSurfaceHandle m_ResolvedColorHandle;
+ RenderSurfaceHandle m_DepthHandle;
+
+ int m_RegisteredSizeForStats;
+
+ ListNode<RenderTexture> m_RenderTexturesNode;
+
+ bool m_SecondaryTexIDUsed;
+};
+
+int EstimateRenderTextureSize (int width, int height, int depth, RenderTextureFormat format, DepthBufferFormat depthFormat, TextureDimension dim, bool mipMap);
+
+// return or passed tex (but created if needed) or NULL if RT cannot be created by any reason
+RenderTexture* EnsureRenderTextureIsCreated(RenderTexture* tex);
+
+bool RenderTextureSupportsStencil(RenderTexture* rt);
+
+void RenderTextureDiscardContents(RenderTexture* rt, bool discardColor, bool discardDepth);
+
+#include "Runtime/Scripting/Backend/ScriptingTypes.h"
+struct ScriptingRenderBuffer
+{
+ int m_RenderTextureInstanceID;
+ RenderSurfaceBase* m_BufferPtr;
+};
diff --git a/Runtime/Graphics/S3Decompression.cpp b/Runtime/Graphics/S3Decompression.cpp
new file mode 100644
index 0000000..b2f518d
--- /dev/null
+++ b/Runtime/Graphics/S3Decompression.cpp
@@ -0,0 +1,1882 @@
+#include "UnityPrefix.h"
+#include "S3Decompression.h"
+#include "Runtime/Utilities/LogAssert.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Graphics/Image.h"
+#include "Runtime/Graphics/FlashATFDecompression.h"
+#include "Runtime/Graphics/ETC2Decompression.h"
+
+#define PRINT_DECOMPRESSION_TIMES 0
+#if PRINT_DECOMPRESSION_TIMES
+#include "Runtime/Input/TimeManager.h"
+#endif
+
+
+// \todo [2010-02-09 petri] Define in some config file?
+#define HAS_ETC_DECOMPRESSOR (UNITY_ANDROID && !i386 || UNITY_EDITOR || UNITY_BB10 || UNITY_TIZEN)
+#define HAS_ATC_DECOMPRESSOR (UNITY_ANDROID && !i386 || UNITY_EDITOR)
+
+#define HAS_ASTC_DECOMPRESSOR (UNITY_ANDROID || UNITY_EDITOR)
+
+#if HAS_ATC_DECOMPRESSOR
+ #include "External/Qualcomm_TextureConverter/TextureConverter.h"
+ #if UNITY_WIN
+ #include <process.h>
+ // On Windows, Qonvert is compiled with MSVCRT, but we link against LIBCMT; filling the gaps here
+ extern "C" int _imp___getpid(void){ return _getpid(); }
+ #elif UNITY_OSX
+ // On OSX, Qonvert is compiled with -fstack-protector, but we don't use it for the MacEditor
+ extern "C"
+ {
+ void* __stack_chk_guard = (void*)0xdeadc0de;
+ void __stack_chk_fail() { ErrorString("stack_chk_fail failed"); }
+ }
+ #elif defined(ARM_ARCH_VFP)
+ // On Android, Qonvert is compiled with armv5 (no vfp), but we don't want to include the softfp emulation
+ extern "C" unsigned __aeabi_f2uiz(float f){return (int)f;}
+ #endif
+#endif
+
+// ------------------------------------------------------------------------
+// DXT
+
+
+#if HAS_DXT_DECOMPRESSOR
+
+struct DXTColBlock
+{
+ UInt16 col0;
+ UInt16 col1;
+ UInt8 row[4];
+};
+
+struct DXTAlphaBlockExplicit
+{
+ UInt16 row[4];
+};
+
+struct DXTAlphaBlock3BitLinear
+{
+ UInt8 alpha0;
+ UInt8 alpha1;
+ UInt8 stuff[6];
+};
+
+#if UNITY_BIG_ENDIAN
+
+struct Color8888
+{
+ UInt8 b;
+ UInt8 g;
+ UInt8 r;
+ UInt8 a;
+};
+
+static inline UInt16 GetByteSwap16( UInt16 i )
+{
+ return static_cast<UInt16>((i << 8) | (i >> 8));
+}
+
+static inline UInt32 GetByteSwap32( UInt32 i )
+{
+ return static_cast<UInt32>((i >> 24) | (i >> 8) & 0x0000ff00 | (i << 8) & 0x00ff0000 | (i << 24));
+}
+
+struct Color565
+{
+ unsigned b : 5;
+ unsigned g : 6;
+ unsigned r : 5;
+};
+
+#else
+
+static inline UInt16 GetByteSwap16( UInt16 i ) { return i; }
+static inline UInt32 GetByteSwap32( UInt32 i ) { return i; }
+
+struct Color8888 // EH? This seems to be wrong!
+{
+ UInt8 r;
+ UInt8 g;
+ UInt8 b;
+ UInt8 a;
+};
+
+struct Color565
+{
+ unsigned b : 5;
+ unsigned g : 6;
+ unsigned r : 5;
+};
+
+#endif
+
+
+inline void GetColorBlockColors( const DXTColBlock* block, Color8888 colors[4] )
+{
+ union
+ {
+ Color565 color565;
+ UInt16 color16;
+ } col0, col1;
+ col0.color16 = GetByteSwap16( block->col0 );
+ col1.color16 = GetByteSwap16( block->col1 );
+
+ const Color565* col;
+
+ // It's not enough just to shift bits to full 8 bit precision - the lower bits
+ // must also be filled to match the way hardware does the rounding.
+ col = &col0.color565;
+ colors[0].r = ( col->r << 3 ) | ( col->r >> 2 );
+ colors[0].g = ( col->g << 2 ) | ( col->g >> 4 );
+ colors[0].b = ( col->b << 3 ) | ( col->b >> 2 );
+ colors[0].a = 0xff;
+
+ col = &col1.color565;
+ colors[1].r = ( col->r << 3 ) | ( col->r >> 2 );
+ colors[1].g = ( col->g << 2 ) | ( col->g >> 4 );
+ colors[1].b = ( col->b << 3 ) | ( col->b >> 2 );
+ colors[1].a = 0xff;
+
+ if( col0.color16 > col1.color16 )
+ {
+ // Four-color block: derive the other two colors.
+ // 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3
+ // These two bit codes correspond to the 2-bit fields
+ // stored in the 64-bit block.
+
+ colors[2].r = (UInt8)(((UInt16)colors[0].r * 2 + (UInt16)colors[1].r )/3);
+ colors[2].g = (UInt8)(((UInt16)colors[0].g * 2 + (UInt16)colors[1].g )/3);
+ colors[2].b = (UInt8)(((UInt16)colors[0].b * 2 + (UInt16)colors[1].b )/3);
+ colors[2].a = 0xff;
+
+ colors[3].r = (UInt8)(((UInt16)colors[0].r + (UInt16)colors[1].r *2 )/3);
+ colors[3].g = (UInt8)(((UInt16)colors[0].g + (UInt16)colors[1].g *2 )/3);
+ colors[3].b = (UInt8)(((UInt16)colors[0].b + (UInt16)colors[1].b *2 )/3);
+ colors[3].a = 0xff;
+ }
+ else
+ {
+ // Three-color block: derive the other color.
+ // 00 = color_0, 01 = color_1, 10 = color_2, 11 = transparent.
+ // These two bit codes correspond to the 2-bit fields
+ // stored in the 64-bit block.
+
+ colors[2].r = (UInt8)(((UInt16)colors[0].r + (UInt16)colors[1].r )/2);
+ colors[2].g = (UInt8)(((UInt16)colors[0].g + (UInt16)colors[1].g )/2);
+ colors[2].b = (UInt8)(((UInt16)colors[0].b + (UInt16)colors[1].b )/2);
+ colors[2].a = 0xff;
+
+ // set transparent to black to match DXT specs
+ colors[3].r = 0x00;
+ colors[3].g = 0x00;
+ colors[3].b = 0x00;
+ colors[3].a = 0x00;
+ }
+}
+
+// width is width of destination image in pixels
+inline void DecodeColorBlock( UInt32* dest, const DXTColBlock& colorBlock, int width, const UInt32 colors[4] )
+{
+ // r steps through lines in y
+ for( int r=0; r < 4; r++, dest += width-4 )
+ {
+ // width * 4 bytes per pixel per line
+ // each j block row is 4 lines of pixels
+
+ // Do four pixels, step in twos because n is only used for the shift
+ // n steps through pixels
+ for( int n = 0; n < 8; n += 2 )
+ {
+ UInt32 bits = (colorBlock.row[r] >> n) & 3;
+ DebugAssert (bits <= 3);
+ *dest = colors[bits];
+ ++dest;
+ }
+ }
+}
+
+inline void DecodeAlphaExplicit( UInt32* dest, const DXTAlphaBlockExplicit& alphaBlock, int width, UInt32 alphazero )
+{
+ // alphazero is a bit mask that when ANDed with the image color
+ // will zero the alpha bits, so if the image DWORDs are
+ // ARGB then alphazero will be 0x00FFFFFF or if
+ // RGBA then alphazero will be 0xFFFFFF00
+ // alphazero constructed automatically from field order of Color8888 structure
+
+ union
+ {
+ Color8888 col;
+ UInt32 col32;
+ } u;
+ u.col.r = u.col.g = u.col.b = 0;
+
+ for( int row=0; row < 4; row++, dest += width-4 )
+ {
+ UInt16 wrd = GetByteSwap16( alphaBlock.row[ row ] );
+
+ for( int pix = 0; pix < 4; ++pix )
+ {
+ // zero the alpha bits of image pixel
+ *dest &= alphazero;
+
+ u.col.a = wrd & 0x000f; // get lowest 4 bits
+ u.col.a = u.col.a | (u.col.a << 4);
+
+ *dest |= u.col32; // OR into the previously nulled alpha
+
+ wrd >>= 4;
+ ++dest;
+ }
+ }
+}
+
+inline void DecodeAlpha3BitLinear( UInt32* dest, const DXTAlphaBlock3BitLinear& alphaBlock, int width, UInt32 alphazero )
+{
+ union
+ {
+ Color8888 alphaCol[4][4];
+ UInt32 alphaCol32[4][4];
+ } u;
+ UInt32 alphamask = ~alphazero;
+ UInt16 alphas[8];
+
+ alphas[0] = alphaBlock.alpha0;
+ alphas[1] = alphaBlock.alpha1;
+
+ // 8-alpha or 6-alpha block?
+ if( alphas[0] > alphas[1] )
+ {
+ // 8-alpha block: derive the other 6 alphas.
+ // 000 = alpha[0], 001 = alpha[1], others are interpolated
+
+ alphas[2] = ( 6 * alphas[0] + alphas[1] + 3) / 7; // bit code 010
+ alphas[3] = ( 5 * alphas[0] + 2 * alphas[1] + 3) / 7; // Bit code 011
+ alphas[4] = ( 4 * alphas[0] + 3 * alphas[1] + 3) / 7; // Bit code 100
+ alphas[5] = ( 3 * alphas[0] + 4 * alphas[1] + 3) / 7; // Bit code 101
+ alphas[6] = ( 2 * alphas[0] + 5 * alphas[1] + 3) / 7; // Bit code 110
+ alphas[7] = ( alphas[0] + 6 * alphas[1] + 3) / 7; // Bit code 111
+ }
+ else
+ {
+ // 6-alpha block: derive the other alphas.
+ // 000 = alpha[0], 001 = alpha[1], others are interpolated
+
+ alphas[2] = (4 * alphas[0] + alphas[1] + 2) / 5; // Bit code 010
+ alphas[3] = (3 * alphas[0] + 2 * alphas[1] + 2) / 5; // Bit code 011
+ alphas[4] = (2 * alphas[0] + 3 * alphas[1] + 2) / 5; // Bit code 100
+ alphas[5] = ( alphas[0] + 4 * alphas[1] + 2) / 5; // Bit code 101
+ alphas[6] = 0; // Bit code 110
+ alphas[7] = 255; // Bit code 111
+ }
+
+ // Decode 3-bit fields into array of 16 bytes with same value
+ UInt8 blockBits[4][4];
+
+ // first two rows of 4 pixels each:
+ const UInt32 mask = 7; // three bits
+
+ // TBD: Ouch! Unaligned reads there! Poor old PPC!
+
+ UInt32 bits = GetByteSwap32( *(UInt32*)&alphaBlock.stuff[0] ); // first 3 bytes
+
+ blockBits[0][0] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[0][1] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[0][2] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[0][3] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[1][0] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[1][1] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[1][2] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[1][3] = (UInt8)( bits & mask );
+
+ // now last two rows
+ // it's ok to fetch one byte too much because there will always be color block after alpha
+ bits = GetByteSwap32( *(UInt32*)&alphaBlock.stuff[3] ); // last 3 bytes
+
+ blockBits[2][0] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[2][1] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[2][2] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[2][3] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[3][0] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[3][1] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[3][2] = (UInt8)( bits & mask );
+ bits >>= 3;
+ blockBits[3][3] = (UInt8)( bits & mask );
+
+ // decode the codes into alpha values
+ int row, pix;
+
+ for( row = 0; row < 4; row++ )
+ {
+ for( pix=0; pix < 4; pix++ )
+ {
+ u.alphaCol[row][pix].a = (UInt8) alphas[ blockBits[row][pix] ];
+ }
+ }
+
+ // Write out alpha values to the image bits
+ for( row=0; row < 4; row++, dest += width-4 )
+ {
+ for( pix = 0; pix < 4; pix++ )
+ {
+ *dest &= alphazero; // zero the alpha bits of image pixel
+ *dest |= u.alphaCol32[row][pix] & alphamask; // or the bits into the prev. nulled alpha
+ dest++;
+ }
+ }
+}
+
+
+void DecompressDXT1( int xblocks, int yblocks, int destWidth, const UInt32* m_pCompBytes, UInt32* decompBytes )
+{
+ union
+ {
+ Color8888 colors8888[4];
+ UInt32 colors32[4];
+ } u;
+
+ for( int j = 0; j < yblocks; ++j )
+ {
+ const DXTColBlock* block = (const DXTColBlock*)( m_pCompBytes + j * xblocks * 2 ); // 8 bytes per block
+
+ for( int i = 0; i < xblocks; ++i, ++block )
+ {
+ GetColorBlockColors( block, u.colors8888 );
+ UInt32* dest = (UInt32*)((UInt8*)decompBytes + i*16 + (j*4) * destWidth * 4 );
+ DecodeColorBlock( dest, *block, destWidth, u.colors32 );
+ }
+ }
+}
+
+
+void DecompressDXT3( int xblocks, int yblocks, int destWidth, const UInt32* m_pCompBytes, UInt32* decompBytes )
+{
+ union
+ {
+ Color8888 colors[4];
+ UInt32 colors32[4];
+ } u;
+
+ // fill alphazero with appropriate value to zero out alpha when
+ // alphazero is ANDed with the image color
+ u.colors[0].a = 0;
+ u.colors[0].r = u.colors[0].g = u.colors[0].b = 0xff;
+ UInt32 alphazero = u.colors32[0];
+
+ for( int j = 0; j < yblocks; ++j )
+ {
+ const DXTColBlock* block = (const DXTColBlock*)( m_pCompBytes + j * xblocks * 4 ); // 16 bytes for alpha+color block
+
+ for( int i = 0; i < xblocks; ++i, ++block )
+ {
+ // Get alpha block
+ const DXTAlphaBlockExplicit* alphaBlock = (const DXTAlphaBlockExplicit*)block;
+ ++block;
+
+ // Get color block & colors
+ GetColorBlockColors( block, u.colors );
+
+ // Decode the color block into the bitmap bits
+ UInt32* dest = (UInt32*)((UInt8*)decompBytes + i*16 + (j*4) * destWidth * 4 );
+ DecodeColorBlock( dest, *block, destWidth, u.colors32 );
+
+ // Overwrite the previous alpha bits with the alpha block info
+ DecodeAlphaExplicit( dest, *alphaBlock, destWidth, alphazero );
+ }
+ }
+}
+
+
+void DecompressDXT5( int xblocks, int yblocks, int destWidth, const UInt32* m_pCompBytes, UInt32* decompBytes )
+{
+ union
+ {
+ Color8888 colors[4];
+ UInt32 colors32[4];
+ } u;
+
+ // fill alphazero with appropriate value to zero out alpha when
+ // alphazero is ANDed with the image color 32 bit
+ u.colors[0].a = 0;
+ u.colors[0].r = u.colors[0].g = u.colors[0].b = 0xff;
+ UInt32 alphazero = u.colors32[0];
+
+ for( int j = 0; j < yblocks; ++j )
+ {
+ const DXTColBlock* block = (const DXTColBlock*) ( m_pCompBytes + j * xblocks * 4 ); // 16 bytes for alpha+color block
+
+ for( int i = 0; i < xblocks; ++i, ++block )
+ {
+ // Get alpha block
+ const DXTAlphaBlock3BitLinear* alphaBlock = (const DXTAlphaBlock3BitLinear*)block;
+ block++;
+
+ // Get color block & colors
+ GetColorBlockColors( block, u.colors );
+
+ // Decode the color block into the bitmap bits
+ UInt32* dest = (UInt32*)((UInt8*)decompBytes + i*16 + (j*4) * destWidth * 4 );
+ DecodeColorBlock( dest, *block, destWidth, u.colors32 );
+
+ // Overwrite the previous alpha bits with the alpha block info
+ DecodeAlpha3BitLinear( dest, *alphaBlock, destWidth, alphazero );
+ }
+ }
+}
+
+#endif
+
+
+
+// ------------------------------------------------------------------------
+// PVRTC
+
+
+
+#if HAS_PVRTC_DECOMPRESSOR
+
+const int kPVRSizeX2 = 8; // block width 8 pixels in 2BPP case
+const int kPVRSizeX4 = 4; // block width 4 pixels in 4BPP case
+const int kPVRBlockSizeY = 4; // block height always 4 pixels
+
+const int kPVRPunchThroughIndex = 2;
+
+#define PVR_WRAP_POT_COORD(Val, Size) ((Val) & ((Size)-1))
+
+#define CLAMP(X, lower, upper) (std::min(std::max((X),(lower)), (upper)))
+
+#define PVR_LIMIT_COORD(Val, Size, AssumeImageTiles) \
+ ((AssumeImageTiles) ? PVR_WRAP_POT_COORD((Val), (Size)) : CLAMP((Val), 0, (Size)-1))
+
+// 64 bits per PVRTC block
+struct PVRTCBlock
+{
+ UInt32 packedData[2];
+};
+
+
+
+// Given a block, extract the color information and convert to 5554 formats
+static void Unpack5554Colour (const PVRTCBlock *pBlock, int ABColours[2][4])
+{
+ UInt32 RawBits[2];
+
+ int i;
+
+ /*
+ // Extract A and B
+ */
+ RawBits[0] = pBlock->packedData[1] & (0xFFFE); /*15 bits (shifted up by one)*/
+ RawBits[1] = pBlock->packedData[1] >> 16; /*16 bits*/
+
+ /*
+ //step through both colours
+ */
+ for(i = 0; i < 2; i++)
+ {
+ /*
+ // if completely opaque
+ */
+ if(RawBits[i] & (1<<15))
+ {
+ /*
+ // Extract R and G (both 5 bit)
+ */
+ ABColours[i][0] = (RawBits[i] >> 10) & 0x1F;
+ ABColours[i][1] = (RawBits[i] >> 5) & 0x1F;
+
+ /*
+ // The precision of Blue depends on A or B. If A then we need to
+ // replicate the top bit to get 5 bits in total
+ */
+ ABColours[i][2] = RawBits[i] & 0x1F;
+ if(i==0)
+ {
+ ABColours[0][2] |= ABColours[0][2] >> 4;
+ }
+
+ /*
+ // set 4bit alpha fully on...
+ */
+ ABColours[i][3] = 0xF;
+ }
+ /*
+ // Else if colour has variable translucency
+ */
+ else
+ {
+ /*
+ // Extract R and G (both 4 bit).
+ // (Leave a space on the end for the replication of bits
+ */
+ ABColours[i][0] = (RawBits[i] >> (8-1)) & 0x1E;
+ ABColours[i][1] = (RawBits[i] >> (4-1)) & 0x1E;
+
+ /*
+ // replicate bits to truly expand to 5 bits
+ */
+ ABColours[i][0] |= ABColours[i][0] >> 4;
+ ABColours[i][1] |= ABColours[i][1] >> 4;
+
+ /*
+ // grab the 3(+padding) or 4 bits of blue and add an extra padding bit
+ */
+ ABColours[i][2] = (RawBits[i] & 0xF) << 1;
+
+ /*
+ // expand from 3 to 5 bits if this is from colour A, or 4 to 5 bits if from
+ // colour B
+ */
+ if(i==0)
+ {
+ ABColours[0][2] |= ABColours[0][2] >> 3;
+ }
+ else
+ {
+ ABColours[0][2] |= ABColours[0][2] >> 4;
+ }
+
+ /*
+ // Set the alpha bits to be 3 + a zero on the end
+ */
+ ABColours[i][3] = (RawBits[i] >> 11) & 0xE;
+ }/*end if variable alpha*/
+ }/*end for i*/
+
+}
+
+
+// Given the block and the texture type and it's relative position in the
+// 2x2 group of blocks, extract the bit patterns for the fully defined pixels.
+template<bool Do2bitMode>
+static void UnpackModulations(const PVRTCBlock *pBlock,
+ int ModulationVals[8][16],
+ int ModulationModes[8][16],
+ int StartX,
+ int StartY)
+{
+ int BlockModMode= pBlock->packedData[1] & 1;
+ UInt32 ModulationBits = pBlock->packedData[0];
+
+ // if it's in an interpolated mode
+ if(Do2bitMode && BlockModMode)
+ {
+ // run through all the pixels in the block. Note we can now treat all the
+ // "stored" values as if they have 2bits (even when they didn't!)
+ for(int y = 0; y < kPVRBlockSizeY; y++)
+ {
+ for(int x = 0; x < kPVRSizeX2; x++)
+ {
+ ModulationModes[y+StartY][x+StartX] = BlockModMode;
+
+ // if this is a stored value...
+ if(((x^y)&1) == 0)
+ {
+ ModulationVals[y+StartY][x+StartX] = ModulationBits & 3;
+ ModulationBits >>= 2;
+ }
+ }
+ }
+ }
+ // else if direct encoded 2bit mode - i.e. 1 mode bit per pixel
+ else if(Do2bitMode)
+ {
+ for(int y = 0; y < kPVRBlockSizeY; y++)
+ {
+ for(int x = 0; x < kPVRSizeX2; x++)
+ {
+ ModulationModes[y+StartY][x+StartX] = BlockModMode;
+
+ // double the bits so 0=> 00, and 1=>11
+ if(ModulationBits & 1)
+ {
+ ModulationVals[y+StartY][x+StartX] = 0x3;
+ }
+ else
+ {
+ ModulationVals[y+StartY][x+StartX] = 0x0;
+ }
+ ModulationBits >>= 1;
+ }
+ }
+ }
+ // else its the 4bpp mode so each value has 2 bits
+ else
+ {
+ for(int y = 0; y < kPVRBlockSizeY; y++)
+ {
+ for(int x = 0; x < kPVRSizeX4; x++)
+ {
+ ModulationModes[y+StartY][x+StartX] = BlockModMode;
+ ModulationVals[y+StartY][x+StartX] = ModulationBits & 3;
+ ModulationBits >>= 2;
+ }
+ }
+ }
+
+ // make sure nothing is left over
+ DebugAssert(ModulationBits==0);
+}
+
+
+template<bool do2bitMode>
+static int GetUCoordPVR (int x)
+{
+ if (do2bitMode)
+ {
+ int u = (x & 0x7) | ((~x & 0x4) << 1);
+ return u - kPVRSizeX2/2;
+ }
+ else
+ {
+ int u = (x & 0x3) | ((~x & 0x2) << 1);
+ return u - kPVRSizeX4/2;
+ }
+}
+
+static int GetVCoordPVR (int y)
+{
+ int v = (y & 0x3) | ((~y & 0x2) << 1);
+ return v - kPVRBlockSizeY/2;
+}
+
+
+// Performs a HW bit accurate interpolation of either the
+// A or B colors for a particular pixel
+//
+// NOTE: It is assumed that the source colors are in ARGB 5554 format -
+// This means that some "preparation" of the values will be necessary.
+// NOTE: QP is Q-P, SR is S-R
+template<bool Do2bitMode>
+static void InterpolateColoursPVRTC(const int* __restrict P,
+ const int* __restrict QP,
+ const int* __restrict R,
+ const int* __restrict SR,
+ const int u,
+ const int v,
+ int* __restrict Result)
+{
+ int k;
+ int tmp1, tmp2;
+
+ int uscale = Do2bitMode ? 8 : 4;
+
+ for(k = 0; k < 4; k++)
+ {
+ tmp1 = P[k] * uscale + u * QP[k];
+ tmp2 = R[k] * uscale + u * SR[k];
+
+ tmp1 = tmp1 * 4 + v * (tmp2 - tmp1);
+
+ Result[k] = tmp1;
+ }
+
+ /*
+ // Lop off the appropriate number of bits to get us to 8 bit precision
+ */
+ if(Do2bitMode)
+ {
+ /*
+ // do RGB
+ */
+ for(k = 0; k < 3; k++)
+ {
+ Result[k] >>= 2;
+ }
+
+ Result[3] >>= 1;
+ }
+ else
+ {
+ /*
+ // do RGB (A is ok)
+ */
+ for(k = 0; k < 3; k++)
+ {
+ Result[k] >>= 1;
+ }
+ }
+
+ /*
+ // sanity check
+ */
+ for(k = 0; k < 4; k++)
+ {
+ DebugAssert(Result[k] < 256);
+ }
+
+
+ /*
+ // Convert from 5554 to 8888
+ //
+ // do RGB 5.3 => 8
+ */
+ for(k = 0; k < 3; k++)
+ {
+ Result[k] += Result[k] >> 5;
+ }
+ Result[3] += Result[3] >> 4;
+
+ /*
+ // 2nd sanity check
+ */
+ for(k = 0; k < 4; k++)
+ {
+ DebugAssert(Result[k] < 256);
+ }
+
+}
+
+
+// Get the modulation value as a numerator of a fraction of 8ths
+template<bool Do2bitMode>
+static void GetModulationValue(int x,
+ int y,
+ const int ModulationVals[8][16],
+ const int ModulationModes[8][16],
+ int *Mod,
+ int *DoPT)
+{
+ static const int RepVals0[4] = {0, 3, 5, 8};
+ static const int RepVals1[4] = {0, 4, 4, 8};
+
+ int ModVal;
+
+ /*
+ // Map X and Y into the local 2x2 block
+ */
+ y = (y & 0x3) | ((~y & 0x2) << 1);
+ if(Do2bitMode)
+ {
+ x = (x & 0x7) | ((~x & 0x4) << 1);
+
+ }
+ else
+ {
+ x = (x & 0x3) | ((~x & 0x2) << 1);
+ }
+
+ /*
+ // assume no PT for now
+ */
+ *DoPT = 0;
+
+ /*
+ // extract the modulation value. If a simple encoding
+ */
+ if(ModulationModes[y][x]==0)
+ {
+ ModVal = RepVals0[ModulationVals[y][x]];
+ }
+ else if(Do2bitMode)
+ {
+ /*
+ // if this is a stored value
+ */
+ if(((x^y)&1)==0)
+ {
+ ModVal = RepVals0[ModulationVals[y][x]];
+ }
+ /*
+ // else average from the neighbours
+ //
+ // if H&V interpolation...
+ */
+ else if(ModulationModes[y][x] == 1)
+ {
+ ModVal = (RepVals0[ModulationVals[y-1][x]] +
+ RepVals0[ModulationVals[y+1][x]] +
+ RepVals0[ModulationVals[y][x-1]] +
+ RepVals0[ModulationVals[y][x+1]] + 2) / 4;
+ }
+ /*
+ // else if H-Only
+ */
+ else if(ModulationModes[y][x] == 2)
+ {
+ ModVal = (RepVals0[ModulationVals[y][x-1]] +
+ RepVals0[ModulationVals[y][x+1]] + 1) / 2;
+ }
+ /*
+ // else it's V-Only
+ */
+ else
+ {
+ ModVal = (RepVals0[ModulationVals[y-1][x]] +
+ RepVals0[ModulationVals[y+1][x]] + 1) / 2;
+
+ }/*end if/then/else*/
+ }
+ /*
+ // else it's 4BPP and PT encoding
+ */
+ else
+ {
+ ModVal = RepVals1[ModulationVals[y][x]];
+
+ *DoPT = ModulationVals[y][x] == kPVRPunchThroughIndex;
+ }
+
+ *Mod =ModVal;
+}
+
+
+
+// PVRTC UV twiddling interleaves bits of Y & X, like: XYXYXYXYXY.
+// If size of one coordinate is larger, those bits are just copied into higher bits;
+// e.g. XXXX,YYYYYYYY = YYYYXYXYXYXY.
+
+static UInt32 TwiddleY_PVRTC(UInt32 YSize, UInt32 XSize, UInt32 YPos)
+{
+ UInt32 Twiddled;
+
+ UInt32 MinDimension;
+
+ UInt32 SrcBitPos;
+ UInt32 DstBitPos;
+
+ int ShiftCount;
+
+ DebugAssert(YPos < YSize);
+ DebugAssert(IsPowerOfTwo(YSize));
+ DebugAssert(IsPowerOfTwo(XSize));
+
+ if (YSize < XSize)
+ MinDimension = YSize;
+ else
+ MinDimension = XSize;
+
+ // Step through all the bits in the "minimum" dimension
+ SrcBitPos = 1;
+ DstBitPos = 1;
+ Twiddled = 0;
+ ShiftCount = 0;
+
+ while (SrcBitPos < MinDimension)
+ {
+ if(YPos & SrcBitPos)
+ Twiddled |= DstBitPos;
+
+ SrcBitPos <<= 1;
+ DstBitPos <<= 2;
+ ShiftCount += 1;
+ }
+
+ // prepend any unused bits, if they were from Y
+ if (YSize >= XSize)
+ {
+ YPos >>= ShiftCount;
+ Twiddled |= (YPos << (2*ShiftCount));
+ }
+
+ return Twiddled;
+}
+
+
+static UInt32 TwiddleX_PVRTC(UInt32 YSize, UInt32 XSize, UInt32 XPos)
+{
+ UInt32 Twiddled;
+
+ UInt32 MinDimension;
+
+ UInt32 SrcBitPos;
+ UInt32 DstBitPos;
+
+ int ShiftCount;
+
+ DebugAssert(XPos < XSize);
+ DebugAssert(IsPowerOfTwo(YSize));
+ DebugAssert(IsPowerOfTwo(XSize));
+
+ if (YSize < XSize)
+ MinDimension = YSize;
+ else
+ MinDimension = XSize;
+
+ // Step through all the bits in the "minimum" dimension
+ SrcBitPos = 1;
+ DstBitPos = 2;
+ Twiddled = 0;
+ ShiftCount = 0;
+
+ while (SrcBitPos < MinDimension)
+ {
+ if (XPos & SrcBitPos)
+ {
+ Twiddled |= DstBitPos;
+ }
+
+ SrcBitPos <<= 1;
+ DstBitPos <<= 2;
+ ShiftCount += 1;
+ }
+
+ // prepend any unused bits, if they were from X
+ if (YSize < XSize)
+ {
+ XPos >>= ShiftCount;
+ Twiddled |= (XPos << (2*ShiftCount));
+ }
+
+ return Twiddled;
+}
+
+
+
+static UInt32 TwiddleUVPVRTC(UInt32 YSize, UInt32 XSize, UInt32 YPos, UInt32 XPos)
+{
+ return TwiddleY_PVRTC (YSize, XSize, YPos) + TwiddleX_PVRTC (YSize, XSize, XPos);
+}
+
+
+
+template<bool Do2bitMode, bool AssumeImageTiles>
+static void DecompressPVRTC(const PVRTCBlock *pCompressedData,
+ const int XDim,
+ const int YDim,
+ unsigned char* pResultImage)
+{
+ int BlkX, BlkY;
+ int BlkXp1, BlkYp1;
+ int BlkXDim, BlkYDim;
+
+ int ModulationVals[8][16];
+ int ModulationModes[8][16];
+
+ int Mod, DoPT;
+
+ // local neighbourhood of blocks
+ const PVRTCBlock *pBlocks[2][2];
+
+ const PVRTCBlock *pPrevious[2][2] = {{NULL, NULL}, {NULL, NULL}};
+
+ // Low precision colors extracted from the blocks.
+ // Rightmost colors have leftmost values subtracted from them.
+ struct PVRBlockColors {
+ int Reps[2][4];
+ };
+ PVRBlockColors Colours5554[2][2];
+
+ // Interpolated A and B colours for the pixel
+ int ASig[4], BSig[4];
+
+ const int XBlockSize = Do2bitMode ? kPVRSizeX2 : kPVRSizeX4;
+
+
+ // For MBX don't allow the sizes to get too small
+ BlkXDim = std::max(2, XDim / XBlockSize);
+ BlkYDim = std::max(2, YDim / kPVRBlockSizeY);
+
+ // Step through the pixels of the image decompressing each one in turn
+ //
+ // Note that this is a hideously inefficient way to do this!
+ for (int y = 0; y < YDim; y++)
+ {
+ BlkY = (y - kPVRBlockSizeY/2);
+ BlkY = PVR_LIMIT_COORD(BlkY, YDim, AssumeImageTiles);
+ BlkY /= kPVRBlockSizeY;
+ //BlkY = PVR_LIMIT_COORD(BlkY, BlkYDim, AssumeImageTiles);
+ BlkYp1 = PVR_LIMIT_COORD(BlkY+1, BlkYDim, AssumeImageTiles);
+
+ // Since Y & X coordinates twiddle into separate bits,
+ // we can compute Y block twiddle mask here for the whole row.
+ const PVRTCBlock* blockPointerY = pCompressedData + TwiddleY_PVRTC (BlkYDim, BlkXDim, BlkY);
+ const PVRTCBlock* blockPointerY1 = pCompressedData + TwiddleY_PVRTC (BlkYDim, BlkXDim, BlkYp1);
+
+ int coordV = GetVCoordPVR(y);
+
+ for (int x = 0; x < XDim; x++)
+ {
+ // map this pixel to the top left neighbourhood of blocks
+ BlkX = (x - XBlockSize/2);
+ BlkX = PVR_LIMIT_COORD(BlkX, XDim, AssumeImageTiles);
+ BlkX /= XBlockSize;
+ //BlkX = PVR_LIMIT_COORD(BlkX, BlkXDim, AssumeImageTiles);
+
+
+ // compute the positions of the other 3 blocks
+ BlkXp1 = PVR_LIMIT_COORD(BlkX+1, BlkXDim, AssumeImageTiles);
+
+ // Map to block memory locations
+
+ // block offsets, X bits
+ UInt32 blockOffsetX = TwiddleX_PVRTC (BlkYDim, BlkXDim, BlkX);
+ UInt32 blockOffsetX1 = TwiddleX_PVRTC (BlkYDim, BlkXDim, BlkXp1);
+
+ pBlocks[0][0] = blockPointerY + blockOffsetX;
+ pBlocks[0][1] = blockPointerY + blockOffsetX1;
+ pBlocks[1][0] = blockPointerY1 + blockOffsetX;
+ pBlocks[1][1] = blockPointerY1 + blockOffsetX1;
+
+
+ // extract the colours and the modulation information IF the previous values
+ // have changed.
+ if (pPrevious[0][0]!=pBlocks[0][0]||pPrevious[0][1]!=pBlocks[0][1]||
+ pPrevious[1][0]!=pBlocks[1][0]||pPrevious[1][1]!=pBlocks[1][1])
+ {
+ int StartY = 0;
+ for (int i = 0; i < 2; i++)
+ {
+ int StartX = 0;
+ for (int j = 0; j < 2; j++)
+ {
+ Unpack5554Colour(pBlocks[i][j], Colours5554[i][j].Reps);
+
+ UnpackModulations<Do2bitMode>(pBlocks[i][j],
+ ModulationVals,
+ ModulationModes,
+ StartX, StartY);
+
+ StartX += XBlockSize;
+ } // end for j
+
+ // for rightmost color block, subtract leftmost colors now
+ for (int ab = 0; ab < 2; ++ab)
+ {
+ for (int c = 0; c < 4; ++c)
+ Colours5554[i][1].Reps[ab][c] -= Colours5554[i][0].Reps[ab][c];
+ }
+
+ StartY += kPVRBlockSizeY;
+ } // end for i
+
+ // make a copy of the new pointers
+ pPrevious[0][0] = pBlocks[0][0];
+ pPrevious[0][1] = pBlocks[0][1];
+ pPrevious[1][0] = pBlocks[1][0];
+ pPrevious[1][1] = pBlocks[1][1];
+ } // end if the blocks have changed
+
+
+ // decompress the pixel. First compute the interpolated A and B signals
+ int coordU = GetUCoordPVR<Do2bitMode>(x);
+ InterpolateColoursPVRTC<Do2bitMode>(Colours5554[0][0].Reps[0],
+ Colours5554[0][1].Reps[0],
+ Colours5554[1][0].Reps[0],
+ Colours5554[1][1].Reps[0],
+ coordU, coordV,
+ ASig);
+
+ InterpolateColoursPVRTC<Do2bitMode>(Colours5554[0][0].Reps[1],
+ Colours5554[0][1].Reps[1],
+ Colours5554[1][0].Reps[1],
+ Colours5554[1][1].Reps[1],
+ coordU, coordV,
+ BSig);
+
+ GetModulationValue<Do2bitMode>(x,y, ModulationVals, ModulationModes,
+ &Mod, &DoPT);
+
+
+ // compute the modulated color and store in output image
+ for (int i = 0; i < 4; i++)
+ {
+ int res = (ASig[i] * 8 + Mod * (BSig[i] - ASig[i])) >> 3;
+ pResultImage[i] = (UInt8)res;
+ }
+ if (DoPT)
+ {
+ pResultImage[3] = 0;
+ }
+ pResultImage += 4;
+
+ } // end for x
+ } // end for y
+}
+#endif
+
+
+
+
+// ------------------------------------------------------------------------
+// ETC
+
+
+
+#if HAS_ETC_DECOMPRESSOR
+
+#define GETBITS(source, size, startpos) (( (source) >> ((startpos)-(size)+1) ) & ((1<<(size)) -1))
+#define GETBITSHIGH(source, size, startpos) (( (source) >> (((startpos)-32)-(size)+1) ) & ((1<<(size)) -1))
+inline int clampUByte(int v) { return (v < 0) ? 0 : ((v > 255) ? 255 : v); }
+
+inline void STORE_RGB(UInt32* img, int width, int x, int y, int r, int g, int b)
+{
+ Assert(r >= 0 && r <= 255);
+ Assert(g >= 0 && g <= 255);
+ Assert(b >= 0 && b <= 255);
+ Assert(x >= 0 && x < width);
+ img[y*width + x] = (0xFF<<24) | (r<<0) | (g<<8) | (b<<16);
+}
+
+void decompressBlockDiffFlip(unsigned int block_part1, unsigned int block_part2, UInt32* dstImg, int width, int startx, int starty)
+{
+ static const int unscramble[4] = {2, 3, 1, 0};
+
+ int compressParams[16][4];
+
+ compressParams[0][0] = -8; compressParams[0][1] = -2; compressParams[0][2] = 2; compressParams[0][3] = 8;
+ compressParams[1][0] = -8; compressParams[1][1] = -2; compressParams[1][2] = 2; compressParams[1][3] = 8;
+ compressParams[2][0] = -17; compressParams[2][1] = -5; compressParams[2][2] = 5; compressParams[2][3] = 17;
+ compressParams[3][0] = -17; compressParams[3][1] = -5; compressParams[3][2] = 5; compressParams[3][3] = 17;
+ compressParams[4][0] = -29; compressParams[4][1] = -9; compressParams[4][2] = 9; compressParams[4][3] = 29;
+ compressParams[5][0] = -29; compressParams[5][1] = -9; compressParams[5][2] = 9; compressParams[5][3] = 29;
+ compressParams[6][0] = -42; compressParams[6][1] = -13; compressParams[6][2] = 13; compressParams[6][3] = 42;
+ compressParams[7][0] = -42; compressParams[7][1] = -13; compressParams[7][2] = 13; compressParams[7][3] = 42;
+ compressParams[8][0] = -60; compressParams[8][1] = -18; compressParams[8][2] = 18; compressParams[8][3] = 60;
+ compressParams[9][0] = -60; compressParams[9][1] = -18; compressParams[9][2] = 18; compressParams[9][3] = 60;
+ compressParams[10][0] = -80; compressParams[10][1] = -24; compressParams[10][2] = 24; compressParams[10][3] = 80;
+ compressParams[11][0] = -80; compressParams[11][1] = -24; compressParams[11][2] = 24; compressParams[11][3] = 80;
+ compressParams[12][0] =-106; compressParams[12][1] = -33; compressParams[12][2] = 33; compressParams[12][3] = 106;
+ compressParams[13][0] =-106; compressParams[13][1] = -33; compressParams[13][2] = 33; compressParams[13][3] = 106;
+ compressParams[14][0] =-183; compressParams[14][1] = -47; compressParams[14][2] = 47; compressParams[14][3] = 183;
+ compressParams[15][0] =-183; compressParams[15][1] = -47; compressParams[15][2] = 47; compressParams[15][3] = 183;
+
+ UInt8 avg_color[3], enc_color1[3], enc_color2[3];
+ signed char diff[3];
+ int table;
+ int index,shift;
+ int r,g,b;
+ int diffbit;
+ int flipbit;
+
+ diffbit = (GETBITSHIGH(block_part1, 1, 33));
+ flipbit = (GETBITSHIGH(block_part1, 1, 32));
+
+ if( !diffbit )
+ {
+
+ // We have diffbit = 0.
+
+ // First decode left part of block.
+ avg_color[0]= GETBITSHIGH(block_part1, 4, 63);
+ avg_color[1]= GETBITSHIGH(block_part1, 4, 55);
+ avg_color[2]= GETBITSHIGH(block_part1, 4, 47);
+
+ // Here, we should really multiply by 17 instead of 16. This can
+ // be done by just copying the four lower bits to the upper ones
+ // while keeping the lower bits.
+ avg_color[0] |= (avg_color[0] <<4);
+ avg_color[1] |= (avg_color[1] <<4);
+ avg_color[2] |= (avg_color[2] <<4);
+
+ table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+
+ unsigned int pixel_indices_MSB, pixel_indices_LSB;
+
+ pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+ pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+ if( (flipbit) == 0 )
+ {
+ // We should not flip
+ shift = 0;
+ for(int x=startx; x<startx+2; x++)
+ {
+ for(int y=starty; y<starty+4; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ }
+ }
+ else
+ {
+ // We should flip
+ shift = 0;
+ for(int x=startx; x<startx+4; x++)
+ {
+ for(int y=starty; y<starty+2; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ shift+=2;
+ }
+ }
+
+ // Now decode other part of block.
+ avg_color[0]= GETBITSHIGH(block_part1, 4, 59);
+ avg_color[1]= GETBITSHIGH(block_part1, 4, 51);
+ avg_color[2]= GETBITSHIGH(block_part1, 4, 43);
+
+ // Here, we should really multiply by 17 instead of 16. This can
+ // be done by just copying the four lower bits to the upper ones
+ // while keeping the lower bits.
+ avg_color[0] |= (avg_color[0] <<4);
+ avg_color[1] |= (avg_color[1] <<4);
+ avg_color[2] |= (avg_color[2] <<4);
+
+ table = GETBITSHIGH(block_part1, 3, 36) << 1;
+ pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+ pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+ if( (flipbit) == 0 )
+ {
+ // We should not flip
+ shift=8;
+ for(int x=startx+2; x<startx+4; x++)
+ {
+ for(int y=starty; y<starty+4; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ }
+ }
+ else
+ {
+ // We should flip
+ shift=2;
+ for(int x=startx; x<startx+4; x++)
+ {
+ for(int y=starty+2; y<starty+4; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ shift += 2;
+ }
+ }
+
+ }
+ else
+ {
+ // We have diffbit = 1.
+
+ // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32
+ // ---------------------------------------------------------------------------------------------------
+ // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip|
+ // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit |
+ // ---------------------------------------------------------------------------------------------------
+ //
+ //
+ // c) bit layout in bits 31 through 0 (in both cases)
+ //
+ // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+ // --------------------------------------------------------------------------------------------------
+ // | most significant pixel index bits | least significant pixel index bits |
+ // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a |
+ // --------------------------------------------------------------------------------------------------
+
+
+ // First decode left part of block.
+ enc_color1[0]= GETBITSHIGH(block_part1, 5, 63);
+ enc_color1[1]= GETBITSHIGH(block_part1, 5, 55);
+ enc_color1[2]= GETBITSHIGH(block_part1, 5, 47);
+
+
+ // Expand from 5 to 8 bits
+ avg_color[0] = (enc_color1[0] <<3) | (enc_color1[0] >> 2);
+ avg_color[1] = (enc_color1[1] <<3) | (enc_color1[1] >> 2);
+ avg_color[2] = (enc_color1[2] <<3) | (enc_color1[2] >> 2);
+
+
+ table = GETBITSHIGH(block_part1, 3, 39) << 1;
+
+ unsigned int pixel_indices_MSB, pixel_indices_LSB;
+
+ pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+ pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+ if( (flipbit) == 0 )
+ {
+ // We should not flip
+ shift = 0;
+ for(int x=startx; x<startx+2; x++)
+ {
+ for(int y=starty; y<starty+4; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ }
+ }
+ else
+ {
+ // We should flip
+ shift = 0;
+ for(int x=startx; x<startx+4; x++)
+ {
+ for(int y=starty; y<starty+2; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ shift+=2;
+ }
+ }
+
+
+ // Now decode right part of block.
+
+
+ diff[0]= GETBITSHIGH(block_part1, 3, 58);
+ diff[1]= GETBITSHIGH(block_part1, 3, 50);
+ diff[2]= GETBITSHIGH(block_part1, 3, 42);
+
+ enc_color2[0]= enc_color1[0] + diff[0];
+ enc_color2[1]= enc_color1[1] + diff[1];
+ enc_color2[2]= enc_color1[2] + diff[2];
+
+ // Extend sign bit to entire byte.
+ diff[0] = (diff[0] << 5);
+ diff[1] = (diff[1] << 5);
+ diff[2] = (diff[2] << 5);
+ diff[0] = diff[0] >> 5;
+ diff[1] = diff[1] >> 5;
+ diff[2] = diff[2] >> 5;
+
+ // Calculale second color
+ enc_color2[0]= enc_color1[0] + diff[0];
+ enc_color2[1]= enc_color1[1] + diff[1];
+ enc_color2[2]= enc_color1[2] + diff[2];
+
+ // Expand from 5 to 8 bits
+ avg_color[0] = (enc_color2[0] <<3) | (enc_color2[0] >> 2);
+ avg_color[1] = (enc_color2[1] <<3) | (enc_color2[1] >> 2);
+ avg_color[2] = (enc_color2[2] <<3) | (enc_color2[2] >> 2);
+
+
+ table = GETBITSHIGH(block_part1, 3, 36) << 1;
+ pixel_indices_MSB = GETBITS(block_part2, 16, 31);
+ pixel_indices_LSB = GETBITS(block_part2, 16, 15);
+
+ if( (flipbit) == 0 )
+ {
+ // We should not flip
+ shift=8;
+ for(int x=startx+2; x<startx+4; x++)
+ {
+ for(int y=starty; y<starty+4; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ }
+ }
+ else
+ {
+ // We should flip
+ shift=2;
+ for(int x=startx; x<startx+4; x++)
+ {
+ for(int y=starty+2; y<starty+4; y++)
+ {
+ index = ((pixel_indices_MSB >> shift) & 1) << 1;
+ index |= ((pixel_indices_LSB >> shift) & 1);
+ shift++;
+ index=unscramble[index];
+
+ r=clampUByte(avg_color[0]+compressParams[table][index]);
+ g=clampUByte(avg_color[1]+compressParams[table][index]);
+ b=clampUByte(avg_color[2]+compressParams[table][index]);
+ STORE_RGB(dstImg, width, x, y, r, g, b);
+ }
+ shift += 2;
+ }
+ }
+ }
+}
+
+
+static void DecompressETC_RGB4 ( int xblocks, int yblocks, int destWidth, const UInt32* m_pCompBytes, UInt32* m_pDecompBytes )
+{
+ const UInt8* srcPtr = (const UInt8*)m_pCompBytes;
+ UInt32* dstPtr = m_pDecompBytes;
+
+ for (int y = 0; y < yblocks; y++)
+ {
+ for (int x = 0; x < xblocks; x++)
+ {
+ UInt32 part1 = (srcPtr[0]<<24) | (srcPtr[1]<<16) | (srcPtr[2]<<8) | (srcPtr[3]<<0);
+ UInt32 part2 = (srcPtr[4]<<24) | (srcPtr[5]<<16) | (srcPtr[6]<<8) | (srcPtr[7]<<0);
+ decompressBlockDiffFlip(part1, part2, dstPtr, destWidth, x*4, y*4);
+ srcPtr += 8;
+ }
+ }
+}
+
+#endif // HAS_ETC_DECOMPRESSOR
+
+
+#if HAS_ATC_DECOMPRESSOR
+
+template<int textureFormat>
+static void DecompressATC ( int xblocks, int yblocks, int destWidth, const UInt32* m_pCompBytes, UInt32* m_pDecompBytes )
+{
+ bool doAlpha = (textureFormat == kTexFormatATC_RGBA8);
+
+ TQonvertImage src, dst;
+ src.nWidth = xblocks * 4;
+ src.nHeight = yblocks * 4;
+ src.nFormat = doAlpha ? Q_FORMAT_ATC_RGBA_INTERPOLATED_ALPHA : Q_FORMAT_ATC_RGB;
+ src.pFormatFlags = NULL;
+ src.nDataSize = CalculateImageSize (src.nWidth, src.nHeight, textureFormat);
+ src.pData = (unsigned char*)m_pCompBytes;
+
+ dst.nWidth = destWidth;
+ dst.nHeight = yblocks * 4;
+ dst.nFormat = Q_FORMAT_RGBA_8888;
+ dst.pFormatFlags = NULL;
+ dst.nDataSize = CalculateImageSize (dst.nWidth, dst.nHeight, kTexFormatRGBA32);
+ dst.pData = (unsigned char*)m_pDecompBytes;
+
+ int nRet = Qonvert(&src, &dst);
+ if (nRet != Q_SUCCESS)
+ {
+ ErrorStringMsg("DecompressETC2 failed with nRet = %i", nRet);
+ }
+}
+
+#endif // HAS_ATC_DECOMPRESSOR
+
+#if HAS_ASTC_DECOMPRESSOR
+
+// ASTC decoding
+
+#include "External/astcenc/Source/astc_codec_internals.h"
+
+// Needed by astcenc
+void astc_codec_internal_error(const char *filename, int linenum)
+{
+ printf_console("ASTCenc: Internal error: File=%s Line=%d\n", filename, linenum);
+}
+// Define an unlink() function in terms of the Unity DeleteFile function.
+int astc_codec_unlink(const char *filename)
+{
+/* bool res = DeleteFile(filename);
+ return (res ? 0 : -1);*/
+
+ // Not used in runtime
+ return -1;
+}
+int rgb_force_use_of_hdr = 0;
+int alpha_force_use_of_hdr = 0;
+int perform_srgb_transform = 0;
+int print_tile_errors = 0;
+
+// TODO: Make threadsafe when we have multithreaded renderer. Doesn't really hurt to do multiple inits though.
+static int astcenc_initialized = 0;
+
+static void DecompressASTC(const UInt32 *srcData, int destWidth, int destHeight, UInt32 *destData, int blockWidth, int blockHeight)
+{
+ swizzlepattern swz_decode = { 0, 1, 2, 3 };
+
+ if(!astcenc_initialized)
+ {
+ // initialization routines
+ prepare_angular_tables();
+ build_quantization_mode_table();
+ astcenc_initialized = 1;
+ }
+
+
+ astc_codec_image *img = allocate_image(8, destWidth, destHeight, 1, 0);
+ initialize_image(img);
+
+ const int zblocks = 1;
+ const int yblocks = CeilfToInt((float)destHeight / (float)blockHeight);
+ const int xblocks = CeilfToInt((float)destWidth / (float)blockWidth);
+
+ const int xdim = blockWidth;
+ const int ydim = blockHeight;
+ const int zdim = 1;
+
+ int x, y, z;
+
+ imageblock pb;
+ for (z = 0; z < zblocks; z++)
+ for (y = 0; y < yblocks; y++)
+ for (x = 0; x < xblocks; x++)
+ {
+ int offset = (((z * yblocks + y) * xblocks) + x) * 16;
+ uint8_t *bp = ((uint8_t *)srcData) + offset;
+ physical_compressed_block pcb = *(physical_compressed_block *) bp;
+ symbolic_compressed_block scb;
+ physical_to_symbolic(xdim, ydim, zdim, pcb, &scb);
+ decompress_symbolic_block(DECODE_LDR, xdim, ydim, zdim, x * xdim, y * ydim, z * zdim, &scb, &pb);
+ write_imageblock(img, &pb, xdim, ydim, zdim, x * xdim, y * ydim, z * zdim, swz_decode);
+ }
+
+ for(y = 0; y < destHeight; y++)
+ {
+ memcpy(&destData[y*destWidth], img->imagedata8[0][y], destWidth * 4);
+ }
+ destroy_image(img);
+
+}
+
+#endif // HAS_ASTC_DECOMPRESSOR
+
+// ------------------------------------------------------------------------
+// Common
+
+
+bool DecompressNativeTextureFormatWithMipLevel( TextureFormat srcFormat, int srcWidth, int srcHeight, int mipLevel, const UInt32* sourceData,
+ int destWidth, int destHeight, UInt32* destData )
+{
+
+#if HAS_FLASH_ATF_DECOMPRESSOR
+ // Flash requires that we pass the mipLevel explicitly
+ if (IsCompressedFlashATFTextureFormat(srcFormat))
+ {
+ DecompressFlashATFTexture((const UInt8*)sourceData, destWidth, destHeight, mipLevel, (UInt8*)destData);
+ return true;
+ }
+#endif
+
+ return DecompressNativeTextureFormat (srcFormat, srcWidth, srcHeight, (UInt32*)sourceData, destWidth, destHeight, destData);
+}
+
+
+
+bool DecompressNativeTextureFormat( TextureFormat srcFormat, int srcWidth, int srcHeight, const UInt32* sourceData,
+ int destWidth, int destHeight, UInt32* destData )
+{
+ Assert( IsAnyCompressedTextureFormat(srcFormat) );
+ Assert( destWidth >= srcWidth && destHeight >= srcHeight );
+ Assert( destWidth % 4 == 0 && destHeight % 4 == 0 );
+
+#if UNITY_XENON || HAS_DXT_DECOMPRESSOR || HAS_ETC_DECOMPRESSOR || HAS_ATC_DECOMPRESSOR
+ int xblocks = (srcWidth + 3) / 4;
+ int yblocks = (srcHeight + 3) / 4;
+#endif
+
+ const UInt32* srcData = sourceData;
+
+ #if UNITY_XENON
+ UInt32 dataSize = (xblocks * yblocks * ((kTexFormatDXT1 == srcFormat) ? 8 : 16)) >> 1;
+ UInt16 *s = (UInt16*)srcData;
+ UInt16 *d = new UInt16[dataSize];
+ for(int i=0;i<dataSize;i++)
+ d[i]=GetByteSwap16(s[i]);
+ srcData = (UInt32*)d;
+ #endif
+
+ #if PRINT_DECOMPRESSION_TIMES
+ double t0 = GetTimeSinceStartup();
+ #endif
+
+
+ switch( srcFormat )
+ {
+ #if HAS_DXT_DECOMPRESSOR
+ case kTexFormatDXT1: DecompressDXT1( xblocks, yblocks, destWidth, srcData, destData ); break;
+ case kTexFormatDXT3: DecompressDXT3( xblocks, yblocks, destWidth, srcData, destData ); break;
+ case kTexFormatDXT5: DecompressDXT5( xblocks, yblocks, destWidth, srcData, destData ); break;
+ #endif
+
+ #if HAS_ETC_DECOMPRESSOR
+ case kTexFormatETC_RGB4: DecompressETC_RGB4(xblocks, yblocks, destWidth, srcData, destData ); break;
+ #endif
+
+ #if HAS_ATC_DECOMPRESSOR
+ case kTexFormatATC_RGB4: DecompressATC<kTexFormatATC_RGB4>(xblocks, yblocks, destWidth, srcData, destData); break;
+ case kTexFormatATC_RGBA8: DecompressATC<kTexFormatATC_RGBA8>(xblocks, yblocks, destWidth, srcData, destData); break;
+ #endif
+
+ case kTexFormatETC2_RGB: DecompressETC2_RGB8 ((UInt8*)destData, (const UInt8*)srcData, destWidth, destHeight); break;
+ case kTexFormatETC2_RGBA1: DecompressETC2_RGB8_A1 ((UInt8*)destData, (const UInt8*)srcData, destWidth, destHeight); break;
+ case kTexFormatETC2_RGBA8: DecompressETC2_RGBA8 ((UInt8*)destData, (const UInt8*)srcData, destWidth, destHeight); break;
+
+ #if HAS_PVRTC_DECOMPRESSOR
+ case kTexFormatPVRTC_RGB4:
+ case kTexFormatPVRTC_RGBA4: DecompressPVRTC<false,true>( reinterpret_cast<const PVRTCBlock*>(srcData), srcWidth, srcHeight, reinterpret_cast<unsigned char*> (destData) ); break;
+
+ case kTexFormatPVRTC_RGB2:
+ case kTexFormatPVRTC_RGBA2: DecompressPVRTC<true,true>( reinterpret_cast<const PVRTCBlock*>(srcData), srcWidth, srcHeight, reinterpret_cast<unsigned char*> (destData) ); break;
+ #endif
+
+ #if HAS_FLASH_ATF_DECOMPRESSOR
+ case kTexFormatFlashATF_RGB_DXT1:
+ case kTexFormatFlashATF_RGB_JPG:
+ case kTexFormatFlashATF_RGBA_JPG:
+ DecompressFlashATFTexture ((const UInt8*)srcData, destWidth, destHeight, 0, (UInt8*)destData);
+ break;
+ #endif
+
+#if HAS_ASTC_DECOMPRESSOR
+#define DO_ASTC(bx,by) case kTexFormatASTC_RGB_##bx##x##by : case kTexFormatASTC_RGBA_##bx##x##by : DecompressASTC(srcData, destWidth, destHeight, destData, bx, by); break
+
+ DO_ASTC(4, 4);
+ DO_ASTC(5, 5);
+ DO_ASTC(6, 6);
+ DO_ASTC(8, 8);
+ DO_ASTC(10, 10);
+ DO_ASTC(12, 12);
+
+#undef DO_ASTC
+#endif
+
+ default:
+ AssertString( "Unknown compressed texture format!" );
+ #if UNITY_XENON
+ delete[] srcData;
+ #endif
+ return false;
+ }
+
+#if UNITY_XENON
+ delete[] srcData;
+#endif
+
+ #if PRINT_DECOMPRESSION_TIMES
+ double t1 = GetTimeSinceStartup();
+ if (srcWidth >= 512 && srcHeight >= 512)
+ {
+ printf_console("Decompress %x size %ix%i fmt %i time %.3fs\n", srcData, srcWidth, srcHeight, (int)srcFormat, t1-t0);
+ }
+ #endif
+
+ return true;
+}
+
+
+
+// ------------------------------------------------------------------------
+// Unit Tests
+
+
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+
+SUITE (ImageDecompressionTests)
+{
+ #if UNITY_LITTLE_ENDIAN
+ TEST (DecodeDXT5AlphaPalette8a)
+ {
+ UInt32 res[16];
+ memset (res, 0xCC, sizeof(res));
+ UInt8 b[] = { 17, 13, 177, 109, 155, 54, 105, 82, 255 };
+ DecodeAlpha3BitLinear (res, *(DXTAlphaBlock3BitLinear*)b, 4, 0x00FFFFFF);
+ UInt8 r[] = { 13,14,14,14, 14,14,14,15, 14,14,15,15, 14,15,15,16 };
+ for (int i = 0; i < 16; ++i) {
+ CHECK_EQUAL ((int)r[i], (res[i]&0xFF000000)>>24);
+ }
+ }
+ TEST (DecodeDXT5AlphaPalette8b)
+ {
+ UInt32 res[16];
+ memset (res, 0xCD, sizeof(res));
+ UInt8 b[] = { 251, 5, 179, 109, 113, 54, 107, 84, 255 };
+ DecodeAlpha3BitLinear (res, *(DXTAlphaBlock3BitLinear*)b, 4, 0x00FFFFFF);
+ UInt8 r[] = { 181,75,75,75, 75,216,146,181, 75,75,146,110, 75,251,110,216 };
+ for (int i = 0; i < 16; ++i) {
+ CHECK_EQUAL ((int)r[i], (res[i]&0xFF000000)>>24);
+ }
+ }
+ TEST (DecodeDXT5AlphaPalette6)
+ {
+ UInt32 res[16];
+ memset (res, 0xCC, sizeof(res));
+ UInt8 b[] = { 15, 18, 0, 4, 72, 144, 8, 137, 255 };
+ DecodeAlpha3BitLinear (res, *(DXTAlphaBlock3BitLinear*)b, 4, 0x00FFFFFF);
+ UInt8 r[] = { 15,15,15,16, 15,15,16,16, 15,16,16,17, 15,16,16,17 };
+ for (int i = 0; i < 16; ++i) {
+ CHECK_EQUAL ((int)r[i], (res[i]&0xFF000000)>>24);
+ }
+ }
+ #if HAS_PVRTC_DECOMPRESSOR
+ TEST (DecodePVRTC_2_16x16)
+ {
+ const int kSize = 16;
+ UInt32 src[kSize*kSize*2/32] = {
+ 0xeeeeeeee, 0x83eed400, 0xeeeeeeee, 0x8befb006, 0xfefefefe, 0xeed9801e, 0x00fefefe, 0xcf18801e,
+ 0x0eeeeeee, 0x9ff4fc00, 0x00fefefe, 0xaff58000, 0x00ffffff, 0x83add404, 0x00ffffff, 0xb77d8000,
+ };
+ UInt32 expected[kSize*kSize] = {
+ 0xff3f002b,0xffadeb59,0xffa3f24b,0xff99f83b,0xff000056,0xff99f83b,0xffa3f24b,0xffadeb59,0xff3f002b,0xffc0dd77,0xffcbd787,0xffd4d095,0xffdecaa5,0xffd4d095,0xffcbd787,0xffc0dd77,
+ 0xff5f0040,0xffa0e756,0xff96ef40,0xff8cf72b,0xff000081,0xff8cf72b,0xff96ef40,0xffa0e756,0xff5f0040,0xffb6d780,0xffc0cf96,0xffcbc7ac,0xffd6bfc1,0xffcbc7ac,0xffc0cf96,0xffb6d780,
+ 0xff7f0056,0xff95e353,0xff8aed37,0xff7ef61b,0xff0000ad,0xff7ef61b,0xff8aed37,0xff95e353,0xff7f0056,0xffacd18b,0xffb7c8a7,0xffc2bec2,0xffceb5de,0xffc2bec2,0xffb7c8a7,0xffacd18b,
+ 0xff86004d,0xff95e44f,0xff8bee36,0xff7ff61c,0xff0c009a,0xff7ff61c,0xff8bee36,0xff95e44f,0xff86004d,0xffabd381,0xffb6cb9b,0xffc0c1b4,0xffccb9ce,0xffc0c1b4,0xffb6cb9b,0xffabd381,
+ 0xff8c0044,0xff96e74c,0xff8cef35,0xff81f71e,0xff180088,0xff81f71e,0xff8cef35,0xff96e74c,0xff8c0044,0xffabd679,0xffb5ce90,0xffbfc6a7,0xffcabdbd,0xffbfc6a7,0xffb5ce90,0xffabd679,
+ 0xff92003a,0xff96e848,0xff8df034,0xff82f71f,0xff250075,0xff82f71f,0xff8df034,0xff96e848,0xff92003a,0xffaad870,0xffb4d185,0xffbdc998,0xffc8c1ad,0xffbdc998,0xffb4d185,0xffaad870,
+ 0xff980031,0xff97ea45,0xff8ef133,0xff85f822,0xff310063,0xff85f822,0xff8ef133,0xff97ea45,0xff980031,0xffaadb68,0xffb3d479,0xffbccd8b,0xffc6c69c,0xffbccd8b,0xffb3d479,0xffaadb68,
+ 0xff76005a,0xff95ed3c,0xff90f331,0xff8bf926,0xff25008a,0xff8bf926,0xff90f331,0xff95ed3c,0xff76005a,0xff8a004e,0xff9e0043,0xffb30036,0xffc8002b,0xffb30036,0xff9e0043,0xff8a004e,
+ 0xff540084,0xff93f134,0xff92f62f,0xff91fa2a,0xff1800b1,0xff91fa2a,0xff92f62f,0xff93f134,0xff94ed39,0xff95e83e,0xff96e344,0xff97de49,0xff98da4e,0xff97de49,0xff96e344,0xff95e83e,
+ 0xff3200ad,0xff91f52c,0xff94f82d,0xff97fb2e,0xff0c00d8,0xff97fb2e,0xff94f82d,0xff91f52c,0xff8ef22b,0xff8bee2a,0xff88eb29,0xff85e828,0xff81e427,0xff85e828,0xff88eb29,0xff8bee2a,
+ 0xff1000d6,0xff8ff924,0xff96fb2b,0xff9dfd32,0xff0000ff,0xff9dfd32,0xff96fb2b,0xff8ff924,0xff88f71c,0xff80f515,0xff79f30e,0xff72f107,0xff6bef00,0xff72f107,0xff79f30e,0xff80f515,
+ 0xff0c00a0,0xff9cf732,0xff9ffa37,0xffa2fc3c,0xff0000bf,0xff0300b7,0xff0600b0,0xff0900a8,0xff0c00a0,0xff0f0098,0xff120091,0xff150089,0xff180081,0xff150089,0xff120091,0xff0f0098,
+ 0xff08006b,0xffaaf642,0xffaaf945,0xffa9fc47,0xffa9ff4a,0xffa9fc47,0xffaaf945,0xffaaf642,0xffabf33f,0xffabf03c,0xffaced3a,0xffacea37,0xffade735,0xffacea37,0xffaced3a,0xffabf03c,
+ 0xff040035,0xffb7f451,0xffb3f851,0xffaffb51,0xffabff52,0xffaffb51,0xffb3f851,0xffb7f451,0xffbcf151,0xffc0ed50,0xffc4ea50,0xffc9e550,0xffcee250,0xffc9e550,0xffc4ea50,0xffc0ed50,
+ 0xff000000,0xffc6f360,0xffbdf75e,0xffb5fb5c,0xffadff5a,0xffb5fb5c,0xffbdf75e,0xffc6f360,0xffceef63,0xffd6eb65,0xffdee767,0xffe7e269,0xffefde6b,0xffe7e269,0xffdee767,0xffd6eb65,
+ 0xff1f0015,0xff17001a,0xff0f001f,0xff070025,0xff00002b,0xff070025,0xff0f001f,0xff17001a,0xff1f0015,0xff27000f,0xff2f000a,0xff370005,0xff3f0000,0xff370005,0xff2f000a,0xff27000f,
+ };
+ UInt32 res[kSize*kSize];
+ DecompressPVRTC<true,true> (reinterpret_cast<const PVRTCBlock*>(src), kSize, kSize, reinterpret_cast<UInt8*>(res));
+ CHECK_ARRAY_EQUAL (expected, res, kSize*kSize);
+ }
+ TEST (DecodePVRTC_4_16x16)
+ {
+ const int kSize = 16;
+ UInt32 src[kSize*kSize*4/32] = {
+ 0x32323230,0x7faa30e7,0x32323232,0x7fbc40f9,0x03030303,0x050230f6,0x03030303,0x060330f4,
+ 0x32323232,0x7faa40f7,0xa802f232,0xffff30e7,0xff030303,0x0f0040e6,0xaa00ff00,0xff9f40e9,
+ 0x0303035b,0x300f6aca,0xff030303,0x300f68ca,0x409094aa,0x68af5bba,0xff000040,0x200f58ca,
+ 0xff000000,0x2c0140e6,0xaa00ff00,0xffff41db,0xff000000,0x1c0140e8,0xaa00ff00,0xffff40bb,
+ };
+ UInt32 expected[kSize*kSize] = {
+ 0x7f92d62f,0x727ee217,0xf6d2d6ff,0x6a70f100,0xbbb1a5d4,0x727af500,0x777ff700,0x838eef17,0x99c67994,0xa2cca070,0xadd1b56a,0xaed1b971,0xbbe2b688,0xc1e2c193,0xc8e2cc9f,0xc6d4d19f,
+ 0x00b6cb8d,0x747ee223,0xf2bbc1ff,0x686df200,0x998a7dbe,0x6c72f800,0x6e75fb00,0x8187f121,0x66a93c5e,0xa7aadc64,0xbbbbd286,0xb4bbcd88,0xaebbc88a,0xb2d2b58c,0xb5d2be98,0xc2d0c49e,
+ 0x00b1c192,0x777fe22f,0xeea5adff,0x666bf300,0x776356a9,0x666bfb00,0x666bff00,0x7f7ff32b,0x338c0029,0xb2a9da81,0xccbdcead,0xc3bdcab1,0xbbbdc6b5,0xb2bdc1b9,0xb6d2b7aa,0xbdccb79e,
+ 0x00b6b986,0x7d89e72c,0xeeadb1ff,0x6c72f600,0x776958ab,0x6868fc00,0x6663ff00,0x7f79f329,0x338e002b,0xb2a7da7b,0xccbdcea5,0xc3bdcba8,0xbbbdc8ab,0xb2bdc4ae,0xaabdc1b1,0xa7c1b588,
+ 0x00bbb27a,0x8392eb29,0xeeb5b5ff,0x7279f900,0x776f5aad,0x6a65fd00,0x665aff00,0x7f73f327,0x3390002d,0xb2a5da75,0xccbdce9c,0xc3bdcc9e,0xbbbdcaa0,0xb2bdc8a2,0xaabdc6a5,0x9ec4ac78,
+ 0x00c0aa6e,0x8a9bef26,0xeebdb9ff,0x7980fc00,0x77755caf,0x6c61fe00,0x6652ff00,0x7f6df325,0x3392002f,0xb2a2da6f,0xccbdce94,0xc3bdcd95,0xbbbdcc96,0xb2bdcb97,0xaabdca98,0x9fb2d672,
+ 0x00c5a262,0x90a5f323,0xeec6bdff,0x7f88ff00,0x777b5eb1,0x6e5eff00,0x664aff00,0x7f67f323,0x33940031,0xb2a0da69,0xccbdce8c,0xc3bdce8c,0xbbbdce8c,0xb2bdce8c,0xaabdce8c,0xa1b5da69,
+ 0x00afa366,0x8e99f519,0xeebdb9ff,0x8180fe00,0x77715cc4,0x7461fc00,0x6e52fb00,0x8168f219,0x2e71005e,0x46970049,0x5dbd0033,0x55bd0033,0x4cbd0033,0x44bd0033,0x3bbd0033,0x68bd2e66,
+ 0x009aa46b,0x8c8ef711,0xeeb5b5ff,0x8379fd00,0x77675ad8,0x7b65f900,0x775af700,0x8369f111,0x9077eb23,0x9d86e434,0xaa94de46,0xa59ade46,0xa1a0de46,0x9da7de46,0x99adde46,0x94a2e734,
+ 0x0084a56f,0x8a82f908,0xeeadb1ff,0x8572fc00,0x775c58ec,0x8168f600,0x7f63f300,0x856af008,0x8c71ed11,0x9278ea19,0x997fe723,0x9689e723,0x9492e723,0x929be723,0x90a5e723,0x8e99ed19,
+ 0x006fa673,0x8877fb00,0xeea5adff,0x886bfb00,0x775256ff,0x886bf300,0x886bef00,0x886bef00,0x886bef00,0x886bef00,0x886bef00,0x8877ef00,0x8884ef00,0x8890ef00,0x889cef00,0x8890f300,
+ 0x0085b776,0x8178f500,0xf2bbc1ff,0x816df800,0x997d7dff,0x6c5e5bff,0x3f3f39ff,0x4c433af6,0x59463ced,0x66493de3,0x724c3fda,0x6c4c3fda,0x664c3fda,0x5f4c3fda,0x594c3fda,0x7f685fe3,
+ 0x009bc979,0x7b7aef00,0xf6d2d6ff,0x7b70f500,0x7f75f300,0x837af100,0x887fef00,0x8884ed02,0x8888eb04,0x888ce906,0x8890e708,0x8896e206,0x889cde04,0x88a2da02,0x88a9d600,0x8399de00,
+ 0x00b1d97c,0x747be900,0xfae9ebff,0xebdedbff,0xddd4ccff,0xcecabcff,0xbfbfadff,0xc3c0b1fc,0xc7c1b6f9,0xccc2baf6,0xd0c3bff3,0xcec3bff3,0xccc3bff3,0xc9c3bff3,0xc7c3bff3,0xd4cdcaf6,
+ 0x00c7ea7f,0x6e7de200,0x666bef00,0x6e75ef00,0x777fef00,0x7f8aef00,0x8894ef00,0x889ceb04,0x88a5e708,0x88ade20c,0x88b5de10,0x88b5d60c,0x88b5ce08,0x88b5c604,0x88b5bd00,0x7fa2ca00,
+ 0x7b91d617,0x00b5e681,0x00aaed7f,0x00a9e57a,0x00a9de75,0x00a8d76f,0x00a9d06a,0x00b5cf6e,0x00c1ce72,0x00cecd76,0x00dacc7b,0x00dace7e,0x00dad082,0x00dad185,0x00dad488,0x00cdda86,
+ };
+ UInt32 res[kSize*kSize];
+ DecompressPVRTC<false,true> (reinterpret_cast<const PVRTCBlock*>(src), kSize, kSize, reinterpret_cast<UInt8*>(res));
+ CHECK_ARRAY_EQUAL (expected, res, kSize*kSize);
+ }
+ TEST (DecodePVRTC_4_8x8)
+ {
+ const int kSize = 8;
+ UInt32 src[kSize*kSize*4/32] = {
+ 0x4c4c4c4c,0x63fb3494,0x00fc4c4c,0x68fc2352,0xa9fefefe,0x5bac1078,0x00ff5555,0x68dc2072,
+ };
+ UInt32 expected[kSize*kSize] = {
+ 0x444a751c,0xc7bff170,0x55357739,0x7a6fa444,0x9394b95b,0xbfc3d493,0xbbc6c6a5,0xbfc3d493,0x445e7e1e,0xc5bcee61,0x5d3f8a3d,0x7b77ab41,0x909ab858,0xb8c2cb96,0xb2c6b9b1,0xb8c2cb96,
+ 0x44738821,0xc3b9eb54,0x664a9c42,0x7e80b33e,0x8ea1b856,0xb2c1c19a,0xaac6adbd,0xb2c1c19a,0x445e7e1e,0xc5bcee61,0x5d3f8a3d,0x7b77ab41,0x7282a141,0x87a2ab63,0x7faa9e6e,0x87a2ab63,
+ 0x444a751c,0xc7bff170,0x55357739,0x7a6fa444,0x73769d41,0x6c7d983f,0x6685923d,0x6c7d983f,0x44356c1a,0xc9c2f47d,0x4c2b6535,0x78669c47,0x756a9a42,0x716d983d,0x6e719639,0x716d983d,
+ 0x44216318,0xccc6f78c,0xccc6ff8c,0xccc6f78c,0xccc6ef8c,0xccc6e78c,0xccc6de8c,0xccc6e78c,0x44356c1a,0x48306828,0x4c2b6535,0x48306828,0x44356c1a,0x3f3a6f0d,0x3b3f7300,0x3f3a6f0d,
+ };
+ UInt32 res[kSize*kSize];
+ DecompressPVRTC<false,true> (reinterpret_cast<const PVRTCBlock*>(src), kSize, kSize, reinterpret_cast<UInt8*>(res));
+ CHECK_ARRAY_EQUAL (expected, res, kSize*kSize);
+ }
+ TEST (DecodePVRTC_4_8x16)
+ {
+ const int kSizeX = 8;
+ const int kSizeY = 16;
+ UInt32 src[kSizeX*kSizeY*4/32] = {
+ 0x4c4c4c4c,0x63fb3494,0x00fc4c4c,0x68fc2352,0xa9fefefe,0x5bac1078,0x00ff5555,0x68dc2072,
+ 0x32323232,0x7faa40f7,0xa802f232,0xffff30e7,0xff030303,0x0f0040e6,0xaa00ff00,0xff9f40e9,
+ };
+ UInt32 expected[kSizeX*kSizeY] = {
+ 0x5d79bb10,0xe1dcf2aa,0x665ac621,0x9194d24e,0xadb8d47a,0xd8e0d7cd,0xd4e2cade,0xd8e0d7cd,0x5076a118,0xd2cbee7e,0x6652b131,0x878ac246,0x9dadc668,0xc5d1ccb3,0xbfd4bbce,0xc5d1ccb3,
+ 0x44738821,0xc3b9eb54,0x664a9c42,0x7e80b33e,0x8ea1b856,0xb2c1c19a,0xaac6adbd,0xb2c1c19a,0x445e7e1e,0xc5bcee61,0x5d3f8a3d,0x7b77ab41,0x7282a141,0x87a2ab63,0x7faa9e6e,0x87a2ab63,
+ 0x444a751c,0xc7bff170,0x55357739,0x7a6fa444,0x73769d41,0x6c7d983f,0x6685923d,0x6c7d983f,0x44356c1a,0xc9c2f47d,0x4c2b6535,0x78669c47,0x756a9a42,0x716d983d,0x6e719639,0x716d983d,
+ 0x44216318,0xccc6f78c,0xccc6ff8c,0xccc6f78c,0xccc6ef8c,0xccc6e78c,0xccc6de8c,0xccc6e78c,0x55338812,0x5533821b,0x55337d25,0x5533821b,0x55338812,0x55338d09,0x55339200,0x55338d09,
+ 0x0069a769,0x6646ab12,0xddb5d6c6,0x6646ab12,0xa18ca2c6,0x6646af06,0x6646b100,0x6646af06,0x0063a774,0x7758d309,0xe5adc1e2,0x7758d309,0x8c6f7ce2,0x7758d103,0x7758d000,0x7758d103,
+ 0x005ea67f,0x886bfb00,0xeea5adff,0x886bfb00,0x775256ff,0x886bf300,0x886bef00,0x886bf300,0x0076b97f,0x816df800,0xf2bbc1ff,0x816df800,0x997d7dff,0x6c5e5bff,0x3f3f39ff,0x6c5e5bff,
+ 0x008fcc7f,0x7b70f500,0xf6d2d6ff,0x7b70f500,0x7f75f300,0x837af100,0x887fef00,0x837af100,0x00a7de7f,0x7472f200,0xfae9ebff,0xebdedbff,0xddd4ccff,0xcecabcff,0xbfbfadff,0xcecabcff,
+ 0x00bff17f,0x6e75ef00,0x666bef00,0x6e75ef00,0x777fef00,0x7f8aef00,0x8894ef00,0x7f8aef00,0x6a7cd508,0x00aee670,0x00a8ec6e,0x00aee670,0x00b5e072,0x00bcd974,0x00c3d477,0x00bcd974,
+ };
+ UInt32 res[kSizeX*kSizeY];
+ DecompressPVRTC<false,true> (reinterpret_cast<const PVRTCBlock*>(src), kSizeX, kSizeY, reinterpret_cast<UInt8*>(res));
+ CHECK_ARRAY_EQUAL (expected, res, kSizeX*kSizeY);
+ }
+ TEST (DecodePVRTC_4_16x8)
+ {
+ const int kSizeX = 16;
+ const int kSizeY = 8;
+ UInt32 src[kSizeX*kSizeY*4/32] = {
+ 0x4c4c4c4c,0x63fb3494,0x00fc4c4c,0x68fc2352,0xa9fefefe,0x5bac1078,0x00ff5555,0x68dc2072,
+ 0x32323232,0x7faa40f7,0xa802f232,0xffff30e7,0xff030303,0x0f0040e6,0xaa00ff00,0xff9f40e9,
+ };
+ UInt32 expected[kSizeX*kSizeY] = {
+ 0x6e5ab31c,0xb8aedc87,0x55357739,0x7a6fa444,0x9394b95b,0xbfc3d493,0xbbc6c6a5,0xc9c9cabb,0x0098c169,0x6668d600,0xf6d2d6ff,0x7b70f500,0xbba9a5ff,0x837af100,0x887fef00,0x7b6dd10e,
+ 0x725abc1e,0xa89ace75,0x5d3f8a3d,0x7b77ab41,0x909ab858,0xb8c2cb96,0xb2c6b9b1,0xc2c2bbc4,0x009aba6c,0x6a6fd900,0xf2bbc1ff,0x816df800,0x997d7dff,0x8572f200,0x8875ef00,0x7d68d50f,
+ 0x775ac621,0x9988bf65,0x664a9c42,0x7e80b33e,0x8ea1b856,0xb2c1c19a,0xaac6adbd,0xbbbdadce,0x009cb36f,0x6e77dc00,0xeea5adff,0x886bfb00,0x775256ff,0x886bf300,0x886bef00,0x7f63da10,
+ 0x725abc1e,0xa89ace75,0x5d3f8a3d,0x7b77ab41,0x7282a141,0x87a2ab63,0x7faa9e6e,0x90a6ac7a,0x009aba6c,0x6a6fd900,0xf2bbc1ff,0x816df800,0x997d7dff,0x6c5e5bff,0x3f3f39ff,0x625d6bd1,
+ 0x6e5ab31c,0xb8aedc87,0x55357739,0x7a6fa444,0x73769d41,0x6c7d983f,0x6685923d,0x7588a846,0x0098c169,0x6668d600,0xf6d2d6ff,0x7b70f500,0x7f75f300,0x837af100,0x887fef00,0x7b6dd10e,
+ 0x6a5aaa1a,0xc8c0eb97,0x4c2b6535,0x78669c47,0x756a9a42,0x716d983d,0x6e719639,0x7b7bac42,0x0096c866,0x615fd300,0xfae9ebff,0xebdedbff,0xddd4ccff,0xcecabcff,0xbfbfadff,0xc2bfc1dc,
+ 0x665aa018,0xd8d4f9a9,0xccc6ff8c,0xccc6f78c,0xccc6ef8c,0xccc6e78c,0xccc6de8c,0xd8d4e7a9,0x0094d063,0x5d58d000,0x666bef00,0x6e75ef00,0x777fef00,0x7f8aef00,0x8894ef00,0x7777c80c,
+ 0x6a5aaa1a,0x5b438728,0x4c2b6535,0x48306828,0x44356c1a,0x3f3a6f0d,0x3b3f7300,0x484a9300,0x5555b300,0x009fdb72,0x00aaef7f,0x00a8e67f,0x00a7de7f,0x00a5d67f,0x00a4ce7f,0x0098c674,
+ };
+ UInt32 res[kSizeX*kSizeY];
+ DecompressPVRTC<false,true> (reinterpret_cast<const PVRTCBlock*>(src), kSizeX, kSizeY, reinterpret_cast<UInt8*>(res));
+ CHECK_ARRAY_EQUAL (expected, res, kSizeX*kSizeY);
+ }
+ TEST (TwiddleUVPVRTC)
+ {
+ // 00000000, 11111111 = 0101010101010101
+ CHECK_EQUAL (0x5555, TwiddleUVPVRTC (0x100, 0x100, 0xFF, 0x00));
+ // 00011011, 11110000 = 0101011110001010
+ CHECK_EQUAL (0x578A, TwiddleUVPVRTC (0x100, 0x100, 0xF0, 0x1B));
+
+ // 10100000, 1111 = 101001010101
+ CHECK_EQUAL (0xA55, TwiddleUVPVRTC (0x10, 0x100, 0xF, 0xA0));
+ // 0000, 11101111 = 111001010101
+ CHECK_EQUAL (0xE55, TwiddleUVPVRTC (0x100, 0x10, 0xEF, 0x0));
+ }
+ #endif // HAS_PVRTC_DECOMPRESSOR
+ #endif
+
+} // SUITE
+
+#endif // ENABLE_UNIT_TESTS
diff --git a/Runtime/Graphics/S3Decompression.h b/Runtime/Graphics/S3Decompression.h
new file mode 100644
index 0000000..d29a4bc
--- /dev/null
+++ b/Runtime/Graphics/S3Decompression.h
@@ -0,0 +1,18 @@
+#ifndef S3DECOMPRESSION_H
+#define S3DECOMPRESSION_H
+
+#include "TextureFormat.h"
+
+// Decompresses into RGBA32
+bool DecompressNativeTextureFormat (
+ TextureFormat srcFormat, int srcWidth, int srcHeight, const UInt32* srcData,
+ int destWidth, int destHeight, UInt32* destData );
+
+// Some texture formats also need to know the mipLevel
+bool DecompressNativeTextureFormatWithMipLevel( TextureFormat srcFormat, int srcWidth, int srcHeight, int mipLevel, const UInt32* sourceData,
+ int destWidth, int destHeight, UInt32* destData );
+
+
+void DecompressDXT1 (int xblocks, int yblocks, int destWidth, const UInt32* m_pCompBytes, UInt32* m_pDecompBytes);
+
+#endif
diff --git a/Runtime/Graphics/ScreenManager.cpp b/Runtime/Graphics/ScreenManager.cpp
new file mode 100644
index 0000000..4094338
--- /dev/null
+++ b/Runtime/Graphics/ScreenManager.cpp
@@ -0,0 +1,201 @@
+#include "UnityPrefix.h"
+#include "ScreenManager.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/GfxDevice/GfxDeviceSetup.h"
+
+static ScreenManagerPlatform* gScreenManager = NULL;
+
+#if UNITY_IPHONE
+ extern "C" void NotifyAutoOrientationChange();
+#endif
+
+
+void InitScreenManager()
+{
+ Assert(gScreenManager == NULL);
+ gScreenManager = new ScreenManagerPlatform();
+}
+
+void ReleaseScreenManager()
+{
+ Assert(gScreenManager != NULL);
+ delete gScreenManager;
+ gScreenManager = NULL;
+}
+
+ScreenManagerPlatform &GetScreenManager()
+{
+ Assert(gScreenManager != NULL);
+ return *gScreenManager;
+}
+
+ScreenManagerPlatform* GetScreenManagerPtr()
+{
+ return gScreenManager;
+}
+
+ScreenManager::ScreenManager ()
+{
+ m_Width = 0;
+ m_Height = 0;
+ m_SwitchResolutionCallback = NULL;
+ m_CursorInWindow = false;
+ m_IsFullscreen = false;
+ m_AllowCursorHide = true;
+ m_AllowCursorLock = true;
+
+ m_ScreenOrientation = kPortrait;
+ m_RequestedOrientation = kScreenOrientationUnknown;
+
+ // Do not allow to autorotate by default.
+ m_EnabledOrientations = 0;
+}
+
+void ScreenManager::RequestResolution (int width, int height, bool fullscreen, int preferredRefreshRate)
+{
+ m_ResolutionRequest.width = width;
+ m_ResolutionRequest.height = height;
+ m_ResolutionRequest.fullScreen = fullscreen ? 1 : 0;
+ m_ResolutionRequest.refreshRate = preferredRefreshRate;
+}
+
+void ScreenManager::RequestSetFullscreen (bool fullscreen)
+{
+ m_ResolutionRequest.fullScreen = fullscreen ? 1 : 0;
+}
+
+bool ScreenManager::HasFullscreenRequested () const
+{
+ return m_ResolutionRequest.fullScreen == 1;
+}
+
+void ScreenManager::SetCursorInsideWindow (bool insideWindow)
+{
+ m_CursorInWindow = insideWindow;
+ SetShowCursor(GetShowCursor());
+}
+
+int ScreenManager::FindClosestResolution (const ScreenManager::Resolutions& resolutions, int width, int height) const
+{
+ if (resolutions.empty ())
+ return -1;
+
+ int maxDistance = std::numeric_limits<int>::max ();
+ int index = 0;
+ for (int i=0;i<resolutions.size ();i++)
+ {
+ int curWidth = resolutions[i].width;
+ int curHeight = resolutions[i].height;
+ int distance = Abs (width - curWidth) + Abs (height - curHeight);
+ if (distance < maxDistance)
+ {
+ index = i;
+ maxDistance = distance;
+ }
+ }
+ return index;
+}
+
+void ScreenManager::RegisterDidSwitchResolutions (DidSwitchResolutions* resolution)
+{
+ m_SwitchResolutionCallback = resolution;
+}
+
+void ScreenManager::SetRequestedResolution ()
+{
+ if (m_ResolutionRequest.width != -1 && m_ResolutionRequest.height != -1)
+ {
+ SetResolutionImmediate (m_ResolutionRequest.width, m_ResolutionRequest.height, m_ResolutionRequest.IsFullScreen(), m_ResolutionRequest.refreshRate);
+ m_ResolutionRequest.Reset();
+ }
+
+ if (m_ResolutionRequest.fullScreen != -1)
+ {
+ SetIsFullScreenImmediate (m_ResolutionRequest.fullScreen ? true : false);
+ m_ResolutionRequest.fullScreen = -1;
+ }
+}
+
+void ScreenManager::SetIsFullScreenImmediate (bool fullscreen)
+{
+ if (fullscreen != IsFullScreen())
+ SetResolutionImmediate (GetWidth (), GetHeight (), fullscreen, 0);
+}
+
+ScreenManager::Resolution ScreenManager::GetCurrentResolution() const
+{
+ Resolution res;
+ res.width = GetWidth();
+ res.height = GetHeight();
+ res.refreshRate = 0;
+ return res; //@TODO
+}
+
+void ScreenManager::SetupScreenManagerEditor( float w, float h )
+{
+ m_Width = RoundfToIntPos(w);
+ m_Height = RoundfToIntPos(h);
+}
+
+
+void ScreenManager::SetAllowCursorHide (bool allowHide)
+{
+ m_AllowCursorHide = allowHide;
+ if (!m_AllowCursorHide)
+ SetShowCursor(true);
+}
+
+void ScreenManager::SetAllowCursorLock (bool allowLock)
+{
+ m_AllowCursorLock = allowLock;
+ if (!m_AllowCursorLock)
+ SetLockCursor(false);
+}
+
+
+void ScreenManager::SetIsOrientationEnabled(EnabledOrientation orientation, bool enabled)
+{
+#if UNITY_IPHONE
+ NotifyAutoOrientationChange();
+#endif
+
+#if UNITY_WP8
+ // upside down portrait is not available on wp8
+ if (orientation == EnabledOrientation::kAutorotateToPortraitUpsideDown)
+ enabled = false;
+#endif
+
+ if (enabled)
+ m_EnabledOrientations |= orientation;
+ else
+ m_EnabledOrientations &= ~orientation;
+}
+
+
+#include "Runtime/Misc/PlayerSettings.h"
+
+void ScreenManager::EnableOrientationsFromPlayerSettings()
+{
+ SetIsOrientationEnabled(kAutorotateToPortrait, GetPlayerSettings().GetAutoRotationAllowed(0));
+ SetIsOrientationEnabled(kAutorotateToPortraitUpsideDown, GetPlayerSettings().GetAutoRotationAllowed(1));
+ SetIsOrientationEnabled(kAutorotateToLandscapeRight, GetPlayerSettings().GetAutoRotationAllowed(2));
+ SetIsOrientationEnabled(kAutorotateToLandscapeLeft, GetPlayerSettings().GetAutoRotationAllowed(3));
+}
+
+#define INIT_ORIENT_FROM_PLAYER_SETTINGS_IMPL(name, func) \
+void ScreenManager::name(int playerSettingsOrient) \
+{ \
+ switch(playerSettingsOrient) \
+ { \
+ case 0: func(kPortrait); break; \
+ case 1: func(kPortraitUpsideDown); break; \
+ case 2: func(kLandscapeRight); break; \
+ case 3: func(kLandscapeLeft); break; \
+ \
+ default: Assert(false && #name "do not accept autorotation."); \
+ } \
+} \
+
+INIT_ORIENT_FROM_PLAYER_SETTINGS_IMPL(SetConcreteOrientationFromPlayerSettings, SetScreenOrientation);
+INIT_ORIENT_FROM_PLAYER_SETTINGS_IMPL(RequestConcreteOrientationFromPlayerSettings, RequestOrientation);
diff --git a/Runtime/Graphics/ScreenManager.h b/Runtime/Graphics/ScreenManager.h
new file mode 100644
index 0000000..77adf85
--- /dev/null
+++ b/Runtime/Graphics/ScreenManager.h
@@ -0,0 +1,257 @@
+#pragma once
+
+#include <vector>
+
+enum ScreenOrientation
+{
+ kScreenOrientationUnknown,
+
+ kPortrait,
+ kPortraitUpsideDown,
+ kLandscapeLeft,
+ kLandscapeRight,
+
+ kAutoRotation,
+
+ kScreenOrientationCount
+};
+
+inline ScreenOrientation PlayerSettingsToScreenOrientation(int defaultScreenOrientation)
+{
+ switch (defaultScreenOrientation) {
+ case 0 :
+ return kPortrait;
+ case 1 :
+ return kPortraitUpsideDown;
+ case 2 :
+ return kLandscapeRight;
+ case 3 :
+ return kLandscapeLeft;
+ case 4 :
+ return kAutoRotation;
+ default:
+ return kScreenOrientationUnknown;
+ }
+}
+
+inline int ScreenOrientationToPlayerSettings(ScreenOrientation screenOrientation)
+{
+ switch (screenOrientation) {
+ case kPortrait :
+ return 0;
+ case kPortraitUpsideDown :
+ return 1;
+ case kLandscapeRight :
+ return 2;
+ case kLandscapeLeft :
+ return 3;
+ case kAutoRotation :
+ return 4;
+ default:
+ return 5;
+ }
+}
+
+
+enum SleepTimeout
+{
+ kNeverSleep = -1,
+ kSystemSetting = -2,
+};
+
+enum EnabledOrientation
+{
+ kAutorotateToPortrait = 1,
+ kAutorotateToPortraitUpsideDown = 2,
+ kAutorotateToLandscapeLeft = 4,
+ kAutorotateToLandscapeRight = 8
+};
+
+#define kResolutionWidth "Screenmanager Resolution Width"
+#define kResolutionHeight "Screenmanager Resolution Height"
+#define kIsFullScreen "Screenmanager Is Fullscreen mode"
+#define kGraphicsQuality "UnityGraphicsQuality"
+
+class ScreenManager
+{
+public:
+ ScreenManager ();
+
+ void RequestResolution (int width, int height, bool fullscreen, int preferredRefreshRate);
+ void RequestSetFullscreen (bool fullscreen);
+ bool HasFullscreenRequested () const;
+
+ virtual void SetRequestedResolution ();
+ void SetIsFullScreenImmediate (bool fullscreen);
+ virtual bool SetResolutionImmediate (int /*width*/, int /*height*/, bool /*fullscreen*/, int /*preferredRefreshRate*/) { return false; }
+
+ struct Resolution
+ {
+ // Keep in sync with .NET Resolution struct!
+ int width;
+ int height;
+ int refreshRate;
+
+ friend bool operator < (const Resolution& lhs, const Resolution& rhs)
+ {
+ if( lhs.width != rhs.width )
+ return lhs.width < rhs.width;
+ return lhs.height < rhs.height;
+ }
+
+ /**
+ * Returns true if this resolution is >= given width/height.
+ * Used to filter out resolutions that are too big for windowed modes.
+ */
+ bool IsTooBigFor( int w, int h ) const
+ {
+ return width >= w || height >= h;
+ }
+
+ bool IsRotated() const { return height > width; }
+
+ Resolution ()
+ {
+ width = 0;
+ height = 0;
+ refreshRate = 0;
+ }
+ };
+
+ struct ResolutionRequest
+ {
+ ResolutionRequest() { Reset(); }
+
+ int width;
+ int height;
+ int fullScreen;
+ int refreshRate;
+
+ bool IsFullScreen() const { return fullScreen == 1; }
+
+ void Reset()
+ {
+ width = -1;
+ height = -1;
+ fullScreen = -1;
+ refreshRate = -1;
+ }
+ };
+
+ typedef std::vector<Resolution> Resolutions;
+ virtual Resolutions GetResolutions (int /*preferredRefreshRate*/ = 0, bool /*clampToDesktopRes*/ = false) { return Resolutions(); }
+ int FindClosestResolution (const ScreenManager::Resolutions& resolutions, int width, int height) const;
+
+ virtual Resolution GetCurrentResolution() const;
+
+ virtual bool GetShowCursor () const { return true; }
+ virtual void SetShowCursor (bool /*show*/) { }
+
+ virtual bool GetLockCursor () const { return false; }
+ virtual void SetLockCursor (bool /*lock*/) { }
+
+ virtual bool GetAllowLayeredRendering () const { return true; }
+ virtual void SetAllowLayeredRendering (bool /*allow*/) { }
+
+ virtual int GetScreenTimeout () const { return kNeverSleep; }
+ virtual void SetScreenTimeout (int /*value*/) { }
+
+ bool GetAllowCursorHide() const { return m_AllowCursorHide; }
+ void SetAllowCursorHide (bool allowHide);
+
+ bool GetAllowCursorLock() const { return m_AllowCursorLock; }
+ void SetAllowCursorLock (bool allowLock);
+
+ // Named *IsFocus to avoid confusion with windows API
+ virtual bool GetIsFocused() const { return true; }
+ virtual void SetIsFocused (bool /*focus*/) { }
+
+ virtual bool GetCursorInsideWindow () const { return m_CursorInWindow; }
+ virtual void SetCursorInsideWindow (bool insideWindow);
+
+ // Do these need to be virtual?
+ virtual int GetWidth () const { return m_Width; }
+ virtual int GetHeight () const { return m_Height; }
+
+ int GetWidthAsRequested () const { return m_ResolutionRequest.width == -1 ? GetWidth () : m_ResolutionRequest.width; }
+ int GetHeightAsRequested () const { return m_ResolutionRequest.height == -1 ? GetHeight () : m_ResolutionRequest.height; }
+ bool GetFullScreenAsRequested () const { return m_ResolutionRequest.fullScreen == -1 ? IsFullScreen () : m_ResolutionRequest.IsFullScreen (); }
+ int GetRefreshRateAsRequested () const { return m_ResolutionRequest.refreshRate == -1 ? GetCurrentResolution ().refreshRate : m_ResolutionRequest.refreshRate; }
+
+ virtual float GetDPI () const { return 0.f; }
+ virtual bool IsFullScreen () const { return m_IsFullscreen; }
+
+ void RequestOrientation(ScreenOrientation value) { m_RequestedOrientation = value; }
+ ScreenOrientation GetRequestedOrientation() const { return m_RequestedOrientation; }
+
+ virtual ScreenOrientation GetScreenOrientation() const { return m_ScreenOrientation; };
+ virtual void SetScreenOrientation (ScreenOrientation value) { m_ScreenOrientation = value; }
+
+ typedef void DidSwitchResolutions ();
+ void RegisterDidSwitchResolutions (DidSwitchResolutions* resolution);
+
+ virtual void SetupScreenManagerEditor(float w, float h);
+
+ void SetIsOrientationEnabled(EnabledOrientation orientation, bool enabled);
+ bool GetIsOrientationEnabled(EnabledOrientation orientation) const { return m_EnabledOrientations & orientation; }
+
+ // helpers for connecting ScreenManager and PlayerSettings
+ void EnableOrientationsFromPlayerSettings();
+ void SetConcreteOrientationFromPlayerSettings(int playerSettingsOrient);
+ void RequestConcreteOrientationFromPlayerSettings(int playerSettingsOrient);
+
+protected:
+ ResolutionRequest m_ResolutionRequest;
+
+ DidSwitchResolutions* m_SwitchResolutionCallback;
+
+ bool m_AllowCursorHide;
+ bool m_AllowCursorLock;
+ bool m_CursorInWindow;
+ bool m_IsFullscreen;
+
+ int m_Width;
+ int m_Height;
+
+ unsigned int m_EnabledOrientations;
+ ScreenOrientation m_ScreenOrientation;
+ ScreenOrientation m_RequestedOrientation;
+};
+
+#if UNITY_PEPPER
+#include "PlatformDependent/PepperPlugin/ScreenManagerPepper.h"
+#elif UNITY_OSX
+#include "PlatformDependent/OSX/ScreenManagerOSX.h"
+#elif UNITY_WP8
+#include "PlatformDependent/WP8Player/ScreenManagerWP8.h"
+#elif UNITY_METRO
+#include "PlatformDependent/MetroPlayer/ScreenManagerMetro.h"
+#elif UNITY_WIN
+#include "../../PlatformDependent/WinPlayer/ScreenManagerWin.h"
+#elif UNITY_XENON
+#include "../../PlatformDependent/Xbox360/Source/ScreenManagerXenon.h"
+#elif UNITY_PS3
+#include "../../PlatformDependent/PS3Player/ScreenManagerPS3.h"
+#elif UNITY_ANDROID
+#include "PlatformDependent/AndroidPlayer/ScreenManagerAndroid.h"
+#elif UNITY_IPHONE
+#include "PlatformDependent/iPhonePlayer/ScreenManagerIPhone.h"
+#elif UNITY_LINUX && SUPPORT_X11
+#include "PlatformDependent/Linux/ScreenManagerLinux.h"
+#elif UNITY_WII
+#include "PlatformDependent/Wii/WiiScreenManager.h"
+#elif UNITY_FLASH
+#include "PlatformDependent/FlashSupport/cpp/FlashScreenManager.h"
+#elif UNITY_BB10
+#include "PlatformDependent/BB10Player/ScreenManagerBB10.h"
+#elif UNITY_TIZEN
+#include "PlatformDependent/TizenPlayer/ScreenManagerTizen.h"
+#else
+#define ScreenManagerPlatform ScreenManager
+#endif
+
+void InitScreenManager();
+void ReleaseScreenManager();
+ScreenManagerPlatform &GetScreenManager();
+ScreenManagerPlatform* GetScreenManagerPtr();
+
diff --git a/Runtime/Graphics/SpriteFrame.cpp b/Runtime/Graphics/SpriteFrame.cpp
new file mode 100644
index 0000000..80c568f
--- /dev/null
+++ b/Runtime/Graphics/SpriteFrame.cpp
@@ -0,0 +1,272 @@
+#include "UnityPrefix.h"
+#include "SpriteFrame.h"
+
+#if ENABLE_SPRITES
+
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/Graphics/SpriteUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Misc/BuildSettings.h"
+#if UNITY_EDITOR
+ #include "Editor/Src/EditorSettings.h"
+ #include "Editor/Src/SpritePacker/SpritePacker.h"
+ #include "Runtime/BaseClasses/IsPlaying.h"
+#endif
+
+static const int kMinSpriteDimensionForMesh = 32;
+static const float kSpriteAABBDepth = 0.1f;
+using namespace Unity;
+
+IMPLEMENT_CLASS(Sprite)
+IMPLEMENT_OBJECT_SERIALIZE(Sprite)
+
+Sprite::Sprite(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_PixelsToUnits(100.0f)
+, m_Extrude(0)
+#if UNITY_EDITOR
+, m_AtlasReady(false)
+#endif
+{
+#if ENABLE_MULTITHREADED_CODE
+ m_CurrentCPUFence = 0;
+ m_WaitOnCPUFence = false;
+#endif
+}
+
+Sprite::~Sprite()
+{
+ WaitOnRenderThreadUse();
+ m_IntermediateUsers.Notify( kImNotifyAssetDeleted );
+}
+
+void Sprite::AwakeFromLoad(AwakeFromLoadMode mode)
+{
+ Super::AwakeFromLoad(mode);
+
+#if UNITY_EDITOR
+ RefreshAtlasRD();
+#endif
+}
+
+template<class TransferFunction>
+void Sprite::Transfer(TransferFunction& transfer)
+{
+ Super::Transfer(transfer);
+
+ TRANSFER(m_Rect);
+ TRANSFER(m_Offset);
+ TRANSFER(m_PixelsToUnits);
+ TRANSFER(m_Extrude);
+
+ TRANSFER_EDITOR_ONLY_HIDDEN(m_AtlasName);
+ TRANSFER_EDITOR_ONLY_HIDDEN(m_PackingTag);
+
+#if UNITY_EDITOR
+ bool atlasPacked = !m_PackingTag.empty();
+ if (!atlasPacked)
+ {
+ transfer.Transfer(m_RD, "m_RD");
+ transfer.Align();
+ }
+ else
+ {
+ if (transfer.IsSerializingForGameRelease())
+ {
+ const bool packingEnabled = (GetEditorSettings().GetSpritePackerMode() != EditorSettings::kSPOff); // Make sure packing is not disabled
+ const bool serializePacked = (packingEnabled && GetIsPacked());
+
+ // Note: it is not expected that all sprites with atlas hints will be packed.
+ transfer.Transfer(serializePacked ? m_AtlasRD : m_RD, "m_RD");
+ transfer.Align();
+ }
+ else
+ {
+ transfer.Transfer(m_RD, "m_RD");
+ transfer.Transfer(m_AtlasRD, "m_AtlasRD", kHideInEditorMask);
+ }
+ }
+#else
+ transfer.Transfer(m_RD, "m_RD");
+ transfer.Align();
+#endif
+
+#if ENABLE_SPRITECOLLIDER
+ TRANSFER(m_Poly);
+#endif
+}
+
+bool Sprite::GetIsPacked() const
+{
+#if UNITY_EDITOR
+ return (m_AtlasReady != 0);
+#else
+ return m_RD.settings.packed;
+#endif
+}
+
+#if UNITY_EDITOR
+static bool s_packingAllowedInPlayMode = false;
+void Sprite::OnEnterPlaymode()
+{
+ s_packingAllowedInPlayMode = (GetEditorSettings().GetSpritePackerMode() == EditorSettings::kSPOn);
+}
+#endif
+
+const SpriteRenderData& Sprite::GetRenderDataForPlayMode() const
+{
+#if UNITY_EDITOR
+ return GetRenderData(s_packingAllowedInPlayMode && IsWorldPlaying());
+#else
+ return GetRenderData(true);
+#endif
+}
+
+const SpriteRenderData& Sprite::GetRenderData(bool getEditorOnlyAtlasRenderDataIfPacked) const
+{
+#if UNITY_EDITOR
+ if (getEditorOnlyAtlasRenderDataIfPacked && GetIsPacked())
+ return m_AtlasRD;
+#endif
+ return m_RD;
+}
+
+void Sprite::Initialize(Texture2D* texture, const Rectf& rect, const Vector2f& pivot, float pixelsToUnits, unsigned int extrude, SpriteMeshType meshType)
+{
+ const bool hasAdvancedVersion = GetBuildSettings().hasAdvancedVersion;
+
+ Assert(texture);
+ bool generateRenderMesh = (meshType == kSpriteMeshTypeTight);
+ generateRenderMesh &= (rect.width >= kMinSpriteDimensionForMesh);
+ generateRenderMesh &= (rect.height >= kMinSpriteDimensionForMesh);
+ generateRenderMesh &= (texture->GetRawImageData() != NULL); //BUGFIX:569636 - if we can't access pixel data, generate a quad.
+ generateRenderMesh &= hasAdvancedVersion; //Note: tight mesh is Pro only.
+
+ // Common data
+ m_Rect = rect;
+ m_Offset = PivotToOffset(rect, pivot);
+ m_PixelsToUnits = pixelsToUnits;
+ m_Extrude = hasAdvancedVersion ? extrude : 0; //Note: extrude is Pro only.
+
+ // Render data
+ m_RD.texture = texture;
+ if (generateRenderMesh)
+ {
+ Rectf meshRect;
+ m_RD.GenerateFullMesh(m_Rect, m_Offset, pixelsToUnits, m_Extrude, &meshRect);
+ m_RD.textureRect = meshRect;
+ m_RD.textureRect.x += m_Rect.GetPosition().x;
+ m_RD.textureRect.y += m_Rect.GetPosition().y;
+ }
+ else
+ {
+ //Note: we could do alpha-trim here. But do we want to lose the texture space in this case?
+ m_RD.GenerateQuadMesh(m_Rect, m_Offset, pixelsToUnits);
+ m_RD.textureRect = m_Rect;
+ }
+ m_RD.textureRectOffset = m_RD.textureRect.GetPosition() - rect.GetPosition();
+}
+
+void Sprite::WaitOnRenderThreadUse()
+{
+#if ENABLE_MULTITHREADED_CODE
+ if (m_WaitOnCPUFence)
+ {
+ GetGfxDevice().WaitOnCPUFence(m_CurrentCPUFence);
+ m_WaitOnCPUFence = false;
+ }
+#endif
+}
+
+void Sprite::GenerateOutline(float detail, unsigned char alphaTolerance, bool holeDetection, std::vector<dynamic_array<Vector2f> >& outVertices, int extrudeOverride)
+{
+ unsigned int extrude = (extrudeOverride < 0) ? m_Extrude : extrudeOverride;
+ GenerateSpriteOutline(m_RD.texture, m_PixelsToUnits, m_Rect, m_Offset, detail, alphaTolerance, holeDetection, extrude, kPathEmbed, &outVertices);
+}
+
+SpriteRenderData::SpriteRenderData()
+: settingsRaw(0)
+{
+}
+
+void SpriteRenderData::GenerateFullMesh(const Rectf& rect, const Vector2f& rectOffset, float pixelsToUnits, unsigned int extrude, Rectf* meshRect)
+{
+ GenerateSpriteOutline(texture, pixelsToUnits, rect, rectOffset, kSpriteDefaultDetail, kSpriteDefaultAlphaTolerance, true, extrude, kPathEmbed, NULL, &vertices, &indices, meshRect);
+}
+
+void SpriteRenderData::GenerateQuadMesh(const Rectf& rect, const Vector2f& rectOffset, float pixelsToUnits)
+{
+ const float scaler = 1.0f / pixelsToUnits;
+ const int texGlWidth = texture->GetGLWidth();
+ const int texGlHeight = texture->GetGLHeight();
+
+ const float halfW = rect.width * 0.5f * scaler;
+ const float halfH = rect.height * 0.5f * scaler;
+ const Vector2f offset = rectOffset * scaler;
+ const Vector4f uv = Vector4f(rect.x / texGlWidth, rect.y / texGlHeight, rect.GetXMax() / texGlWidth, rect.GetYMax() / texGlHeight);
+
+ vertices.resize(4);
+ vertices[0].pos = Vector3f(-halfW - offset.x, halfH - offset.y, 0.0f);
+ vertices[0].uv = Vector2f(uv[0], uv[3]);
+ vertices[1].pos = Vector3f( halfW - offset.x, halfH - offset.y, 0.0f);
+ vertices[1].uv = Vector2f(uv[2], uv[3]);
+ vertices[2].pos = Vector3f(-halfW - offset.x, -halfH - offset.y, 0.0f);
+ vertices[2].uv = Vector2f(uv[0], uv[1]);
+ vertices[3].pos = Vector3f( halfW - offset.x, -halfH - offset.y, 0.0f);
+ vertices[3].uv = Vector2f(uv[2], uv[1]);
+
+ indices.resize(6);
+ indices[0] = 0;
+ indices[1] = 1;
+ indices[2] = 2;
+ indices[3] = 2;
+ indices[4] = 1;
+ indices[5] = 3;
+}
+
+AABB Sprite::GetBounds(Vector2f extraOffset) const
+{
+ Vector2f halfRect(m_Rect.width / m_PixelsToUnits * 0.5f, m_Rect.height / m_PixelsToUnits * 0.5f);
+ Vector2f offset(m_Offset.x / m_PixelsToUnits, m_Offset.y / m_PixelsToUnits);
+
+ MinMaxAABB minmax;
+ Vector3f minV(-halfRect.x - offset.x + extraOffset.x, halfRect.y - offset.y + extraOffset.y, kSpriteAABBDepth);
+ Vector3f maxV( halfRect.x - offset.x + extraOffset.x, -halfRect.y - offset.y + extraOffset.y, -kSpriteAABBDepth);
+ minmax.Encapsulate(minV);
+ minmax.Encapsulate(maxV);
+ return minmax;
+}
+
+Vector2f Sprite::PivotToOffset(const Rectf& rect, const Vector2f& pivot)
+{
+ const Vector2f alignPos = rect.GetPosition() + rect.GetSize().Scale(pivot);
+ Vector2f offset = alignPos - rect.GetCenterPos();
+ return offset;
+}
+
+#if UNITY_EDITOR
+void Sprite::RefreshAtlasRD ()
+{
+ UnityStr atlasName;
+ const SpriteRenderData* packedRD = (m_PackingTag.empty() ? NULL : SpritePacker::GetPackedSpriteRD(*this, atlasName));
+ if (packedRD)
+ {
+ WaitOnRenderThreadUse();
+ m_AtlasReady = true;
+ m_AtlasRD = *packedRD;
+ m_AtlasName = atlasName;
+ }
+ else
+ ClearAtlasRD ();
+}
+
+void Sprite::ClearAtlasRD ()
+{
+ WaitOnRenderThreadUse();
+ m_AtlasReady = false;
+ m_AtlasRD = SpriteRenderData ();
+ m_AtlasName.clear();
+}
+#endif
+
+#endif //ENABLE_SPRITES
diff --git a/Runtime/Graphics/SpriteFrame.h b/Runtime/Graphics/SpriteFrame.h
new file mode 100644
index 0000000..e319e2c
--- /dev/null
+++ b/Runtime/Graphics/SpriteFrame.h
@@ -0,0 +1,190 @@
+#pragma once
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_SPRITES
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/Texture2D.h"
+#include "Runtime/Graphics/Polygon2D.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Camera/IntermediateUsers.h"
+
+const float kSpriteDefaultDetail = -1.0f; // if less than zero, automatic lod evaluation will performed
+const UInt8 kSpriteDefaultAlphaTolerance = 0;
+const UInt32 kSpriteDefaultExtrude = 1;
+const UInt8 kSpriteMaxAlphaTolerance = 254;
+
+class AABB;
+
+struct SpriteVertex
+{
+ DECLARE_SERIALIZE_NO_PPTR (SpriteVertex)
+
+ Vector3f pos;
+ Vector2f uv;
+};
+
+enum SpritePackingMode
+{
+ kSPMTight = 0,
+ kSPMRectangle
+};
+
+enum SpritePackingRotation
+{
+ kSPRNone = 0,
+ // Reserved
+ kSPRAny = 15
+};
+
+enum SpriteMeshType
+{
+ kSpriteMeshTypeFullRect = 0,
+ kSpriteMeshTypeTight = 1
+};
+
+struct SpriteRenderData
+{
+ DECLARE_SERIALIZE (SpriteRenderData)
+
+ SpriteRenderData();
+
+ PPtr<Texture2D> texture;
+
+ // Mesh is scaled according to kTexturePixelsPerUnit
+ std::vector<SpriteVertex> vertices;
+ std::vector<UInt16> indices;
+
+ Rectf textureRect; // In pixels (texture space). Invalid if packingMode is Tight.
+ Vector2f textureRectOffset; // In pixels (texture space). Invalid if packingMode is Tight.
+
+ union
+ {
+ struct
+ {
+ UInt32 packed : 1; // bool
+ UInt32 packingMode : 1; // SpritePackingMode
+ UInt32 packingRotation : 4; // SpritePackingRotation
+ UInt32 reserved : 26;
+ } settings;
+ UInt32 settingsRaw;
+ };
+
+ void GenerateQuadMesh(const Rectf& rect, const Vector2f& rectOffset, float pixelsToUnits);
+ void GenerateFullMesh(const Rectf& rect, const Vector2f& rectOffset, float pixelsToUnits, unsigned int extrude, Rectf* meshRect);
+};
+
+class Sprite : public NamedObject
+{
+public:
+ REGISTER_DERIVED_CLASS(Sprite, NamedObject)
+ DECLARE_OBJECT_SERIALIZE(Sprite)
+
+ Sprite(MemLabelId label, ObjectCreationMode mode);
+ // ~Sprite(); declared-by-macro
+
+ void Initialize(Texture2D* texture, const Rectf& rect, const Vector2f& pivot, float pixelsToUnits, unsigned int extrude, SpriteMeshType meshType);
+
+ virtual void AwakeFromLoad(AwakeFromLoadMode mode);
+
+ // detail [0; 1] where 0 is simplified and 1 is max quality.
+ void GenerateOutline(float detail, unsigned char alphaTolerance, bool holeDetection, std::vector<dynamic_array<Vector2f> >& outVertices, int extrudeOverride = -1);
+
+ // Common data
+ Vector2f GetSize() const { return m_Rect.GetSize(); }
+ Vector2f GetSizeInUnits() const { return m_Rect.GetSize()/m_PixelsToUnits; }
+
+ const Rectf& GetRect() const { return m_Rect; }
+ const Vector2f& GetOffset() const { return m_Offset; }
+ float GetPixelsToUnits() const { return m_PixelsToUnits; }
+
+#if ENABLE_SPRITECOLLIDER
+ Polygon2D& GetPoly() { return m_Poly; }
+#endif
+
+ // Rendering data
+ // Note: we want live preview of atlases when in editor, but only for rendering in play mode.
+ // GetRenderData(false) will always return the source texture render data.
+ // GetRenderData(true) will return the atlas texture render data if it is available, otherwise fall back to source texture render data.
+ // GetRenderDataForPlayMode() is a convenience function which tries to get atlas texture render data when world is playing.
+ const SpriteRenderData& GetRenderData(bool getEditorOnlyAtlasRenderDataIfPacked) const;
+ const SpriteRenderData& GetRenderDataForPlayMode() const;
+
+ bool GetIsPacked() const;
+ AABB GetBounds(Vector2f extraOffset = Vector2f(0, 0)) const;
+
+#if UNITY_EDITOR
+ void SetPackingTag (const std::string& packingTag) { m_PackingTag = packingTag; }
+ std::string GetPackingTag() const { return m_PackingTag; }
+
+ std::string GetActiveAtlasName() const { return m_AtlasReady ? m_AtlasName : ""; }
+ Texture2D* GetActiveAtlasTexture() const { return m_AtlasReady ? m_AtlasRD.texture : NULL; }
+
+ // Changing texture import settings will rebuild Sprites and not use atlases until the next atlas rebuild, which will trigger RefreshAtlasRD or ClearAtlasRD.
+ void RefreshAtlasRD ();
+ void ClearAtlasRD ();
+
+ static void OnEnterPlaymode ();
+#endif
+
+#if ENABLE_MULTITHREADED_CODE
+ void SetCurrentCPUFence( UInt32 fence ) { m_CurrentCPUFence = fence; m_WaitOnCPUFence = true; }
+#endif
+ void WaitOnRenderThreadUse();
+
+ static Vector2f PivotToOffset(const Rectf& rect, const Vector2f& pivot); // [0;1] to rect space
+
+ void AddIntermediateUser( ListNode<IntermediateRenderer>& node ) { m_IntermediateUsers.AddUser(node); }
+
+private:
+ // Common data
+ Rectf m_Rect; // In pixels (originating texture, full rect definition)
+ Vector2f m_Offset; // In pixels (from center of final rect, defines pivot point based on alignment)
+
+ // Rendering data
+ SpriteRenderData m_RD;
+ float m_PixelsToUnits;
+ unsigned int m_Extrude;
+
+#if UNITY_EDITOR
+ int m_AtlasReady;
+ SpriteRenderData m_AtlasRD;
+ UnityStr m_AtlasName;
+ UnityStr m_PackingTag;
+#endif
+
+#if ENABLE_SPRITECOLLIDER
+ Polygon2D m_Poly; // Collider
+#endif
+
+#if ENABLE_MULTITHREADED_CODE
+ UInt32 m_CurrentCPUFence;
+ bool m_WaitOnCPUFence;
+#endif
+
+ IntermediateUsers m_IntermediateUsers; // IntermediateRenderer users of this sprite
+};
+
+template<class TransferFunction>
+void SpriteVertex::Transfer(TransferFunction& transfer)
+{
+ transfer.Transfer(pos, "pos");
+ transfer.Transfer(uv, "uv");
+}
+
+template<class TransferFunction>
+void SpriteRenderData::Transfer(TransferFunction& transfer)
+{
+ TRANSFER(texture);
+ TRANSFER(vertices);
+ TRANSFER(indices);
+
+ transfer.Align();
+ TRANSFER(textureRect);
+ TRANSFER(textureRectOffset);
+ TRANSFER(settingsRaw);
+}
+
+#endif //ENABLE_SPRITES
diff --git a/Runtime/Graphics/SpriteUtility.cpp b/Runtime/Graphics/SpriteUtility.cpp
new file mode 100644
index 0000000..5c131c3
--- /dev/null
+++ b/Runtime/Graphics/SpriteUtility.cpp
@@ -0,0 +1,155 @@
+#include "UnityPrefix.h"
+#include "SpriteUtility.h"
+
+#if ENABLE_SPRITES
+
+#include "Runtime/Geometry/SpriteMeshGenerator.h"
+
+static const float kSpriteEdgeBias = 1.0f;
+
+bool GetSpriteMeshRectPixelBounds (const Texture2D& texture, const Rectf& rectInaccurate, int& rectX, int& rectY, int& rectRight, int& rectBottom)
+{
+ rectX = Floorf(rectInaccurate.x);
+ rectY = Floorf(rectInaccurate.y);
+ rectRight = Ceilf(rectInaccurate.GetRight());
+ rectBottom = Ceilf(rectInaccurate.GetBottom());
+
+ // Sanity check to see if out of texture rect or if error is more than 1 pixel for whole-texture sprites.
+ const bool errorX = (rectX < 0);
+ const bool errorY = (rectY < 0);
+ const bool errorR = (rectRight > texture.GetGLWidth() + 1);
+ const bool errorB = (rectBottom > texture.GetGLHeight() + 1);
+ const bool hasError = (errorX || errorY || errorR || errorB);
+ Assert(!hasError);
+
+ // In rare cases when a texture is downscaled to match the max texture size limit it might introduce an odd scaling factor, cause a 1 pixel error and grow beyond the texture size.
+ rectRight = std::min(rectRight, texture.GetGLWidth());
+ rectBottom = std::min(rectBottom, texture.GetGLHeight());
+
+ return hasError;
+}
+
+void GenerateSpriteOutline(PPtr<Texture2D> texture, float pixelsToUnits, const Rectf& rectInaccurate, const Vector2f& rectOffset, float detail, unsigned char alphaTolerance, bool holeDetection, unsigned int extrude, int simplifyMode, std::vector<dynamic_array<Vector2f> >* outLine, std::vector<SpriteVertex>* outVertices, std::vector<UInt16>* outIndices, Rectf* meshRect)
+{
+ if(texture.IsNull())
+ return;
+
+ const int texW = texture->GetGLWidth();
+ const int texH = texture->GetGLHeight();
+
+ // Figure out what pixels to use.
+ // If a platform texture size limit is set, rectInaccurate will not define actual pixels.
+ int rectX, rectY, rectRight, rectBottom;
+ GetSpriteMeshRectPixelBounds(*texture, rectInaccurate, rectX, rectY, rectRight, rectBottom);
+ const int rectWidth = rectRight - rectX;
+ const int rectHeight = rectBottom - rectY;
+ const float rectAdjustX = rectInaccurate.x - rectX;
+ const float rectAdjustY = rectInaccurate.y - rectY;
+
+ // Extract rectangle
+ const int imageSize = rectWidth * rectHeight * sizeof(ColorRGBA32);
+ ColorRGBA32* imageData = (ColorRGBA32*)UNITY_MALLOC(kMemDefault, imageSize);
+ {
+ const int textureSize = texW * texH * sizeof(ColorRGBA32);
+ ColorRGBA32* texData = (ColorRGBA32*)UNITY_MALLOC(kMemDefault, textureSize);
+ texture->GetPixels32(0, texData);
+
+ for (int row = 0; row < rectHeight; ++row)
+ {
+ ColorRGBA32* dest = imageData + row * rectWidth;
+ ColorRGBA32* src = texData + (rectY + row) * texW + rectX;
+ UNITY_MEMCPY(dest, src, sizeof(ColorRGBA32) * rectWidth);
+ }
+
+ UNITY_FREE(kMemDefault, texData);
+ }
+
+ // Detect shape
+ const float hullTolerance = (detail>=0.0f) ? 1.0f - clamp01(detail) : detail;
+ extrude = clamp<unsigned int>(extrude, 0, 32);
+ SpriteMeshGenerator smg;
+ smg.MakeShape(imageData, rectWidth, rectHeight, hullTolerance, alphaTolerance, holeDetection, extrude, kSpriteEdgeBias, simplifyMode);
+
+ // Adjustments for offset and pixel-to-unity scale.
+ const float scale = 1.0 / pixelsToUnits;
+ const float scale_x = 1.0 / texW;
+ const float scale_y = 1.0 / texH;
+ const Vector2f oxy(float(rectWidth) * 0.5f + rectOffset.x - rectAdjustX, float(rectHeight) * 0.5f + rectOffset.y - rectAdjustY);
+
+ // Fill outline
+ if (outLine)
+ {
+ const std::vector<SpriteMeshGenerator::path>& paths = smg.GetPaths();
+ outLine->resize(paths.size());
+
+ int ci = 0;
+ for (std::vector<SpriteMeshGenerator::path>::const_iterator p = paths.begin(); p != paths.end(); ++p)
+ {
+ const SpriteMeshGenerator::path& path = *p;
+ const std::vector<SpriteMeshGenerator::vertex>& poly = path.m_path;
+ dynamic_array<Vector2f> finalPath;
+ finalPath.reserve(poly.size());
+ for (std::vector<SpriteMeshGenerator::vertex>::const_iterator it = poly.begin(); it != poly.end(); ++it)
+ {
+ Vector2f polyPoint = it->p;
+ Vector2f finalPoint((polyPoint.x-oxy.x)*scale, (polyPoint.y-oxy.y)*scale);
+ finalPath.push_back(finalPoint);
+ }
+ (*outLine)[ci++] = finalPath;
+ }
+ }
+
+ // Fill mesh
+ if (outVertices)
+ {
+ Assert(outIndices);
+
+ // Decompose outline
+ std::vector<Vector2f> vertices;
+ std::vector<int> indices;
+ smg.Decompose(&vertices, &indices);
+
+ if (indices.size() > 0)
+ {
+ outVertices->clear();
+ outIndices->clear();
+ // Assign indices
+ std::reverse(indices.begin(), indices.end());
+ outIndices->assign(indices.begin(), indices.end());
+
+ // Assign vertices
+ outVertices->reserve(vertices.size());
+ for (std::vector<SpriteMeshGenerator::vertex>::size_type i = 0; i < vertices.size(); ++i)
+ {
+ SpriteVertex v;
+ v.pos = Vector3f((vertices[i].x-oxy.x)*scale, (vertices[i].y-oxy.y)*scale, 0.0f);
+ v.uv = Vector2f((vertices[i].x+rectInaccurate.x)*scale_x, (vertices[i].y+rectInaccurate.y)*scale_y);
+ outVertices->push_back(v);
+ }
+ }
+ }
+ else
+ {
+ Assert(outIndices == NULL);
+ }
+
+ // Set mesh rect
+ if (meshRect)
+ {
+ if (!smg.FindBounds(*meshRect))
+ *meshRect = Rectf(rectX, rectY, rectWidth, rectHeight);
+ }
+
+ // Clean up
+ UNITY_FREE(kMemDefault, imageData);
+}
+
+void GetAABBVerticesForSprite (const AABB& aabb, Vector3f* outVertices)
+{
+ outVertices[0] = aabb.m_Center + Vector3f (-aabb.m_Extent.x, -aabb.m_Extent.y, 0.0f);
+ outVertices[1] = aabb.m_Center + Vector3f (+aabb.m_Extent.x, -aabb.m_Extent.y, 0.0f);
+ outVertices[2] = aabb.m_Center + Vector3f (-aabb.m_Extent.x, +aabb.m_Extent.y, 0.0f);
+ outVertices[3] = aabb.m_Center + Vector3f (+aabb.m_Extent.x, +aabb.m_Extent.y, 0.0f);
+}
+
+#endif //ENABLE_SPRITES
diff --git a/Runtime/Graphics/SpriteUtility.h b/Runtime/Graphics/SpriteUtility.h
new file mode 100644
index 0000000..aff8491
--- /dev/null
+++ b/Runtime/Graphics/SpriteUtility.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_SPRITES
+#include "Runtime/Graphics/Texture2D.h"
+#include "Runtime/Graphics/SpriteFrame.h"
+
+enum PathSimplifyMode
+{
+ kPathReduce=0,
+ kPathEmbed
+};
+
+void GenerateSpriteOutline(PPtr<Texture2D> texture, float pixelsToUnits, const Rectf& rect, const Vector2f& rectOffset, float detail, unsigned char alphaTolerance, bool holeDetection, unsigned int extrude, int simplifyMode, std::vector<dynamic_array<Vector2f> >* outLine = NULL, std::vector<SpriteVertex>* outVertices = NULL, std::vector<UInt16>* outIndices = NULL, Rectf* meshRect = NULL);
+
+// Returns the front face of the AABB.
+// Sprite is on local Z = 0, however the AABB has volume. Flatten it on Z and return 4 vertices only.
+void GetAABBVerticesForSprite (const AABB& aabb, Vector3f* outVertices);
+
+bool GetSpriteMeshRectPixelBounds (const Texture2D& texture, const Rectf& rectInaccurate, int& rectX, int& rectY, int& rectRight, int& rectBottom);
+
+#endif //ENABLE_SPRITES
diff --git a/Runtime/Graphics/SubstanceArchive.cpp b/Runtime/Graphics/SubstanceArchive.cpp
new file mode 100644
index 0000000..33bc5a7
--- /dev/null
+++ b/Runtime/Graphics/SubstanceArchive.cpp
@@ -0,0 +1,121 @@
+#include "UnityPrefix.h"
+#include "SubstanceArchive.h"
+#include "SubstanceSystem.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/AssetPipeline/SubstanceImporter.h"
+#include "Editor/Src/AssetPipeline/SubstanceCache.h"
+#include "Editor/Src/EditorUserBuildSettings.h"
+#endif
+
+using namespace std;
+
+IMPLEMENT_CLASS_HAS_POSTINIT( SubstanceArchive )
+
+IMPLEMENT_OBJECT_SERIALIZE( SubstanceArchive )
+
+SubstanceArchive::SubstanceArchive( MemLabelId label, ObjectCreationMode mode )
+ : Super( label, mode )
+{
+#if ENABLE_SUBSTANCE
+ m_isPushed = false;
+ m_generatedGraphs.clear();
+ m_linkedBinaryData.clear();
+#endif
+}
+
+SubstanceArchive::~SubstanceArchive()
+{
+#if ENABLE_SUBSTANCE
+ GetSubstanceSystem().NotifyPackageDestruction(this);
+ for (std::map< UnityStr, UInt8* >::iterator i=m_linkedBinaryData.begin() ; i != m_linkedBinaryData.end() ; ++i)
+ {
+ UNITY_FREE(kMemSubstance, i->second);
+ }
+ m_linkedBinaryData.clear();
+#endif
+}
+
+void SubstanceArchive::AwakeFromLoad( AwakeFromLoadMode awakeMode )
+{
+ Super::AwakeFromLoad( awakeMode );
+}
+
+UInt8* SubstanceArchive::GetBufferData ()
+{
+ return &m_PackageData[0];
+}
+
+unsigned SubstanceArchive::GetBufferSize ()
+{
+ return m_PackageData.size();
+}
+
+#if ENABLE_SUBSTANCE
+bool SubstanceArchive::SaveLinkedBinaryData( const UnityStr& prototypeName, const UInt8* data, const int size)
+{
+ if (IsCloneDataAvailable(prototypeName))
+ {
+ WarningStringMsg("Trying to save linked substance data to a package that already has it");
+ return false;
+ }
+
+ UInt8* linkedBinaryData = (UInt8*) UNITY_MALLOC_ALIGNED_NULL(kMemSubstance, size, 32);
+ if (!linkedBinaryData)
+ {
+ WarningStringMsg("Could not allocate memory for a Substance package linked data");
+ return false;
+ }
+
+ memcpy( (void *) linkedBinaryData, (const void *) data, size);
+ m_linkedBinaryData[prototypeName] = linkedBinaryData;
+ return true;
+}
+
+UInt8* SubstanceArchive::GetLinkedBinaryData( const UnityStr& prototypeName ) const
+{
+ return (m_linkedBinaryData.find(prototypeName)->second);
+}
+
+bool SubstanceArchive::IsCloneDataAvailable( const UnityStr& prototypeName ) const
+{
+ return (m_linkedBinaryData.count(prototypeName) == 1);
+}
+#endif
+
+#if UNITY_EDITOR
+void SubstanceArchive::Init( const UInt8* _pPackage, unsigned int _PackageLength )
+{
+ // Copy the package content
+ m_PackageData.assign( _pPackage, _pPackage + _PackageLength );
+
+ AwakeFromLoad(kDefaultAwakeFromLoad);
+}
+#endif
+
+template<class T>
+void SubstanceArchive::Transfer( T& transfer )
+{
+ Super::Transfer( transfer );
+
+ transfer.Transfer( m_PackageData, "m_PackageData" );
+ transfer.Align();
+}
+
+void SubstanceArchive::PostInitializeClass ()
+{
+#if ENABLE_SUBSTANCE
+ g_SubstanceSystem = new SubstanceSystem();
+#endif
+}
+
+void SubstanceArchive::CleanupClass ()
+{
+#if ENABLE_SUBSTANCE
+ delete g_SubstanceSystem;
+#endif
+}
+
+#if ENABLE_SUBSTANCE
+SubstanceSystem* SubstanceArchive::g_SubstanceSystem = NULL;
+#endif
diff --git a/Runtime/Graphics/SubstanceArchive.h b/Runtime/Graphics/SubstanceArchive.h
new file mode 100644
index 0000000..ac49912
--- /dev/null
+++ b/Runtime/Graphics/SubstanceArchive.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "External/Allegorithmic/builds/Engines/include/substance/handle.h"
+
+class ColorRGBAf;
+class ColorRGBA32;
+class ProceduralTexture;
+class SubstanceSystem;
+
+/* A SubstanceArchive is a resource representation for an imported Substance package.
+ * It hosts the persisted binary version of the package that is either imported
+ * or reloaded from disk.
+ */
+
+
+class SubstanceArchive : public NamedObject
+{
+protected: // FIELDS
+
+ // The SBSAR package as a binary
+ dynamic_array<UInt8> m_PackageData;
+
+public: // METHODS
+
+ REGISTER_DERIVED_CLASS( SubstanceArchive, NamedObject )
+ DECLARE_OBJECT_SERIALIZE( SubstanceArchive )
+
+ SubstanceArchive( MemLabelId label, ObjectCreationMode mode );
+
+ // Reloads the package from disk
+ void AwakeFromLoad( AwakeFromLoadMode awakeMode );
+
+#if UNITY_EDITOR
+ // Creates the package from a binary SBSBIN file and the XML description file (one time call only when importing the SBSBIN file)
+ void Init( const UInt8* _pPackage, unsigned int _PackageLength );
+#endif
+
+ UInt8* GetBufferData ();
+ unsigned GetBufferSize ();
+
+#if ENABLE_SUBSTANCE
+public:
+ // Flag indicating whether the package's SBSASM has already pushed for linking
+ bool m_isPushed;
+ // Set of graph names that have already been generated from this package
+ std::set<UnityStr> m_generatedGraphs;
+
+ // Cache of single-package no-const-inputs SBSBIN data used for cloning
+public:
+ bool SaveLinkedBinaryData (const UnityStr& prototypeName, const UInt8* data, const int size);
+ UInt8* GetLinkedBinaryData (const UnityStr& prototypeName) const;
+ bool IsCloneDataAvailable (const UnityStr& prototypeName) const;
+private:
+ std::map< UnityStr, UInt8* > m_linkedBinaryData;
+#endif
+
+public:
+
+ // Substance system initialization
+ static void InitializeClass (){}
+ static void PostInitializeClass ();
+ static void CleanupClass ();
+
+#if ENABLE_SUBSTANCE
+ static SubstanceSystem& GetSubstanceSystem() { return *g_SubstanceSystem; }
+ static SubstanceSystem* GetSubstanceSystemPtr() { return g_SubstanceSystem; }
+private:
+ static SubstanceSystem* g_SubstanceSystem;
+#endif
+};
diff --git a/Runtime/Graphics/SubstanceInput.h b/Runtime/Graphics/SubstanceInput.h
new file mode 100644
index 0000000..bb0a661
--- /dev/null
+++ b/Runtime/Graphics/SubstanceInput.h
@@ -0,0 +1,243 @@
+#pragma once
+
+#include "Configuration/UnityConfigure.h"
+
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "External/Allegorithmic/builds/Engines/include/substance/inputdesc.h"
+#include "Texture2D.h"
+
+enum ProceduralPropertyType
+{
+ ProceduralPropertyType_Boolean = 0,
+ ProceduralPropertyType_Float,
+ ProceduralPropertyType_Vector2,
+ ProceduralPropertyType_Vector3,
+ ProceduralPropertyType_Vector4,
+ ProceduralPropertyType_Color3,
+ ProceduralPropertyType_Color4,
+ ProceduralPropertyType_Enum,
+ ProceduralPropertyType_Texture
+};
+
+inline bool IsSubstanceAnyFloatType (SubstanceInputType type) { return type >= Substance_IType_Float && type <= Substance_IType_Float4; }
+inline bool IsSubstanceAnyIntType (SubstanceInputType type) { return (type == Substance_IType_Integer) || (type == Substance_IType_Integer2) || (type == Substance_IType_Integer3) || (type == Substance_IType_Integer4); }
+inline int GetRequiredInputComponentCount (SubstanceInputType type)
+{
+ switch (type)
+ {
+ case Substance_IType_Float:
+ case Substance_IType_Integer:
+ return 1;
+ case Substance_IType_Float2:
+ case Substance_IType_Integer2:
+ return 2;
+ case Substance_IType_Float3:
+ case Substance_IType_Integer3:
+ return 3;
+ case Substance_IType_Float4:
+ case Substance_IType_Integer4:
+ return 4;
+ default:
+ AssertString("Not supported");
+ return -1;
+ }
+}
+
+struct SubstanceEnumItem
+{
+ DECLARE_SERIALIZE(SubstanceEnumItem)
+
+ int value;
+ UnityStr text;
+};
+
+struct SubstanceValue
+{
+ DECLARE_SERIALIZE(SubstanceValue)
+
+ float scalar[4];
+ PPtr<Texture2D> texture;
+
+ SubstanceValue() { memset(scalar, 0, sizeof(float)*4); }
+};
+
+struct SubstanceInput
+{
+ DECLARE_SERIALIZE(SubstanceInput)
+
+ // type & names
+ UnityStr name;
+ UnityStr label;
+ UnityStr group;
+ ProceduralPropertyType type;
+ SubstanceValue value;
+ SubstanceInputType internalType;
+ unsigned int internalIndex;
+ unsigned int internalIdentifier;
+ unsigned int shuffledIdentifier;
+
+ // Ranges and step
+ float minimum;
+ float maximum;
+ float step;
+
+ // Enum values
+ std::vector<SubstanceEnumItem> enumValues;
+ std::vector<std::string> GetEnumOptions() const
+ {
+ std::vector<std::string> enumOptions;
+ std::vector<SubstanceEnumItem>::const_iterator it;
+ for (it=enumValues.begin();it!=enumValues.end();++it)
+ {
+ enumOptions.push_back(it->text);
+ }
+ return enumOptions;
+ }
+
+ // Flags
+ enum Flag
+ {
+ Flag_SkipHint = 1<<0, // input is in head of the graph so the cache is not usefull
+ Flag_Modified = 1<<1, // input has been modified
+ Flag_Cached = 1<<2, // input is cached to speed up modifications
+ Flag_Awake = 1<<3, // this flag is set when AwakeFromLoad comes, this to rebuild all without having hints everywhere
+ Flag_Clamp = 1<<4 // needs to clamp value when set
+ };
+ unsigned int flags;
+ bool IsFlagEnabled(const Flag& flag) const { return flags & (unsigned int)flag; }
+ void EnableFlag(const Flag& flag, bool enabled=true) { if (enabled) flags |= (unsigned int)flag; else flags &= ~(unsigned int)flag; }
+
+ // Altered texture links
+ std::set<unsigned int> alteredTexturesUID;
+
+ SubstanceInput ()
+ {
+ type = ProceduralPropertyType_Float;
+ flags = SubstanceInput::Flag_Awake;
+ value.scalar[0] = value.scalar[1] = value.scalar[2] = value.scalar[3] = 0.0F;
+ internalType = Substance_IType_Float;
+ internalIndex = 0;
+ internalIdentifier = 0;
+
+ minimum = -std::numeric_limits<float>::max();
+ maximum = std::numeric_limits<float>::max();
+ }
+};
+
+inline void ClampSubstanceInputValues (const SubstanceInput& input, SubstanceValue& value)
+{
+ // validate combo value
+ if (input.type==ProceduralPropertyType_Enum && input.enumValues.size()>0)
+ {
+ std::vector<SubstanceEnumItem>::const_iterator it;
+ for (it=input.enumValues.begin();it!=input.enumValues.end();++it)
+ {
+ if ((int)value.scalar[0]==it->value)
+ return;
+ }
+ value.scalar[0] = (float)input.enumValues[0].value;
+ return;
+ }
+
+ // clamp scalar values
+ if (input.IsFlagEnabled(SubstanceInput::Flag_Clamp))
+ {
+ int count = GetRequiredInputComponentCount(input.internalType);
+
+ if (IsSubstanceAnyIntType(input.internalType))
+ {
+ for (int index=0;index<count;++index)
+ value.scalar[index] = (float)clamp<int>((int)(value.scalar[index]+0.5f), (int)input.minimum, (int)input.maximum);
+ }
+ else
+ {
+ for (int index=0;index<count;++index)
+ value.scalar[index] = clamp<float>(value.scalar[index], input.minimum, input.maximum);
+ }
+ } // step integers
+ else if (IsSubstanceAnyIntType(input.internalType))
+ {
+ int count = GetRequiredInputComponentCount(input.internalType);
+ for (int index=0;index<count;++index)
+ value.scalar[index] = (float)((int)(value.scalar[index]+0.5f));
+ }
+}
+
+inline bool AreSubstanceInputValuesEqual(SubstanceInputType type, SubstanceValue& first, SubstanceValue& second)
+{
+ if (IsSubstanceAnyFloatType(type))
+ {
+ return memcmp(first.scalar, second.scalar,
+ sizeof(float)*GetRequiredInputComponentCount(type))==0;
+ }
+ else if (IsSubstanceAnyIntType(type))
+ {
+ int count = GetRequiredInputComponentCount(type);
+
+ for (int index=0;index<count;++index)
+ {
+ if ((int)first.scalar[index]!=(int)second.scalar[index])
+ return false;
+ }
+ return true;
+ }
+ else if (type==Substance_IType_Image)
+ {
+ return first.texture==second.texture;
+ }
+ return false;
+}
+
+typedef std::vector<SubstanceInput> SubstanceInputs;
+
+template<class T>
+void SubstanceEnumItem::Transfer(T& transfer)
+{
+ TRANSFER(value);
+ TRANSFER(text);
+}
+
+template<class T>
+void SubstanceValue::Transfer(T& transfer)
+{
+ TRANSFER(scalar[0]);
+ TRANSFER(scalar[1]);
+ TRANSFER(scalar[2]);
+ TRANSFER(scalar[3]);
+ TRANSFER(texture);
+}
+
+template<class T>
+void SubstanceInput::Transfer(T& transfer)
+{
+ // type & names
+ TRANSFER(name);
+ TRANSFER(label);
+ TRANSFER(group);
+ transfer.Transfer(reinterpret_cast<int&> (type), "type");
+ TRANSFER(value);
+ transfer.Transfer(reinterpret_cast<int&> (internalType), "internalType");
+ TRANSFER(internalIndex);
+ TRANSFER(internalIdentifier);
+
+ // Range and step
+ TRANSFER(minimum);
+ TRANSFER(maximum);
+ TRANSFER(step);
+
+ // Flags
+ TRANSFER(flags);
+
+ // Altered texture links
+ TRANSFER(alteredTexturesUID);
+
+ // Enum
+ TRANSFER(enumValues);
+
+ // Update status
+ if (transfer.IsReading())
+ {
+ EnableFlag(Flag_Awake, true);
+ EnableFlag(Flag_Cached, false);
+ }
+}
diff --git a/Runtime/Graphics/SubstanceSystem.cpp b/Runtime/Graphics/SubstanceSystem.cpp
new file mode 100644
index 0000000..2231bbf
--- /dev/null
+++ b/Runtime/Graphics/SubstanceSystem.cpp
@@ -0,0 +1,876 @@
+#include "UnityPrefix.h"
+#include "SubstanceSystem.h"
+
+#if ENABLE_SUBSTANCE
+#include "ProceduralMaterial.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Misc/SystemInfo.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Serialize/LoadProgress.h"
+#include "Runtime/BaseClasses/ManagerContext.h"
+#include "Runtime/BaseClasses/GameManager.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/AssetPipeline/SubstanceImporter.h"
+#include "Editor/Src/Application.h"
+#endif
+
+using namespace std;
+
+int SubstanceSystem::s_maximumSubstancePerFrame = 40;
+
+SubstanceSystem::SubstanceSystem() :
+ processedSubstance(NULL),
+ outputCallback(NULL),
+ lastProcessedSubstance(NULL),
+ queryClearCache(NULL),
+ isIntegrating(false),
+ integrationTimeStamp(0)
+{
+ int Error = substanceContextInit( &m_Context, &m_Device );
+ Assert( !Error && "Failed to initialize blend platform context!" ); // We use the BLEND platform
+
+ // Setup memory callback
+ Error = substanceContextSetCallback( m_Context, Substance_Callback_Malloc, (void*) OnMalloc );
+ Assert( !Error && "Failed to setup malloc callback!" );
+ Error = substanceContextSetCallback( m_Context, Substance_Callback_Free, (void*) OnFree );
+ Assert( !Error && "Failed to setup free callback!" );
+
+ // Setup the callback for image inputs
+ Error = substanceContextSetCallback( m_Context, Substance_Callback_InputImageLock, (void*) OnInputImageLock );
+ Assert( !Error && "Failed to setup image lock callback!" );
+ Error = substanceContextSetCallback( m_Context, Substance_Callback_InputImageUnlock, (void*) OnInputImageUnlock );
+ Assert( !Error && "Failed to setup image unlock callback!" );
+ Error = substanceContextSetCallback( m_Context, Substance_Callback_OutOfMemory, (void*) OnOutOfMemory );
+ Assert( !Error && "Failed to setup out_of_memory callback!" );
+
+ SetOutputCallback();
+ processingThread.SetName ("UnitySubstanceThread");
+ processingThread.Run(ThreadMain, this);
+ SetProcessorUsage(ProceduralProcessorUsage_One);
+}
+
+SubstanceSystem::~SubstanceSystem()
+{
+ processingThread.SignalQuit();
+ threadSemaphore.Signal();
+ processingThread.WaitForExit();
+
+ if ( m_Context == NULL )
+ return; // Already released !
+
+ // Release the context
+ substanceContextRelease( m_Context );
+ m_Context = NULL;
+}
+
+void SubstanceSystem::SetOutputCallback(void* callback)
+{
+ outputCallback = callback==NULL?(void*)OnOutputCompleted:callback;
+ int err = substanceContextSetCallback( m_Context, Substance_Callback_OutputCompleted, outputCallback );
+ AssertMsg(err == 0, "Failed to sertup render callback!");
+}
+
+bool SubstanceSystem::AreQueuesEmpty()
+{
+ return integrationQueue.size()==0
+ && waitingSubstanceQueue.size()==0
+ && processingSubstanceQueue.size()==0
+ && processedSubstance==NULL
+ && updatingSubstanceQueue.size()==0;
+}
+
+bool SubstanceSystem::AreIntegratingQueuesEmpty()
+{
+ {
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ if (integrationQueue.size()>0)
+ for (std::vector<ProceduralMaterial*>::iterator it=integrationQueue.begin() ; it!=integrationQueue.end() ; ++it)
+ if ((*it)->integrationTimeStamp==integrationTimeStamp)
+ return false;
+ }
+
+ {
+ Mutex::AutoLock waitingLocker(waitingMutex);
+ if (waitingSubstanceQueue.size()>0)
+ for (std::list<Substance*>::iterator it=waitingSubstanceQueue.begin() ; it!=waitingSubstanceQueue.end() ; ++it)
+ if ((*it)->material->integrationTimeStamp==integrationTimeStamp)
+ return false;
+ }
+
+ {
+ Mutex::AutoLock processingLocker(processingMutex);
+ if (processingSubstanceQueue.size()>0)
+ for (std::list<Substance*>::iterator it=processingSubstanceQueue.begin() ; it!=processingSubstanceQueue.end() ; ++it)
+ if ((*it)->material->integrationTimeStamp==integrationTimeStamp)
+ return false;
+ }
+
+ {
+ Mutex::AutoLock processedLocker(processedMutex);
+ if (processedSubstance!=NULL && processedSubstance->material->integrationTimeStamp==integrationTimeStamp)
+ return false;
+ }
+
+ {
+ Mutex::AutoLock updatingLocker(updatingMutex);
+ if (updatingSubstanceQueue.size()>0)
+ for (std::list<Substance*>::iterator it=updatingSubstanceQueue.begin() ; it!=updatingSubstanceQueue.end() ; ++it)
+ if ((*it)->material->integrationTimeStamp==integrationTimeStamp)
+ return false;
+ }
+
+ return true;
+}
+
+bool SubstanceSystem::IsSubstanceProcessing(const ProceduralMaterial* substance)
+{
+ // Search into integration queue
+ {
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ std::vector<ProceduralMaterial*>::iterator it = std::find(integrationQueue.begin(), integrationQueue.end(), substance);
+ if (it!=integrationQueue.end())
+ return true;
+ }
+
+ // Search into processing queue
+ {
+ Mutex::AutoLock processingLocker(processingMutex);
+ std::list<Substance*>::iterator it = std::find_if(processingSubstanceQueue.begin(), processingSubstanceQueue.end(), QueueFinder(substance));
+ if (it!=processingSubstanceQueue.end())
+ return true;
+ }
+
+ // Is currently processing ?
+ {
+ Mutex::AutoLock processedLocker(processedMutex);
+ if (processedSubstance!=NULL && processedSubstance->material==substance)
+ return true;
+ }
+
+ // Search into updating queue
+ {
+ Mutex::AutoLock updatingLocker(updatingMutex);
+ std::list<Substance*>::iterator it = std::find_if(updatingSubstanceQueue.begin(), updatingSubstanceQueue.end(), QueueFinder(substance));
+ if (it!=updatingSubstanceQueue.end())
+ return true;
+ }
+
+ return false;
+}
+
+void SubstanceSystem::WaitFinished(LoadProgress* progress)
+{
+ isIntegrating = true;
+ int integrate_count = integrationQueue.size();
+ if (progress!=NULL)
+ {
+ progress->totalItems += integrate_count;
+ }
+
+ while (!AreIntegratingQueuesEmpty())
+ {
+ if (Thread::CurrentThreadIsMainThread())
+ Update();
+
+ if (progress!=NULL)
+ {
+ int new_count = integrationQueue.size();
+ progress->ItemProcessed(integrate_count-new_count);
+ integrate_count = integrationQueue.size();
+ }
+
+ Thread::Sleep(0.001);
+ }
+ isIntegrating = false;
+}
+
+void SubstanceSystem::WaitFinished(const ProceduralMaterial* substance)
+{
+ isIntegrating = true;
+ while (IsSubstanceProcessing(substance))
+ {
+ if (Thread::CurrentThreadIsMainThread())
+ Update();
+
+ Thread::Sleep(0.001);
+ }
+ isIntegrating = false;
+}
+
+void SubstanceSystem::Update(bool isQuitSignaled)
+{
+ if (!AreQueuesEmpty())
+ threadSemaphore.Signal();
+
+ // Clear waiting queue
+ {
+ Mutex::AutoLock waitingLocker(waitingMutex);
+ while (waitingSubstanceQueue.size()>0)
+ {
+ delete waitingSubstanceQueue.back();
+ waitingSubstanceQueue.pop_back();
+ }
+ }
+
+ // Update animated substances
+ if (!isIntegrating && !isQuitSignaled)
+ {
+ Mutex::AutoLock deleteLocker(deletePingMutex);
+ for (std::vector<ProceduralMaterial*>::iterator it=animatedSubstanceArray.begin();it!=animatedSubstanceArray.end();++it)
+ {
+ ProceduralMaterial *substance = *it;
+ substance->UpdateAnimation(GetTimeManager().GetCurTime());
+ }
+ }
+
+ // Upload updated substances
+ int uploadedCount = s_maximumSubstancePerFrame;
+ Substance* updatedSubstance;
+ while (uploadedCount-- && updatingSubstanceQueue.size()>0)
+ {
+ // Pick one if available
+ {
+ Mutex::AutoLock updatingLocker(updatingMutex);
+ std::list<Substance*>::iterator it = updatingSubstanceQueue.begin();
+ if (it==updatingSubstanceQueue.end())
+ break;
+ updatedSubstance = *it;
+ updatingSubstanceQueue.erase(it);
+ }
+
+ // Upload textures
+ {
+ //WarningStringMsg("SUBSTANCE: Uploading %d textures from %s", updatedSubstance->updatedTextures.size(), updatedSubstance->material->GetName());
+ for (std::map< ProceduralTexture*, SubstanceTexture >::iterator it=updatedSubstance->updatedTextures.begin();it!=updatedSubstance->updatedTextures.end();++it)
+ {
+ it->first->UploadSubstanceTexture(it->second);
+#if UNITY_EDITOR
+ SubstanceImporter::OnTextureModified(*updatedSubstance, *it->first, it->second);
+ //WarningStringMsg("SUBSTANCE: Uploading %s from material %s", it->first->GetName(), it->first->GetSubstanceMaterial()->GetName());
+#endif
+ }
+
+#if UNITY_EDITOR
+ SubstanceImporter::OnSubstanceModified(*updatedSubstance);
+#endif
+ for (std::map< ProceduralTexture*, SubstanceTexture >::iterator it=updatedSubstance->updatedTextures.begin();it!=updatedSubstance->updatedTextures.end();++it)
+ {
+ OnFree(it->second.buffer);
+ }
+ delete updatedSubstance;
+ }
+ }
+
+ if (isQuitSignaled)
+ {
+ // Clear processing queue
+ Mutex::AutoLock processingLocker(processingMutex);
+ for (std::list<Substance*>::iterator it=processingSubstanceQueue.begin();it!=processingSubstanceQueue.end();++it)
+ delete *it;
+ processingSubstanceQueue.clear();
+ }
+}
+
+void SubstanceSystem::UpdateThreaded()
+{
+ // Integrate loaded substances, if any
+ Integrate();
+
+ // Retrieve next substance to process
+ {
+ Mutex::AutoLock processingLocker(processingMutex);
+ std::list<Substance*>::iterator it = processingSubstanceQueue.begin();
+ if (it==processingSubstanceQueue.end())
+ return;
+
+ {
+ Mutex::AutoLock processedLocker(processedMutex);
+ processedSubstance = *it;
+ }
+ processingSubstanceQueue.erase(it);
+ }
+
+ // Set input values
+ for (std::map<std::string, SubstanceValue>::iterator i=processedSubstance->inputValues.begin();i!=processedSubstance->inputValues.end();++i)
+ {
+ processedSubstance->material->Callback_SetSubstanceInput(i->first, i->second);
+ }
+
+ // Clear cache if required
+ if (processedSubstance->material==queryClearCache)
+ {
+ ApplyMemoryBudget(processedSubstance->material, false);
+ queryClearCache = NULL;
+ }
+
+ // Generate textures
+ processedTextures.clear();
+ if (!processedSubstance->material->ProcessTexturesThreaded(processedSubstance->updatedTextures))
+ {
+ Mutex::AutoLock processedLocker(processedMutex);
+ delete processedSubstance;
+ processedSubstance = NULL;
+ return;
+ }
+
+ // Push substance into the updating queue
+ {
+ Mutex::AutoLock updatingLocker(updatingMutex);
+ updatingSubstanceQueue.push_back(processedSubstance);
+ }
+ // Clear
+ {
+ Mutex::AutoLock processedLocker(processedMutex);
+ processedSubstance = NULL;
+ }
+}
+
+void SubstanceSystem::QueueInput( ProceduralMaterial* material, std::string inputName, SubstanceValue& inputValue )
+{
+ // Skip if the material is broken
+ if (material->IsFlagEnabled(ProceduralMaterial::Flag_Broken))
+ return;
+
+ material->SetDirty();
+
+#if UNITY_EDITOR
+ SubstanceImporter::OnInputModified(*material, inputName, inputValue);
+#endif
+
+ // Directly apply value if not loaded
+ if (material->GetSubstanceHandle()==NULL)
+ {
+ material->Callback_SetSubstanceInput(inputName, inputValue);
+ return;
+ }
+
+ // Search if the substance is already in the process queue
+ {
+ Mutex::AutoLock locker(processingMutex);
+ std::list<Substance*>::iterator it = std::find_if(processingSubstanceQueue.begin(), processingSubstanceQueue.end(), QueueFinder(material));
+ if (it!=processingSubstanceQueue.end())
+ {
+ (*it)->inputValues[inputName] = inputValue;
+ return;
+ }
+ }
+
+ // Search if is already in the waiting queue
+ {
+ Mutex::AutoLock waitingLocker(waitingMutex);
+ std::list<Substance*>::iterator it = std::find_if(waitingSubstanceQueue.begin(), waitingSubstanceQueue.end(), QueueFinder(material));
+ if (it!=waitingSubstanceQueue.end())
+ {
+ (*it)->inputValues[inputName] = inputValue;
+ return;
+ }
+ }
+
+ // Search if is already in the integration queue
+ {
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ std::vector<ProceduralMaterial*>::iterator i = std::find(integrationQueue.begin(), integrationQueue.end(), material);
+ if (i!=integrationQueue.end())
+ {
+ material->Callback_SetSubstanceInput(inputName, inputValue);
+ return;
+ }
+ }
+
+ // Else add it in the waiting queue
+ Substance* substance = new Substance();
+ substance->material = material;
+ substance->inputValues[inputName] = inputValue;
+ {
+ Mutex::AutoLock waitingLocker(waitingMutex);
+ waitingSubstanceQueue.push_back(substance);
+ }
+}
+
+void SubstanceSystem::QueueSubstance(ProceduralMaterial* material)
+{
+ // Skip if the material is broken
+ if (material->IsFlagEnabled(ProceduralMaterial::Flag_Broken))
+ return;
+
+ if (material->GetSubstanceHandle()==NULL)
+ {
+ if (IsWorldPlaying() && material->GetLoadingBehavior()==ProceduralLoadingBehavior_None)
+ material->EnableFlag(ProceduralMaterial::Flag_ForceGenerate);
+ QueueLoading(material);
+ return;
+ }
+
+ // If it exists in the processing queue, input values are already pushed -> leave it as is
+ if (processingSubstanceQueue.size()>0)
+ {
+ Mutex::AutoLock locker(processingMutex);
+ std::list<Substance*>::iterator it = std::find_if(processingSubstanceQueue.begin(), processingSubstanceQueue.end(), QueueFinder(material));
+ if (it!=processingSubstanceQueue.end())
+ return;
+ }
+
+ // If it exists in the waiting queue, push it into the processing queue
+ if (waitingSubstanceQueue.size()>0)
+ {
+ Mutex::AutoLock waitingLocker(waitingMutex);
+ std::list<Substance*>::iterator it = std::find_if(waitingSubstanceQueue.begin(), waitingSubstanceQueue.end(), QueueFinder(material));
+ if (it!=waitingSubstanceQueue.end())
+ {
+ Mutex::AutoLock locker(processingMutex);
+ processingSubstanceQueue.push_back(*it);
+ waitingSubstanceQueue.erase(it);
+ return;
+ }
+ }
+
+ // Push it into the processing queue
+ Substance* substance = new Substance();
+ substance->material = material;
+ Mutex::AutoLock locker(processingMutex);
+ processingSubstanceQueue.push_back(substance);
+}
+
+void SubstanceSystem::QueueLoading(ProceduralMaterial* material)
+{
+ // Skip if the material is broken
+ if (material->IsFlagEnabled(ProceduralMaterial::Flag_Broken))
+ return;
+
+ // Already loaded ?
+ if (material->GetSubstanceHandle()!=NULL)
+ return;
+
+ // If it exists in the waiting queue, directly assign input values
+ {
+ Mutex::AutoLock waitingLocker(waitingMutex);
+ std::list<Substance*>::iterator it = std::find_if(waitingSubstanceQueue.begin(), waitingSubstanceQueue.end(), QueueFinder(material));
+ if (it!=waitingSubstanceQueue.end())
+ {
+#if UNITY_EDITOR
+ material->EnableFlag(ProceduralMaterial::Flag_Awake, false);
+#endif
+ for (std::map<std::string, SubstanceValue>::iterator i=(*it)->inputValues.begin() ; i!=(*it)->inputValues.end() ; ++i)
+ material->Callback_SetSubstanceInput(i->first, i->second);
+ delete *it;
+ waitingSubstanceQueue.erase(it);
+ }
+ }
+
+ // Push it to the integrationQueue if it isn't
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ std::vector<ProceduralMaterial*>::iterator i = std::find(integrationQueue.begin(), integrationQueue.end(), material);
+ if (i==integrationQueue.end())
+ integrationQueue.push_back(material);
+}
+
+void SubstanceSystem::QueryClearCache(ProceduralMaterial* material)
+{
+ queryClearCache = material;
+ QueueSubstance(material);
+}
+
+void* SubstanceSystem::ThreadMain(void*data)
+{
+ SubstanceSystem* system = static_cast<SubstanceSystem*>(data);
+ while (!system->processingThread.IsQuitSignaled())
+ {
+ system->threadSemaphore.WaitForSignal();
+ system->threadSemaphore.Reset();
+
+ for (int i=0 ; i<s_maximumSubstancePerFrame ; ++i)
+ system->UpdateThreaded();
+ }
+ return NULL;
+}
+
+#define SUBSTANCE_TRACE_MEM_CALLBACKS 0
+
+void* SUBSTANCE_CALLBACK SubstanceSystem::OnMalloc(size_t bytesCount, size_t alignment)
+{
+ void *ptr = UNITY_MALLOC_ALIGNED_NULL(kMemSubstance, bytesCount, alignment);
+ if (!ptr)
+ {
+ ErrorString("Could not allocate memory in OnMalloc (SubstanceSystem)");
+ }
+#if SUBSTANCE_TRACE_MEM_CALLBACKS
+ Substance *substance = GetSubstanceSystem().processedSubstance;
+ WarningStringMsg("\n%08X %d Alloc %s", ptr, bytesCount, (substance != NULL ? substance->material->GetName() : "No Substance"));
+#endif
+ return ptr;
+}
+
+void SUBSTANCE_CALLBACK SubstanceSystem::OnFree(void* bufferPtr)
+{
+#if SUBSTANCE_TRACE_MEM_CALLBACKS
+ Substance *substance = GetSubstanceSystem().processedSubstance;
+ WarningStringMsg("\n%08X Free %s", bufferPtr, (substance != NULL ? substance->material->GetName() : "No Substance"));
+#endif
+ UNITY_FREE(kMemSubstance, bufferPtr);
+}
+
+void SUBSTANCE_CALLBACK SubstanceSystem::OnOutputCompleted( SubstanceHandle* _pHandle, unsigned int _OutputIndex, size_t _JobUserData )
+{
+ SubstanceOutputDesc outputDesc;
+ if (substanceHandleGetOutputDesc(_pHandle, _OutputIndex, &outputDesc)==0)
+ {
+ std::map<unsigned int, ProceduralTexture*>::iterator it;
+ it = GetSubstanceSystem ().processedTextures.find(outputDesc.outputId);
+ if (it!=GetSubstanceSystem ().processedTextures.end())
+ {
+ ProceduralTexture* texture = it->second;
+ Substance* substance = GetSubstanceSystem().processedSubstance;
+ Assert( substance!=NULL && "Failed to update substance!" );
+ //WarningStringMsg("SUBSTANCE: OnOutputCompleted received texture %s from substance %s", it->second->GetName(), substance->material->GetName());
+ if (substanceHandleGetOutputs(substance->material->GetSubstanceHandle(), Substance_OutOpt_TextureId, texture->GetSubstanceTextureUID(), 1, &substance->updatedTextures[texture])!=0)
+ {
+ ErrorStringObject("Failed to retrieve substance texture data", substance->material);
+ return;
+ }
+ }
+ }
+}
+
+void SUBSTANCE_CALLBACK SubstanceSystem::OnInputImageLock( SubstanceHandle* _pHandle, size_t _JobUserData, unsigned int _InputIndex, SubstanceTextureInput** _ppCurrentTextureInputDesc, const SubstanceTextureInput* _pPreferredTextureInputDesc )
+{
+ Substance* substance = GetSubstanceSystem().processedSubstance;
+ Assert( substance!=NULL && "Failed to update substance input texture!" );
+ substance->material->ApplyTextureInput(_InputIndex, *_pPreferredTextureInputDesc);
+}
+
+void SUBSTANCE_CALLBACK SubstanceSystem::OnInputImageUnlock( SubstanceHandle* _pHandle, size_t _JobUserData, unsigned int _InputIndex, SubstanceTextureInput* _ppCurrentTextureInputDesc )
+{
+}
+
+void SUBSTANCE_CALLBACK SubstanceSystem::OnOutOfMemory(SubstanceHandle *handle, int memoryType)
+{
+ Substance* substance = GetSubstanceSystem().processedSubstance;
+ if (substance->material->GetProceduralMemoryWorkBudget()!=ProceduralCacheSize_NoLimit)
+ {
+ GetSubstanceSystem().ApplyMemoryBudget(substance->material, true, true);
+ }
+}
+
+void SubstanceSystem::NotifySubstanceCreation(ProceduralMaterial* substance)
+{
+ // Add it into the animated substance array if needed
+ if (substance->IsFlagEnabled(ProceduralMaterial::Flag_Animated))
+ {
+ if (std::find(animatedSubstanceArray.begin(), animatedSubstanceArray.end(), substance)==animatedSubstanceArray.end())
+ {
+ animatedSubstanceArray.push_back(substance);
+ }
+ }
+}
+
+void SubstanceSystem::NotifySubstanceDestruction(ProceduralMaterial* substance)
+{
+ // Remove it from animated array if in
+ {
+ if (substance->IsFlagEnabled(ProceduralMaterial::Flag_Animated))
+ {
+ Mutex::AutoLock deletePingLocker(deletePingMutex);
+ std::vector<ProceduralMaterial*>::iterator i(std::find(animatedSubstanceArray.begin(), animatedSubstanceArray.end(), substance));
+ if (i!=animatedSubstanceArray.end())
+ animatedSubstanceArray.erase(i);
+ }
+ }
+
+ // Remove it from integration array if in
+ {
+ Mutex::AutoLock deletePingLocker(deletePingMutex);
+ Mutex::AutoLock deleteIntegrationLocker(deleteIntegrationMutex);
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ std::vector<ProceduralMaterial*>::iterator i = std::find(integrationQueue.begin(), integrationQueue.end(), substance);
+ if (i!=integrationQueue.end())
+ integrationQueue.erase(i);
+ }
+
+ // Remove it from processing array if in
+ {
+ Mutex::AutoLock processingLocker(processingMutex);
+ std::list<Substance*>::iterator it;
+ while((it=std::find_if(processingSubstanceQueue.begin(), processingSubstanceQueue.end(), QueueFinder(substance)))!=processingSubstanceQueue.end())
+ {
+ delete *it;
+ processingSubstanceQueue.erase(it);
+ }
+ }
+
+ // Wait if it's processing
+ while(1)
+ {
+ {
+ Mutex::AutoLock processedLocker(processedMutex);
+ if (processedSubstance==NULL || substance->GetSubstanceData()!=processedSubstance->material->GetSubstanceData())
+ break;
+ }
+ Thread::Sleep(0.001);
+ }
+
+ // Remove it from updating array if in
+ {
+ Mutex::AutoLock updatingLocker(updatingMutex);
+ std::list<Substance*>::iterator it = std::find_if(updatingSubstanceQueue.begin(), updatingSubstanceQueue.end(), QueueFinder(substance));
+ if (it!=updatingSubstanceQueue.end())
+ {
+ // Delete the output buffers from the Substance if they have not yet been pushed to the GPU
+ // This can happen when building for example, where very quick cycles of load/generate/discard are performed
+ for (std::map<ProceduralTexture*,SubstanceTexture>::iterator itTex = (*it)->updatedTextures.begin() ;
+ itTex != (*it)->updatedTextures.end() ; ++itTex)
+ {
+ OnFree(itTex->second.buffer);
+ }
+
+ delete *it;
+ updatingSubstanceQueue.erase(it);
+ }
+ }
+
+ // Clear last processed if needed
+ {
+ Mutex::AutoLock lastSubstanceLocker(lastProcessedSubstanceMutex);
+ if (substance==lastProcessedSubstance)
+ lastProcessedSubstance = NULL;
+ }
+}
+
+void SubstanceSystem::NotifyTextureDestruction(ProceduralTexture* texture)
+{
+ if (texture->GetSubstanceMaterial()!=NULL)
+ NotifySubstanceDestruction(texture->GetSubstanceMaterial());
+}
+
+void SubstanceSystem::NotifyPackageDestruction(SubstanceArchive* package)
+{
+ // Remove it from animated array if in
+ {
+ Mutex::AutoLock deletePingLocker(deletePingMutex);
+ std::vector<ProceduralMaterial*>::iterator it;
+ while((it=std::find_if(animatedSubstanceArray.begin(), animatedSubstanceArray.end(), ArrayPackageFinder(package)))!=animatedSubstanceArray.end())
+ {
+ animatedSubstanceArray.erase(it);
+ }
+ }
+
+ // Remove it from integration array if in
+ {
+ Mutex::AutoLock deletePingLocker(deletePingMutex);
+ Mutex::AutoLock deleteIntegrationLocker(deleteIntegrationMutex);
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ std::vector<ProceduralMaterial*>::iterator i;
+ while((i=std::find_if(integrationQueue.begin(), integrationQueue.end(), ArrayPackageFinder(package)))!=integrationQueue.end())
+ integrationQueue.erase(i);
+ }
+
+ // Remove it from processing array if in
+ {
+ Mutex::AutoLock processingLocker(processingMutex);
+ std::list<Substance*>::iterator it;
+ while((it=std::find_if(processingSubstanceQueue.begin(), processingSubstanceQueue.end(), QueuePackageFinder(package)))!=processingSubstanceQueue.end())
+ {
+ delete *it;
+ processingSubstanceQueue.erase(it);
+ }
+ }
+
+ // Wait if it's processing
+ while(1)
+ {
+ {
+ Mutex::AutoLock processedLocker(processedMutex);
+ if (processedSubstance==NULL || package!=processedSubstance->material->GetSubstancePackage())
+ break;
+ }
+ Thread::Sleep(0.001);
+ }
+
+ // Remove it from updating array if in
+ {
+ Mutex::AutoLock updatingLocker(updatingMutex);
+ std::list<Substance*>::iterator it;
+ while((it=std::find_if(updatingSubstanceQueue.begin(), updatingSubstanceQueue.end(), QueuePackageFinder(package)))!=updatingSubstanceQueue.end())
+ {
+ // Delete the output buffers from the Substance if they have not yet been pushed to the GPU
+ // This can happen when building for example, where very quick cycles of load/generate/discard are performed
+ for (std::map<ProceduralTexture*,SubstanceTexture>::iterator itTex = (*it)->updatedTextures.begin() ;
+ itTex != (*it)->updatedTextures.end() ; ++itTex)
+ {
+ OnFree(itTex->second.buffer);
+ }
+
+ delete *it;
+ updatingSubstanceQueue.erase(it);
+ }
+ }
+
+ // Clear last processed if needed
+ {
+ Mutex::AutoLock lastSubstanceLocker(lastProcessedSubstanceMutex);
+ if (lastProcessedSubstance!=NULL && package==lastProcessedSubstance->GetSubstancePackage())
+ lastProcessedSubstance = NULL;
+ }
+}
+
+void SubstanceSystem::SetProcessorUsage(ProceduralProcessorUsage usage)
+{
+ m_ProcessorUsage = usage;
+
+ switch(m_ProcessorUsage)
+ {
+ case ProceduralProcessorUsage_Half:
+ processingThread.SetPriority(kNormalPriority);
+ break;
+ case ProceduralProcessorUsage_All:
+ processingThread.SetPriority(kNormalPriority);
+ break;
+ default:
+ processingThread.SetPriority(kLowPriority);
+ }
+}
+
+void SubstanceSystem::ClearProcessingQueue()
+{
+ Mutex::AutoLock processingLocker(processingMutex);
+ for (std::list<Substance*>::iterator it=processingSubstanceQueue.begin() ; it!=processingSubstanceQueue.end() ; ++it)
+ {
+ delete *it;
+ }
+ processingSubstanceQueue.clear();
+}
+
+SubstanceSystem::Context::Context(ProceduralProcessorUsage usage)
+{
+ m_OldProcessorUsage = GetSubstanceSystem().GetProcessorUsage();
+ GetSubstanceSystem().SetProcessorUsage(usage);
+}
+
+SubstanceSystem::Context::~Context()
+{
+ GetSubstanceSystem().SetProcessorUsage(m_OldProcessorUsage);
+}
+
+void SubstanceSystem::ForceSubstanceResults(std::map<ProceduralTexture*, SubstanceTexture>& results)
+{
+ Assert( processedSubstance!=NULL && "Substance caching incorrect behavior!" );
+ processedSubstance->updatedTextures = results;
+}
+
+void SubstanceSystem::UpdateMemoryBudget()
+{
+ Mutex::AutoLock lastSubstanceLocker(lastProcessedSubstanceMutex);
+ if (lastProcessedSubstance!=NULL &&
+ lastProcessedSubstance->GetSubstanceHandle()!=processedSubstance->material->GetSubstanceHandle())
+ {
+ //WarningStringMsg("Emptying Substance cache for handle %p, for substance %s\n", lastProcessedSubstance->GetSubstanceHandle(), lastProcessedSubstance->GetName());
+ ApplyMemoryBudget(lastProcessedSubstance, false);
+ }
+
+ lastProcessedSubstance = processedSubstance->material;
+ ApplyMemoryBudget(lastProcessedSubstance, true);
+}
+
+void SubstanceSystem::ApplyMemoryBudget(ProceduralMaterial* substance, bool requireMaximum, bool outOfMemory)
+{
+ if (substance->GetSubstanceHandle()==NULL)
+ {
+ // The substance is not loaded now, so skip it.
+ return;
+ }
+
+ // Always use tiny memory budget in the editor if the material is loading, this to lower memory usage.
+ // Only do this when NOT playing (in Play mode, the runtime-set mem. budget must be used)
+#if UNITY_EDITOR
+ if (!IsWorldPlaying() && requireMaximum && substance->IsFlagEnabled(ProceduralMaterial::Flag_Awake))
+ substance->SetProceduralMemoryWorkBudget(ProceduralCacheSize_Tiny);
+#endif
+
+ SubstanceHardResources memoryBudget;
+ memset(&memoryBudget, 0, sizeof(SubstanceHardResources));
+ int processorCount = max(systeminfo::GetProcessorCount(), 1);
+ int mediumLimit = max(processorCount/2, 1);
+
+ for (int index=0;index<SUBSTANCE_CPU_COUNT_MAX;++index)
+ {
+ unsigned char processorUsage(Substance_Resource_FullUse);
+
+ if ((m_ProcessorUsage==ProceduralProcessorUsage_Half && index>=mediumLimit)
+ || (m_ProcessorUsage==ProceduralProcessorUsage_One && index>0))
+ {
+ processorUsage = Substance_Resource_DoNotUse;
+ }
+
+ memoryBudget.cpusUse[index] = processorUsage;
+ }
+
+ if (outOfMemory)
+ {
+ substance->SetProceduralMemoryWorkBudget(
+ (ProceduralCacheSize)((int)substance->GetProceduralMemoryWorkBudget()+1));
+ }
+
+ size_t workSize = GetProceduralMemoryBudget(substance->GetProceduralMemoryWorkBudget());
+ if (workSize==1)
+ workSize = 128 * 1024 * 1024;
+ size_t sleepSize = GetProceduralMemoryBudget(substance->GetProceduralMemorySleepBudget());
+
+ memoryBudget.systemMemoryBudget = requireMaximum?workSize:sleepSize;
+
+ if (substanceHandleSwitchHard(substance->GetSubstanceHandle(), Substance_Sync_Synchronous, &memoryBudget, NULL, 0)!=0)
+ ErrorStringObject("Failed to set substance memory budget", substance);
+
+ if (!outOfMemory)
+ {
+ if (substanceHandleStart(substance->GetSubstanceHandle(), Substance_Sync_Synchronous)!=0)
+ ErrorStringObject("Failed to update substance memory budget", substance);
+ }
+}
+
+void SubstanceSystem::Integrate()
+{
+ if (integrationQueue.size()>0)
+ {
+ Mutex::AutoLock deleteLocker(deleteIntegrationMutex);
+
+ // Retrieve materials to pack
+ std::vector<ProceduralMaterial*> packedMaterials;
+ {
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ packedMaterials = integrationQueue;
+ }
+
+ // Pack all available substances
+ ProceduralMaterial::PackSubstances(packedMaterials);
+
+ for (std::vector<ProceduralMaterial*>::iterator it=packedMaterials.begin() ; it!=packedMaterials.end() ; ++it)
+ {
+ if (!(*it)->IsFlagEnabled(ProceduralMaterial::Flag_Broken)
+ && ((*it)->IsFlagEnabled(ProceduralMaterial::Flag_ForceGenerate)
+ || !IsWorldPlaying() || (*it)->GetLoadingBehavior()!=ProceduralLoadingBehavior_None))
+ {
+ QueueSubstance(*it);
+ queryClearCache = *it;
+ }
+
+ // Remove it from the integrationQueue
+ Mutex::AutoLock integrationLocker(integrationMutex);
+ std::vector<ProceduralMaterial*>::iterator i = std::find(integrationQueue.begin(), integrationQueue.end(), *it);
+ integrationQueue.erase(i);
+ }
+ }
+}
+
+SubstanceSystem& GetSubstanceSystem ()
+{
+ return SubstanceArchive::GetSubstanceSystem();
+}
+
+SubstanceSystem* GetSubstanceSystemPtr ()
+{
+ return SubstanceArchive::GetSubstanceSystemPtr();
+}
+
+
+#endif //ENABLE_SUBSTANCE
diff --git a/Runtime/Graphics/SubstanceSystem.h b/Runtime/Graphics/SubstanceSystem.h
new file mode 100644
index 0000000..e87b49d
--- /dev/null
+++ b/Runtime/Graphics/SubstanceSystem.h
@@ -0,0 +1,160 @@
+#pragma once
+
+#include "ProceduralMaterial.h"
+#include "SubstanceArchive.h"
+#include "Texture2D.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Threads/Mutex.h"
+#include "Runtime/Threads/Semaphore.h"
+#include <queue>
+#include <algorithm>
+
+#if ENABLE_SUBSTANCE
+#include "External/Allegorithmic/builds/Engines/include/substance/handle.h"
+#include "External/Allegorithmic/builds/Engines/include/substance/device.h"
+
+class LoadProgress;
+
+class SubstanceSystem
+{
+ SubstanceDevice m_Device;
+ SubstanceContext* m_Context;
+
+public:
+
+ SubstanceSystem ();
+ ~SubstanceSystem ();
+
+ SubstanceContext* GetContext () { return m_Context; }
+
+ // Set the callback handling generated texture
+ // The default substance system is used when set to NULL
+ void SetOutputCallback(void* callback=NULL);
+
+ // State queries
+ bool AreQueuesEmpty();
+ bool AreIntegratingQueuesEmpty();
+ bool IsSubstanceProcessing(const ProceduralMaterial* substance);
+
+ // State wait
+ void WaitFinished(LoadProgress* progress = NULL);
+ void WaitFinished(const ProceduralMaterial* substance);
+
+ // Update substance system, called from the main thread
+ void Update(bool isQuitSignaled=false);
+
+ // Update substance system, called from the substance thread
+ // . generate queued substances
+ // . integrate substances
+ void UpdateThreaded();
+
+ // Queued updates coming from ProceduralMaterial
+ void QueueInput(ProceduralMaterial* material, std::string inputName, SubstanceValue& inputValue);
+ void QueueSubstance(ProceduralMaterial* material);
+ void QueueLoading(ProceduralMaterial* material);
+
+ void QueryClearCache(ProceduralMaterial* material);
+
+ static void * ThreadMain(void*data);
+
+ // Substance callbacks
+ static void* SUBSTANCE_CALLBACK OnMalloc(size_t bytesCount, size_t alignment=16);
+ static void SUBSTANCE_CALLBACK OnFree(void* bufferPtr);
+ static void SUBSTANCE_CALLBACK OnOutputCompleted(SubstanceHandle* _pHandle, unsigned int _OutputIndex, size_t _JobUserData);
+ static void SUBSTANCE_CALLBACK OnInputImageLock(SubstanceHandle* _pHandle, size_t _JobUserData, unsigned int _InputIndex, SubstanceTextureInput** _ppCurrentTextureInputDesc, const SubstanceTextureInput* _pPreferredTextureInputDesc);
+ static void SUBSTANCE_CALLBACK OnInputImageUnlock(SubstanceHandle* _pHandle, size_t _JobUserData, unsigned int _InputIndex, SubstanceTextureInput* _ppCurrentTextureInputDesc);
+ static void SUBSTANCE_CALLBACK OnOutOfMemory(SubstanceHandle *handle, int memoryType);
+
+ // Substance notifications
+ void NotifySubstanceCreation(ProceduralMaterial* substance);
+ void NotifySubstanceDestruction(ProceduralMaterial* substance);
+ void NotifyTextureDestruction(ProceduralTexture* texture);
+ void NotifyPackageDestruction(SubstanceArchive* package);
+
+ // Priority handling
+ void SetProcessorUsage(ProceduralProcessorUsage usage);
+ ProceduralProcessorUsage GetProcessorUsage() const { return m_ProcessorUsage; }
+ void ClearProcessingQueue();
+
+ // Queued substance data
+ struct Substance
+ {
+ ProceduralMaterial* material;
+ std::map<std::string, SubstanceValue> inputValues;
+ std::map<ProceduralTexture*, SubstanceTexture> updatedTextures;
+ };
+ std::map<unsigned int, ProceduralTexture*> processedTextures;
+ void ForceSubstanceResults(std::map<ProceduralTexture*, SubstanceTexture>& results);
+
+ // Memory budget management
+ void UpdateMemoryBudget();
+
+ // Context of generation which temporary set processor usage
+ class Context
+ {
+ public:
+ Context(ProceduralProcessorUsage usage);
+ ~Context();
+ private:
+ ProceduralProcessorUsage m_OldProcessorUsage;
+ };
+
+ void BeginPreloading() { ++integrationTimeStamp; }
+ unsigned int integrationTimeStamp;
+
+private:
+ void ApplyMemoryBudget(ProceduralMaterial* substance, bool requireMaximum, bool outOfMemory=false);
+
+ // Threaded integration / packing of substances
+ void Integrate();
+
+ // Substance queue helpers
+ struct QueueFinder
+ {
+ const ProceduralMaterial* m_Material;
+ QueueFinder(const ProceduralMaterial* material) : m_Material(material) {}
+ bool operator()(const Substance* substance) { return substance->material == m_Material; }
+ };
+
+ struct QueuePackageFinder
+ {
+ const SubstanceArchive* m_Package;
+ QueuePackageFinder(const SubstanceArchive* package) : m_Package(package) {}
+ bool operator()(const Substance* substance) { return substance->material->GetSubstancePackage()==m_Package; }
+ };
+
+ struct ArrayPackageFinder
+ {
+ const SubstanceArchive* m_Package;
+ ArrayPackageFinder(const SubstanceArchive* package) : m_Package(package) {}
+ bool operator()(const ProceduralMaterial* substance) { return substance->GetSubstancePackage()==m_Package; }
+ };
+
+ std::list<Substance*> waitingSubstanceQueue;
+ std::list<Substance*> processingSubstanceQueue;
+ std::list<Substance*> updatingSubstanceQueue;
+ std::vector<ProceduralMaterial*> integrationQueue;
+ Thread processingThread;
+ Mutex processingMutex;
+ Mutex updatingMutex;
+ Mutex processedMutex;
+ Mutex integrationMutex;
+ Mutex waitingMutex;
+ Substance* processedSubstance;
+ void* outputCallback;
+ ProceduralMaterial* lastProcessedSubstance;
+ Mutex lastProcessedSubstanceMutex;
+ std::vector<ProceduralMaterial*> animatedSubstanceArray;
+ ProceduralProcessorUsage m_ProcessorUsage;
+ ProceduralMaterial* queryClearCache;
+ Mutex deleteIntegrationMutex;
+ Mutex deletePingMutex;
+ bool isIntegrating;
+ Semaphore threadSemaphore;
+ static int s_maximumSubstancePerFrame;
+};
+
+SubstanceSystem& GetSubstanceSystem ();
+SubstanceSystem* GetSubstanceSystemPtr ();
+
+#endif
diff --git a/Runtime/Graphics/Texture.cpp b/Runtime/Graphics/Texture.cpp
new file mode 100644
index 0000000..a343da4
--- /dev/null
+++ b/Runtime/Graphics/Texture.cpp
@@ -0,0 +1,332 @@
+#include "UnityPrefix.h"
+#include "Texture.h"
+#include "Image.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "External/shaderlab/Library/texenv.h"
+#include "Runtime/Graphics/ProceduralMaterial.h"
+
+using namespace std;
+
+static int gTextureBaseLevel = 0;
+static int gAnisoSetting = Texture::kEnableAniso;
+
+static const int kForceAnisoMinLevelDefault = 9;
+static const int kAnisoMaxLevelDefault = 16;
+
+static int gForceAnisoMinLevel = kForceAnisoMinLevelDefault;
+static int gAnisoMaxLevel = kAnisoMaxLevelDefault;
+
+
+Texture::TextureIDMap Texture::s_TextureIDMap;
+
+
+///@todo: texture should not allocate memory based on texture base level.
+
+Texture::Texture(MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode), m_UVScaleX(1.0f), m_UVScaleY(1.0f), m_TexelSizeX(1.0f), m_TexelSizeY(1.0f)
+{
+ // We use unchecked version since we may not be on the main thread
+ // This means CreateTextureID() implementation must be thread safe!
+ m_TexID = GetUncheckedGfxDevice().CreateTextureID();
+ m_UsageMode = kTexUsageNone;
+ m_ColorSpace = kTexColorSpaceLinear;
+}
+
+Texture::~Texture ()
+{
+ MainThreadCleanup ();
+}
+
+bool Texture::MainThreadCleanup ()
+{
+ Texture::s_TextureIDMap.erase (m_TexID);
+
+ // FreeTextureID() implementation must be thread safe!
+ GetUncheckedGfxDevice().FreeTextureID(m_TexID);
+ m_TexID = TextureID();
+
+ // Notify TexEnvs using this texture
+ for( size_t i = 0; i < m_TexEnvUsers.size(); ++i )
+ {
+ ASSERT_RUNNING_ON_MAIN_THREAD
+ m_TexEnvUsers[i]->SetUsedTexture(NULL, 0);
+ }
+ m_TexEnvUsers.clear();
+ return true;
+}
+
+
+void Texture::CheckConsistency()
+{
+ Super::CheckConsistency();
+ m_TextureSettings.CheckConsistency ();
+}
+
+void Texture::Reset ()
+{
+ Super::Reset ();
+ m_UsageMode = kTexUsageNone;
+ m_ColorSpace = kTexColorSpaceLinear;
+}
+
+void Texture::SetMasterTextureLimit (int i, bool reloadTextures /*= true*/)
+{
+ if (gTextureBaseLevel == i)
+ return;
+
+ gTextureBaseLevel = i;
+ if (!reloadTextures)
+ return;
+
+ vector<SInt32> objects;
+ Object::FindAllDerivedObjects (ClassID (Texture), &objects);
+ for( size_t j = 0; j < objects.size(); ++j )
+ {
+ Texture& tex = *PPtr<Texture> (objects[j]);
+#if UNITY_EDITOR
+ if (tex.IgnoreMasterTextureLimit ())
+ continue;
+#endif
+ tex.UnloadFromGfxDevice(false);
+ tex.UploadToGfxDevice();
+ }
+
+ ProceduralMaterial::ReloadAll();
+}
+
+void Texture::ReloadAll (bool unload, bool load, bool forceUnloadAll)
+{
+ vector<SInt32> objects;
+ Object::FindAllDerivedObjects (ClassID (Texture), &objects, true);
+ for (size_t i=0;i<objects.size ();++i)
+ {
+ Texture& tex = *PPtr<Texture> (objects[i]);
+
+ if (unload)
+ tex.UnloadFromGfxDevice(forceUnloadAll);
+ if (load)
+ tex.UploadToGfxDevice();
+ }
+
+ ProceduralMaterial::ReloadAll(unload, load);
+}
+
+static void SetAnisoLimitEnumImpl(int aniso, bool forced = false)
+{
+ if (aniso == gAnisoSetting && !forced)
+ return;
+
+ gAnisoSetting = aniso;
+ if (gAnisoSetting == Texture::kDisableAniso)
+ TextureSettings::SetAnisoLimits (1, 1);
+ else if (gAnisoSetting == Texture::kForceEnableAniso)
+ TextureSettings::SetAnisoLimits (gForceAnisoMinLevel, gAnisoMaxLevel);
+ else
+ TextureSettings::SetAnisoLimits (1, gAnisoMaxLevel);
+
+ vector<Texture*> objects;
+ Object::FindObjectsOfType (&objects);
+ for (int i=0;i<objects.size ();i++)
+ objects[i]->ApplySettings ();
+}
+
+void Texture::SetAnisoLimit (int aniso)
+{
+ SetAnisoLimitEnumImpl(aniso, false);
+}
+
+
+void Texture::SetGlobalAnisoLimits(int forcedMin, int globalMax)
+{
+ if(forcedMin == -1)
+ forcedMin = kForceAnisoMinLevelDefault;
+ if(globalMax == -1)
+ globalMax = kAnisoMaxLevelDefault;
+
+ if(gForceAnisoMinLevel == forcedMin && gAnisoMaxLevel == globalMax)
+ return;
+
+ gForceAnisoMinLevel = forcedMin;
+ gAnisoMaxLevel = globalMax;
+
+ SetAnisoLimitEnumImpl(gAnisoSetting, true);
+}
+
+int Texture::GetAnisoLimit ()
+{
+ return gAnisoSetting;
+}
+
+bool Texture::HasMipMap () const {return false;}
+
+void Texture::ApplySettings()
+{
+ m_TextureSettings.Apply( GetTextureID(), GetDimension(), HasMipMap(), GetActiveTextureColorSpace() );
+ NotifyMipBiasChanged();
+}
+
+void Texture::AddTexEnvUser(ShaderLab::TexEnv* texenv)
+{
+ SET_ALLOC_OWNER(this);
+ size_t index = m_TexEnvUsers.size();
+ texenv->SetUsedTexture(this, index);
+ m_TexEnvUsers.push_back(texenv);
+}
+
+void Texture::RemoveTexEnvUser(ShaderLab::TexEnv* texenv, size_t index)
+{
+ DebugAssert(m_TexEnvUsers[index] == texenv);
+ // Swap with last element and pop
+ m_TexEnvUsers[index] = m_TexEnvUsers.back();
+ m_TexEnvUsers[index]->SetUsedTexture(this, index);
+ m_TexEnvUsers.pop_back();
+ texenv->SetUsedTexture(NULL, 0);
+}
+
+void Texture::NotifyMipBiasChanged()
+{
+ float mipBias = m_TextureSettings.m_MipBias;
+ for( size_t i = 0; i < m_TexEnvUsers.size(); ++i )
+ {
+ ShaderLab::TexEnv* te = m_TexEnvUsers[i];
+ te->TextureMipBiasChanged( mipBias );
+ }
+}
+
+void Texture::NotifyUVScaleChanged()
+{
+ float x = m_UVScaleX;
+ float y = m_UVScaleY;
+ for( size_t i = 0; i < m_TexEnvUsers.size(); ++i )
+ {
+ ShaderLab::TexEnv* te = m_TexEnvUsers[i];
+ te->TextureUVScaleChanged( x, y );
+ }
+}
+
+
+void Texture::SetFilterMode (int mode)
+{
+ if (m_TextureSettings.m_FilterMode != mode) {
+ m_TextureSettings.m_FilterMode = mode;
+ ApplySettings();
+ SetDirty();
+ }
+}
+
+void Texture::SetUsageMode ( TextureUsageMode mode)
+{
+ if (m_UsageMode != mode) {
+ m_UsageMode = mode;
+ ApplySettings();
+ SetDirty();
+ }
+}
+
+void Texture::SetStoredColorSpace(TextureColorSpace space)
+{
+ if (m_ColorSpace != space) {
+ m_ColorSpace = space;
+ ApplySettings();
+ SetDirty();
+ }
+}
+
+void Texture::SetStoredColorSpaceNoDirtyNoApply(TextureColorSpace space)
+{
+ if (m_ColorSpace != space) {
+ m_ColorSpace = space;
+ }
+}
+
+void Texture::SetWrapMode (int mode)
+{
+ if (m_TextureSettings.m_WrapMode != mode) {
+ m_TextureSettings.m_WrapMode = mode;
+ ApplySettings();
+ SetDirty();
+ }
+}
+
+void Texture::SetAnisoLevel (int level)
+{
+ if (m_TextureSettings.m_Aniso != level) {
+ m_TextureSettings.m_Aniso = level;
+ ApplySettings();
+ SetDirty();
+ }
+}
+
+void Texture::SetMipMapBias (float mipBias)
+{
+ if (m_TextureSettings.m_MipBias != mipBias)
+ {
+ m_TextureSettings.m_MipBias = mipBias;
+ ApplySettings();
+ SetDirty();
+ }
+}
+
+
+#if UNITY_EDITOR
+// Helper function so texture inspector can draw preview wrap mode
+void Texture::SetWrapModeNoDirty (int mode)
+{
+ m_TextureSettings.m_WrapMode = mode;
+ ApplySettings();
+}
+
+// Helper function so texture inspector can draw preview aniso level
+void Texture::SetAnisoLevelNoDirty (int level)
+{
+ m_TextureSettings.m_Aniso = level;
+ ApplySettings();
+}
+
+// Helper function so texture inspector can draw a specific mip of the texture
+void Texture::SetMipMapBiasNoDirty (float mipBias)
+{
+ m_TextureSettings.m_MipBias = mipBias;
+ ApplySettings();
+}
+
+// Helper function for editor so it can draw zoomed textures as point.
+void Texture::SetFilterModeNoDirty (int mode)
+{
+ if (m_TextureSettings.m_FilterMode != mode) {
+ m_TextureSettings.m_FilterMode = mode;
+ ApplySettings();
+ }
+}
+
+bool Texture::IgnoreMasterTextureLimit () const
+{
+ return false;
+}
+
+#endif
+
+int Texture::GetMasterTextureLimit ()
+{
+ return gTextureBaseLevel;
+}
+
+bool Texture::ShouldIgnoreInGarbageDependencyTracking ()
+{
+ return true;
+}
+
+void* Texture::GetNativeTexturePtr()
+{
+ return GetGfxDevice().GetNativeTexturePointer(m_TexID);
+}
+
+UInt32 Texture::GetNativeTextureID()
+{
+ return GetGfxDevice().GetNativeTextureID(m_TexID);
+}
+
+
+IMPLEMENT_CLASS (Texture)
diff --git a/Runtime/Graphics/Texture.h b/Runtime/Graphics/Texture.h
new file mode 100644
index 0000000..5babbbb
--- /dev/null
+++ b/Runtime/Graphics/Texture.h
@@ -0,0 +1,165 @@
+#pragma once
+
+#include "Runtime/BaseClasses/NamedObject.h"
+#include "TextureFormat.h"
+#include "TextureSettings.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Modules/ExportModules.h"
+
+#define TRACK_TEXTURE_SIZES (UNITY_EDITOR || ENABLE_PROFILER)
+
+class ImageReference;
+namespace ShaderLab { class TexEnv; }
+
+class EXPORT_COREMODULE Texture : public NamedObject
+{
+protected:
+ TextureSettings m_TextureSettings;
+ TextureID m_TexID;
+
+ int m_UsageMode;
+
+ int m_ColorSpace;
+
+ // Used by movie textures so that 0..1 range covers only the
+ // movie portion. For all other textures this is (1,1).
+ float m_UVScaleX, m_UVScaleY;
+
+ // Texel size. This is 1/size for all textures.
+ float m_TexelSizeX, m_TexelSizeY;
+
+ // TexEnvs that use this texture
+ dynamic_array<ShaderLab::TexEnv*> m_TexEnvUsers;
+
+public:
+ REGISTER_DERIVED_ABSTRACT_CLASS (Texture, NamedObject)
+
+ Texture(MemLabelId label, ObjectCreationMode mode);
+
+ virtual bool MainThreadCleanup();
+
+ virtual void Reset ();
+
+ virtual void CheckConsistency();
+
+ virtual TextureDimension GetDimension () const = 0;
+
+ // Blits the textures contents into image.
+ // Will use the closest matching mipmap.
+ virtual bool ExtractImage (ImageReference* image, int imageIndex = 0) const = 0;
+
+ TextureID GetTextureID() const { return m_TexID; }
+
+ TextureUsageMode GetUsageMode() const { return static_cast<TextureUsageMode>(m_UsageMode); }
+ void SetUsageMode(TextureUsageMode mode);
+
+ // The Color space the texture is imported for.
+ void SetStoredColorSpace(TextureColorSpace space);
+ // Needed to avoid an odd corner case for srgb cube render textures.
+ // If a render texture has the apply function called a texture id will be created and
+ // it will cause an error as it will be the wrong dimension when cubemap is set
+ // This function sets the stored color space without applying it. It will be applied
+ // when the create on the cubemap is called.
+ void SetStoredColorSpaceNoDirtyNoApply(TextureColorSpace space);
+ TextureColorSpace GetStoredColorSpace() const { return static_cast<TextureColorSpace>(m_ColorSpace); }
+
+ // The active color space of the texture
+ // Depending on if we are in linear or gamma rendering mode, textures will be tagged as sRGB or non-SRGB.
+ TextureColorSpace GetActiveTextureColorSpace() const { return (GetActiveColorSpace() == kLinearColorSpace) ? GetStoredColorSpace() : kTexColorSpaceLinear; }
+
+ #if ENABLE_PROFILER
+ virtual int GetStorageMemorySize() const = 0;
+ #endif
+
+ virtual TextureID GetUnscaledTextureID() const { return m_TexID; }
+
+ // Raw (original) texture size. For NPOT textures these values are NPOT.
+ virtual int GetDataWidth() const = 0;
+ virtual int GetDataHeight() const = 0;
+
+ // Size as used by GL. In most cases this matches Data width/height, except for NPOT textures
+ // where this is scaled up to next power of two.
+ virtual int GetGLWidth() const { return GetDataWidth(); }
+ virtual int GetGLHeight() const { return GetDataHeight(); }
+
+ virtual bool HasMipMap () const = 0;
+ virtual int CountMipmaps () const = 0;
+
+ static void SetMasterTextureLimit (int i, bool reloadTextures = true);
+ static int GetMasterTextureLimit ();
+
+ static void ReloadAll (bool unload = true, bool load = true, bool forceUnloadAll = false);
+
+ /// this is at the wrong place
+ enum { kDisableAniso = 0, kEnableAniso = 1, kForceEnableAniso = 2 };
+
+ // Get/Set the global texture anisotrophy levels
+ static void SetAnisoLimit (int aniso);
+ static int GetAnisoLimit ();
+
+ static void SetGlobalAnisoLimits(int forcedMin, int globalMax);
+
+ static Texture* FindTextureByID (TextureID tid)
+ {
+ TextureIDMap::iterator it = s_TextureIDMap.find(tid);
+ return it == s_TextureIDMap.end() ? NULL : it->second;
+ }
+
+ // Set the filtering mode for this texture.
+ // TextureFilterMode kTexFilterNearest, kTexFilterBilinear, kTexFilterTrilinear
+ void SetFilterMode( int mode );
+ int GetFilterMode() const { return m_TextureSettings.m_FilterMode; }
+
+ void SetWrapMode (int mode);
+ int GetWrapMode () const { return m_TextureSettings.m_WrapMode; }
+
+ void SetAnisoLevel (int mode);
+ int GetAnisoLevel () const { return m_TextureSettings.m_Aniso; }
+
+ float GetMipMapBias () const { return m_TextureSettings.m_MipBias; }
+ void SetMipMapBias (float bias);
+
+ const TextureSettings& GetSettings() const { return m_TextureSettings; }
+ TextureSettings& GetSettings() { return m_TextureSettings; }
+ virtual void ApplySettings();
+
+ float GetUVScaleX() const { return m_UVScaleX; }
+ float GetUVScaleY() const { return m_UVScaleY; }
+ void SetUVScale( float x, float y ) { m_UVScaleX = x; m_UVScaleY = y; NotifyUVScaleChanged(); }
+ float GetTexelSizeX() const { return m_TexelSizeX; }
+ float GetTexelSizeY() const { return m_TexelSizeY; }
+ void SetTexelSize( float x, float y ) { m_TexelSizeX = x; m_TexelSizeY = y; }
+
+ void AddTexEnvUser(ShaderLab::TexEnv* texenv);
+ void RemoveTexEnvUser(ShaderLab::TexEnv* texenv, size_t index);
+
+ virtual bool ShouldIgnoreInGarbageDependencyTracking ();
+
+
+#if UNITY_EDITOR
+ void SetAnisoLevelNoDirty (int level);
+ void SetWrapModeNoDirty (int mode);
+ void SetMipMapBiasNoDirty (float mipBias);
+ void SetFilterModeNoDirty (int mode);
+ virtual bool IgnoreMasterTextureLimit () const;
+
+ virtual TextureFormat GetEditorUITextureFormat () const = 0;
+#endif
+
+ void* GetNativeTexturePtr();
+ UInt32 GetNativeTextureID();
+
+protected:
+ void NotifyMipBiasChanged();
+ void NotifyUVScaleChanged();
+
+ // Used when changing master texture mip limit or explicitly reloading/unloading
+ // all resources.
+ virtual void UnloadFromGfxDevice(bool forceUnloadAll) = 0;
+ virtual void UploadToGfxDevice() = 0;
+
+ typedef std::map<TextureID, Texture*> TextureIDMap;
+ static TextureIDMap s_TextureIDMap;
+};
diff --git a/Runtime/Graphics/Texture2D.cpp b/Runtime/Graphics/Texture2D.cpp
new file mode 100644
index 0000000..691dfd9
--- /dev/null
+++ b/Runtime/Graphics/Texture2D.cpp
@@ -0,0 +1,1422 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+#include "Texture2D.h"
+#include "Image.h"
+#include "RenderTexture.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "S3Decompression.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Serialize/SwapEndianArray.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/GfxDevice/GfxDeviceConfigure.h"
+#include "ImageConversion.h"
+#include "Runtime/Misc/Allocator.h"
+#include "DXTCompression.h"
+#include "Runtime/Serialize/PersistentManager.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/MemoryProfiler.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Threads/Mutex.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+#include "Runtime/Misc/BuildSettings.h"
+
+#if UNITY_EDITOR
+#include "CubemapTexture.h"
+#endif
+
+#if UNITY_WII
+#include "PlatformDependent/wii/WiiUtility.h"
+#endif
+
+#if ENABLE_TEXTUREID_MAP
+#include "Runtime/GfxDevice/TextureIdMap.h"
+#endif
+
+
+#define USE_IMMEDIATE_INTEGRATION (UNITY_IPHONE || UNITY_ANDROID || UNITY_BB10 || UNITY_PS3 || UNITY_XENON || UNITY_TIZEN)
+
+
+PROFILER_INFORMATION(gIntegrateLoadedImmediately, "Texture.IntegrateLoadedImmediately", kProfilerLoading)
+PROFILER_INFORMATION(gAwakeFromLoadTex2D, "Texture.AwakeFromLoad", kProfilerLoading)
+
+/*
+ Regular and non-power-of-two textures:
+
+ For regular textures, all TextureRepresentation's are exactly the same; and the data is allocated just
+ once (all representations point to the same data).
+
+ For NPOT textures:
+ * m_TexData is what is serialized (non power of two). Mip maps use floor(size/2) convention,
+ DXT compression is actually next multiple of 4.
+ * If no mipmaps are specified and NPOTRestricted is supported - act as regular texture
+ * If mipmaps are specified and NPOTFULL is supported - act as regular texture
+ * If NPOT textures are not supported:
+ * m_Tex is scaled up to POT at load time. DXT source gets decompressed into ARGB32.
+ * m_TexPadded is POT size, but the original is padded by repeating border pixels. GUITexture uses it.
+*/
+
+bool Texture2D::s_ScreenReadAllowed = true;
+
+static inline UInt32 GetBytesForOnePixel( TextureFormat format )
+{
+ return IsAnyCompressedTextureFormat(format) ? 0 : GetBytesFromTextureFormat(format);
+}
+
+bool IsNPOTTextureAllowed(bool hasMipMap)
+{
+ if(IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_1_a1))
+ return hasMipMap ? gGraphicsCaps.npot == kNPOTFull : gGraphicsCaps.npot >= kNPOTRestricted;
+ else
+ return false;
+}
+
+static int GetNextAllowedTextureSize(int size, bool hasMipMap, TextureFormat format)
+{
+ int multipleMask = GetTextureSizeAllowedMultiple(format) - 1;
+ size = (size + multipleMask) & ~multipleMask;
+ if (!IsNPOTTextureAllowed(hasMipMap))
+ size = NextPowerOfTwo(size);
+ return size;
+}
+
+
+Texture2D::TextureRepresentation::TextureRepresentation()
+: data(NULL)
+, width(0)
+, height(0)
+, format(kTexFormatARGB32)
+, imageSize(0)
+{
+}
+
+Texture2D::Texture2D (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_InitFlags(0)
+{
+ #if UNITY_EDITOR
+ m_IgnoreMasterTextureLimit = false;
+ #endif
+
+ m_MipMap = false;
+ m_TextureDimension = kTexDimNone;
+
+#if UNITY_EDITOR
+ m_EditorDontWriteTextureData = false;
+ m_AlphaIsTransparency = false;
+#endif
+
+ m_PowerOfTwo = true;
+
+ m_ImageCount = 0;
+
+ m_IsReadable = true;
+ m_IsUnreloadable = false;
+ m_ReadAllowed = true;
+ m_TextureUploaded = false;
+ m_UnscaledTextureUploaded = false;
+
+ // We use unchecked version since we may not be on the main thread
+ // This means CreateTextureID() implementation must be thread safe!
+ m_UnscaledTexID = GetUncheckedGfxDevice().CreateTextureID();
+}
+
+void Texture2D::Reset ()
+{
+ Super::Reset();
+
+ m_TextureSettings.Reset ();
+}
+
+//#define LOG_AWAKE(x, arg) printf_console(x, arg);
+#define LOG_AWAKE(x, arg)
+
+typedef std::vector<Texture2D*> TexturesT;
+static TexturesT g_TexturesToUploadOnMainThread;
+static Mutex g_UploadMutex;
+
+void Texture2D::AwakeFromLoadThreaded()
+{
+ Super::AwakeFromLoadThreaded();
+
+#if USE_IMMEDIATE_INTEGRATION
+ LOG_AWAKE("Texture2D: AwakeFromLoadThreaded (%s)\n", GetName());
+ g_UploadMutex.Lock();
+ g_TexturesToUploadOnMainThread.push_back(this);
+ g_UploadMutex.Unlock();
+ // Pause loading for a while
+ // this will allow main thread to start integrating previously loaded textures
+ Thread::Sleep(0.001);
+#endif // USE_IMMEDIATE_INTEGRATION
+}
+
+// Helps to avoid memory peak while loading the level on platforms where graphics driver creates an internal copy of texture data
+//
+// Memory peak arrises in the following scenario:
+// 1) [Load thread] all texture assets are loaded
+// 2) [Main thread] for every asset (OnAwake)
+// a) texture data is uploaded to graphics driver (copy)
+// b) texture data is released (in case of NonReadable textures)
+//
+// Instead we do:
+// 1) [Load thread] load 1 texture asset
+// 2) [Load thread] stall for a moment and allow Main thread to start uploading
+// 3) [Main thread] upload as much texture data as possible
+// 4) [Main thread] if significantly behind Load thread, then block Load thread
+// 5) continue for all assets
+//
+// NOTE: Main thread calls IntegrateLoadedImmediately() from PreloadManager::UpdatePreloadingSingleStep()
+void Texture2D::IntegrateLoadedImmediately()
+{
+#if USE_IMMEDIATE_INTEGRATION
+
+ PROFILER_AUTO(gIntegrateLoadedImmediately, NULL)
+
+ g_UploadMutex.Lock();
+ TexturesT toUpload = g_TexturesToUploadOnMainThread;
+ g_TexturesToUploadOnMainThread.clear();
+
+ size_t totalTextureSize = 0;
+ for (size_t q = 0; q < toUpload.size(); ++q)
+ totalTextureSize += toUpload[q]->m_TexData.imageSize;
+
+ // Evalute texture data ready for immediate integration
+ // If we have too much data to upload, then block the loading thread!
+ // Loading thread will stall until all pending data is uploaded.
+ bool blockLoadingThread = totalTextureSize > (512*1024);
+
+ // Avoid stalling on every texture (if possible) to improve loading speed
+ if (!blockLoadingThread)
+ {
+ g_UploadMutex.Unlock();
+ }
+ else
+ {
+ LOG_AWAKE("Texture2D: IntegrateLoadedImmediately() will block loader thread, pending texture data size: %d bytes\n", totalTextureSize);
+ }
+
+ for (size_t q = 0; q < toUpload.size(); ++q)
+ {
+ Texture2D* tex = toUpload[q];
+ LOG_AWAKE("Texture2D: Immediately upload texture data (%s)\n", tex->GetName());
+ if (tex->m_TexData.data != NULL)
+ tex->UploadTexture (false);
+ }
+
+ if (blockLoadingThread)
+ g_UploadMutex.Unlock();
+
+#endif // USE_IMMEDIATE_INTEGRATION
+}
+
+void Texture2D::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ SET_ALLOC_OWNER(this);
+ Super::AwakeFromLoad (awakeMode);
+
+ if( (awakeMode == kDefaultAwakeFromLoad || awakeMode == kInstantiateOrCreateFromCodeAwakeFromLoad)
+ && m_TexData.data == NULL
+ )
+ {
+ // actually pretty valid
+ return;
+ }
+
+#if USE_IMMEDIATE_INTEGRATION
+ // Check if texture data was already uploaded by IntegrateLoadedImmediately()
+ if ((awakeMode & kDidLoadThreaded) != 0)
+ {
+ bool dynamicTexture = m_TexData.imageSize == 0;
+ Assert(m_TextureUploaded || dynamicTexture);
+
+ if (m_TextureUploaded)
+ return;
+ }
+ LOG_AWAKE("Texture2D: Upload texture data in Awake (%s)\n", GetName());
+#endif // USE_IMMEDIATE_INTEGRATION
+
+ PROFILER_AUTO(gAwakeFromLoadTex2D, this)
+ UploadTexture (false);
+}
+
+Texture2D::~Texture2D ()
+{
+ DestroyTexture ();
+ MainThreadCleanup ();
+}
+
+bool Texture2D::MainThreadCleanup ()
+{
+ DeleteGfxTexture ();
+
+ Texture::s_TextureIDMap.erase (m_UnscaledTexID);
+
+ // FreeTextureID() implementation must be thread safe!
+ GetUncheckedGfxDevice().FreeTextureID(m_UnscaledTexID);
+ m_UnscaledTexID = TextureID();
+
+ return Super::MainThreadCleanup ();
+}
+
+
+static int SourceMipLevelForBlit( int srcWidth, int srcHeight, int dstWidth, int dstHeight )
+{
+ int level = HighestBit( NextPowerOfTwo(srcWidth) ) - HighestBit( NextPowerOfTwo(dstWidth) );
+ level = std::max( level, HighestBit( NextPowerOfTwo(srcHeight) ) - HighestBit( NextPowerOfTwo(dstHeight) ) );
+ level = std::max( 0, level );
+ return level;
+}
+
+// Compressed->compressed extraction. Copies compressed blocks and pads the remainder with
+// transparent black.
+void Texture2D::ExtractCompressedImageInternal( UInt8* dst, int dstWidth, int dstHeight, int imageIndex ) const
+{
+ AssertIf( imageIndex < 0 || imageIndex >= m_ImageCount );
+
+ TextureRepresentation const& rep =m_TexData;
+ AssertIf( !IsAnyCompressedTextureFormat( rep.format ) );
+ if (m_TexData.data == NULL)
+ {
+ ErrorString ("Texture data can not be accessed");
+ return;
+ }
+
+ int mipmapLevel = SourceMipLevelForBlit( rep.width, rep.height, dstWidth, dstHeight );
+ mipmapLevel = std::min( mipmapLevel, CountDataMipmaps() - 1 );
+ int mipmapoffset = CalculateMipMapOffset( rep.width, rep.height, rep.format, mipmapLevel );
+ int width = std::max( rep.width >> mipmapLevel, 1 );
+ int height = std::max( rep.height >> mipmapLevel, 1 );
+
+ // pad-copy compressed->compressed
+ BlitCopyCompressedImage( rep.format, rep.data + imageIndex * rep.imageSize + mipmapoffset,
+ width, height, dst, dstWidth, dstHeight, true );
+}
+
+
+bool Texture2D::ExtractImageInternal( ImageReference* image, bool scaleToSize, int imageIndex ) const
+{
+ AssertIf (imageIndex < 0 || imageIndex >= m_ImageCount);
+
+ TextureRepresentation const& rep = m_TexData;
+ if(rep.data == NULL)
+ {
+ ErrorString ("Texture is not accessible.");
+ return false;
+ }
+
+ int mipmapLevel = SourceMipLevelForBlit( rep.width, rep.height, image->GetWidth(), image->GetHeight() );
+ mipmapLevel = std::min( mipmapLevel, CountDataMipmaps() - 1 );
+ int mipmapoffset = CalculateMipMapOffset( rep.width, rep.height, rep.format, mipmapLevel );
+ int width = std::max( rep.width >> mipmapLevel, 1 );
+ int height = std::max( rep.height >> mipmapLevel, 1 );
+
+ if( IsAnyCompressedTextureFormat(rep.format) )
+ {
+ // Decompress into upper multiple-of-4 size
+ int decompWidth = (width + 3) / 4 * 4;
+ int decompHeight = (height + 3) / 4 * 4;
+
+ Image decompressed( decompWidth, decompHeight, kTexFormatRGBA32 );
+ UInt8* compressed = rep.data + imageIndex * rep.imageSize + mipmapoffset;
+
+ if (!DecompressNativeTextureFormatWithMipLevel( rep.format, width, height, mipmapLevel, (UInt32*)compressed, decompWidth, decompHeight, (UInt32*)decompressed.GetImageData() ))
+ return false;
+
+ // Clip this back to original size
+ ImageReference clipped = decompressed.ClipImage( 0, 0, width, height );
+
+ if( scaleToSize )
+ {
+ image->BlitImage( clipped, ImageReference::BLIT_BILINEAR_SCALE );
+ }
+ else
+ {
+ AssertIf( width > image->GetWidth() || height > image->GetHeight() );
+ image->BlitImage( clipped, ImageReference::BLIT_COPY );
+ PadImageBorder( *image, width, height );
+ }
+ return true;
+ }
+ else
+ {
+ ImageReference source( width, height, width * GetBytesFromTextureFormat(rep.format),
+ rep.format, rep.data + imageIndex * rep.imageSize + mipmapoffset);
+
+ if( scaleToSize )
+ {
+ image->BlitImage( source, ImageReference::BLIT_BILINEAR_SCALE );
+ }
+ else
+ {
+ AssertIf( width > image->GetWidth() || height > image->GetHeight() );
+ image->BlitImage( source, ImageReference::BLIT_COPY );
+ PadImageBorder( *image, width, height );
+ }
+ return true;
+ }
+
+ return false;
+}
+bool Texture2D::ExtractImage (ImageReference* image, int imageIndex) const
+{
+ return ExtractImageInternal( image, true, imageIndex );
+}
+
+bool Texture2D::HasMipMap() const {
+ return m_MipMap;
+}
+
+int Texture2D::CountMipmaps() const
+{
+ if (m_MipMap)
+ return CalculateMipMapCount3D( m_glWidth, m_glHeight, 1 );
+ else
+ return 1;
+}
+int Texture2D::CountDataMipmaps() const
+{
+ if (m_MipMap)
+ return CalculateMipMapCount3D( m_TexData.width, m_TexData.height, 1 );
+ else
+ return 1;
+}
+
+UInt8* Texture2D::AllocateTextureData (int imageSize, TextureFormat format, bool initMemory)
+{
+ // Allocate one more pixel because software bi-linear filtering might require it.
+ int allocSize = imageSize + GetBytesForOnePixel(format);
+ void* buffer = UNITY_MALLOC_ALIGNED (GetTextureDataMemoryLabel(), allocSize, 32);
+
+ // In the past the memory manager cleared textures created from script to 0xcd.
+ // Let's keep doing that even though a color like white makes more sense (case 564961).
+ if (initMemory && buffer)
+ memset(buffer, 0xcd, allocSize);
+
+ return static_cast<UInt8*>(buffer);
+}
+
+void Texture2D::DeallocateTextureData (UInt8* tex)
+{
+ UNITY_FREE (GetTextureDataMemoryLabel(), tex);
+}
+
+void Texture2D::InitTextureInternal (int width, int height, TextureFormat format, int imageSize, UInt8* buffer, int options, int imageCount)
+{
+ // Cleanup old memory
+ if ((options & kThreadedInitialize) == 0)
+ {
+ DestroyTexture ();
+ SetDirty ();
+ }
+ else
+ {
+ AssertIf(m_TexData.data != NULL);
+ }
+
+ m_TextureDimension = kTexDim2D;
+
+ m_TexData.width = width;
+ m_TexData.height = height;
+ m_TexData.format = format;
+ m_TexData.imageSize = imageSize;
+ m_TexData.data = buffer;
+ m_MipMap = options & kMipmapMask;
+ m_InitFlags = options;
+ m_ImageCount = imageCount;
+ UpdatePOTStatus();
+
+ m_glWidth = GetNextAllowedTextureSize(m_TexData.width, m_MipMap, format);
+ m_glHeight = GetNextAllowedTextureSize(m_TexData.height, m_MipMap, format);
+ SetTexelSize( 1.0f/m_glWidth, 1.0f/m_glHeight );
+}
+
+bool Texture2D::InitTexture (int width, int height, TextureFormat format, int options, int imageCount, intptr_t nativeTex)
+{
+ SET_ALLOC_OWNER(this);
+ if (width < 0 || width > 10000 || height < 0 || height > 10000)
+ {
+ ErrorStringObject ("Texture has out of range width / height", this);
+ return false;
+ }
+
+ if (!IsValidTextureFormat(format))
+ {
+ ErrorStringObject ("TextureFormat is invalid!", this);
+ return false;
+ }
+
+ int imageSize;
+ if( options & kMipmapMask)
+ imageSize = CalculateImageMipMapSize( width, height, format );
+ else
+ imageSize = CalculateImageSize( width, height, format );
+
+ unsigned int tlen = imageSize * imageCount;
+ // probably an multiplication overflow
+ if (imageSize != 0 && imageCount != tlen / imageSize)
+ return false;
+ // probably an addition overflow
+ if (tlen + GetBytesForOnePixel(format) < tlen)
+ return false;
+
+ bool allocData = ENABLE_TEXTUREID_MAP == 0 || nativeTex == 0;
+
+#if ENABLE_TEXTUREID_MAP
+ if(nativeTex)
+ TextureIdMap::UpdateTexture(m_TexID, GetGfxDevice().CreateExternalTextureFromNative(nativeTex));
+#endif
+
+ UInt8* buffer = allocData ? AllocateTextureData(imageSize * imageCount, format, true) : 0;
+ InitTextureInternal (width, height, format, imageSize, buffer, options, imageCount);
+
+ return true;
+}
+
+void Texture2D::RebuildMipMap ()
+{
+ TextureRepresentation& rep = m_TexData;
+
+ if( rep.data == NULL || !m_MipMap )
+ return;
+ if( IsAnyCompressedTextureFormat(rep.format) )
+ {
+ ErrorString ("Rebuilding mipmaps of compressed textures is not supported");
+ return;
+ }
+
+ for( int i=0;i<m_ImageCount;i++ )
+ CreateMipMap (rep.data + rep.imageSize * i, rep.width, rep.height, 1, rep.format);
+}
+
+
+#if UNITY_EDITOR
+void Texture2D::SetImage (const ImageReference& image, int flags)
+{
+ SET_ALLOC_OWNER(this);
+ InitTexture( image.GetWidth (), image.GetHeight (), image.GetFormat (), flags );
+
+ int bytesPerPixel = GetBytesFromTextureFormat (m_TexData.format);
+
+ ImageReference dst( m_TexData.width, m_TexData.height, bytesPerPixel * m_TexData.width, m_TexData.format, m_TexData.data );
+ if (image.GetImageData () != NULL)
+ dst.BlitImage (image);
+
+ RebuildMipMap ();
+
+ UploadTexture (true);
+ SetDirty ();
+}
+#endif
+
+
+
+bool Texture2D::GetImageReferenceInternal (ImageReference* image, int frame, int miplevel) const
+{
+ TextureRepresentation const& rep = m_TexData;
+
+ if( rep.data == NULL || IsAnyCompressedTextureFormat(rep.format) )
+ return false;
+
+ AssertIf (frame >= m_ImageCount);
+ AssertIf (miplevel >= CountDataMipmaps ());
+
+ UInt8* base = rep.data + frame * rep.imageSize;
+ base += CalculateMipMapOffset( rep.width, rep.height, rep.format, miplevel );
+ int width = std::max (rep.width >> miplevel, 1);
+ int height = std::max (rep.height >> miplevel, 1);
+
+ *image = ImageReference( width, height, GetRowBytesFromWidthAndFormat(width, rep.format), rep.format, base );
+ return true;
+}
+
+bool Texture2D::GetWriteImageReference (ImageReference* image, int frame, int miplevel)
+{
+ return GetImageReferenceInternal (image, frame, miplevel);
+}
+
+
+void Texture2D::ApplySettings()
+{
+ TextureDimension texdim = GetDimension();
+ m_TextureSettings.Apply( GetTextureID(), texdim, HasMipMap(), GetActiveTextureColorSpace());
+ if( m_UnscaledTextureUploaded )
+ m_TextureSettings.Apply( GetUnscaledTextureID(), texdim, HasMipMap(), GetActiveTextureColorSpace() );
+
+ NotifyMipBiasChanged();
+}
+
+void Texture2D::UpdatePOTStatus()
+{
+ m_PowerOfTwo = IsPowerOfTwo(m_TexData.width) && IsPowerOfTwo(m_TexData.height);
+
+ // force clamp if we will keep it npot
+ if(!m_PowerOfTwo && !m_MipMap && gGraphicsCaps.npot == kNPOTRestricted)
+ m_TextureSettings.m_WrapMode = kTexWrapClamp;
+}
+
+
+void Texture2D::UploadTexture (bool dontUseSubImage)
+{
+ if (m_TexData.data == NULL)
+ {
+ ErrorString("No Texture memory available to upload");
+ return;
+ }
+ if (m_TexData.width == 0 || m_TexData.height == 0)
+ {
+ return;
+ }
+
+ TextureRepresentation scaled = m_TexData;
+ TextureRepresentation padded = m_TexData;
+
+ InitTextureRepresentations(&scaled, &padded);
+
+ int mipCount = CountMipmaps();
+
+ int masterTextureLimit = Texture::GetMasterTextureLimit();
+ #if UNITY_EDITOR
+ if (m_IgnoreMasterTextureLimit)
+ masterTextureLimit = 0;
+ #endif
+
+ // Master texture limit must be clamped to mipCount-1 or otherwise GfxDevice won't upload anything
+ masterTextureLimit = min(mipCount-1, masterTextureLimit);
+
+ TextureUsageMode usageMode = GetUsageMode();
+
+ // upload regular texture
+ {
+ TextureRepresentation& rep = scaled;
+ UInt8* srcData = rep.data;
+ UInt32 uploadFlags = (dontUseSubImage || !m_TextureUploaded) ? GfxDevice::kUploadTextureDontUseSubImage : GfxDevice::kUploadTextureDefault;
+ if (m_InitFlags & kOSDrawingCompatible)
+ uploadFlags |= GfxDevice::kUploadTextureOSDrawingCompatible;
+ GetGfxDevice().UploadTexture2D( GetTextureID(), GetDimension(), srcData, rep.imageSize, rep.width, rep.height, rep.format, mipCount, uploadFlags, masterTextureLimit, usageMode, GetActiveTextureColorSpace() );
+ Texture::s_TextureIDMap.insert (std::make_pair(GetTextureID(),this));
+ m_TextureSettings.Apply( GetTextureID(), GetDimension(), m_MipMap, GetActiveTextureColorSpace());
+ m_TextureUploaded = true;
+ }
+
+ // upload unscaled one if NPOT and not supported
+ if( m_TexData.width != m_glWidth || m_TexData.height != m_glHeight )
+ {
+ TextureRepresentation& rep = padded;
+ UInt8* srcData = rep.data;
+
+ UInt32 uploadFlags = (dontUseSubImage || !m_UnscaledTextureUploaded) ? GfxDevice::kUploadTextureDontUseSubImage : GfxDevice::kUploadTextureDefault;
+ if (m_InitFlags & kOSDrawingCompatible)
+ uploadFlags |= GfxDevice::kUploadTextureOSDrawingCompatible;
+
+ m_UnscaledTextureUploaded = true; // must be before Upload in order for GetUnscaledGLTextureName() to return the right value
+ TextureID tid = GetUnscaledTextureID();
+ GetGfxDevice().UploadTexture2D( tid, GetDimension(), srcData, rep.imageSize, rep.width, rep.height, rep.format, mipCount, uploadFlags, masterTextureLimit, usageMode, GetActiveTextureColorSpace() );
+ Texture::s_TextureIDMap.insert (std::make_pair(tid,this));
+ m_TextureSettings.Apply( tid, GetDimension(), m_MipMap, GetActiveTextureColorSpace() );
+ }
+
+#if UNITY_XENON && !MASTER_BUILD
+ GetGfxDevice().SetTextureName( GetTextureID(), GetName() );
+#endif
+
+#if UNITY_EDITOR
+ DestroyTextureRepresentations(&scaled, &padded, false);
+#elif UNITY_WII
+ // On Wii texture data is being directly referenced from m_TexData.data, so don't delete it after uploading
+ DestroyTextureRepresentations(&scaled, &padded, false);
+#else
+ DestroyTextureRepresentations(&scaled, &padded, !m_IsReadable);
+ if(!m_IsReadable)
+ m_TexData.imageSize = 0;
+#endif
+}
+
+void Texture2D::DestroyTextureRepresentation( TextureRepresentation* rep )
+{
+ if( rep )
+ {
+ if( rep->data == m_TexData.data )
+ rep->data = NULL;
+
+ DeallocateTextureData (rep->data);
+ rep->data = NULL;
+ }
+}
+
+void Texture2D::DestroyTextureRepresentations( TextureRepresentation* scaled, TextureRepresentation* padded, bool freeSourceImage )
+{
+ DestroyTextureRepresentation(padded);
+ DestroyTextureRepresentation(scaled);
+
+ if( freeSourceImage )
+ {
+ DeallocateTextureData (m_TexData.data);
+ m_TexData.data = NULL;
+ }
+}
+
+void Texture2D::DeleteGfxTexture ()
+{
+ if (m_TextureUploaded)
+ {
+ ASSERT_RUNNING_ON_MAIN_THREAD
+ GetGfxDevice().DeleteTexture( GetTextureID() );
+ m_TextureUploaded = false;
+ }
+ if (m_UnscaledTextureUploaded)
+ {
+ ASSERT_RUNNING_ON_MAIN_THREAD
+ GetGfxDevice().DeleteTexture( GetUnscaledTextureID() );
+ m_UnscaledTextureUploaded = false;
+ }
+}
+
+
+void Texture2D::DestroyTexture ()
+{
+ DestroyTextureRepresentations (0,0,true);
+
+ DeleteGfxTexture ();
+}
+
+
+void Texture2D::UnloadFromGfxDevice(bool forceUnloadAll)
+{
+ if (m_IsUnreloadable && !forceUnloadAll)
+ return;
+ DeleteGfxTexture ();
+}
+
+void Texture2D::UploadToGfxDevice()
+{
+ if (m_IsUnreloadable)
+ return;
+
+ GfxDevice& device = GetGfxDevice();
+
+ // We need to load the texture data from disk. Awake FromLoad will be called during ReloadFromDisk.
+ bool needReloadFromDisk = (m_TexData.data == NULL && !m_IsReadable);
+ if (needReloadFromDisk)
+ {
+ TextureSettings settings = m_TextureSettings;
+
+ GetPersistentManager().ReloadFromDisk(this);
+
+ m_TextureSettings = settings;
+ ApplySettings();
+ }
+ // Just upload the texture data we already have in memory
+ else
+ {
+ UploadTexture (true);
+ }
+}
+
+
+#if UNITY_EDITOR
+struct TemporaryTextureSerializationRevert
+{
+ Texture2D* texture;
+ Texture2D::TextureRepresentation texData;
+ int imageCount;
+ bool isReadable;
+
+ TemporaryTextureSerializationRevert (Texture2D& tex2D, bool doRevert)
+ {
+ texture = NULL;
+
+ if (doRevert)
+ {
+ texture = &tex2D;
+
+ texData = texture->m_TexData;
+ imageCount = texture->m_ImageCount;
+ isReadable = texture->m_IsReadable;
+ }
+ }
+ ~TemporaryTextureSerializationRevert ()
+ {
+ if (texture != NULL)
+ {
+ texture->m_TexData = texData;
+ texture->m_ImageCount = imageCount;
+ texture->m_IsReadable = isReadable;
+ }
+ }
+};
+#endif
+
+template<class TransferFunction>
+void Texture2D::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+#if UNITY_EDITOR
+
+ // Store serialization state temporarliy and revert it when exiting function
+ TemporaryTextureSerializationRevert revert(*this, !transfer.IsReading ());
+
+ // When writing dynamic font textures we don't want to write any texture data to disk
+ if (m_EditorDontWriteTextureData && !transfer.IsReading ())
+ {
+ m_ImageCount = m_TexData.imageSize = m_TexData.height = m_TexData.width = 0;
+ m_TexData.data = NULL;
+ }
+
+ // readable flag gets adjusted by forced readable flag
+ if (transfer.IsBuildingTargetPlatform(kBuildAnyPlayerData))
+ m_IsReadable |= transfer.GetBuildUsage().forceTextureReadable;
+
+ TRANSFER_EDITOR_ONLY_HIDDEN(m_AlphaIsTransparency);
+ transfer.Align();
+#endif
+
+ // In case we're converting the texture data to another format, we also properly write out the format ID.
+ transfer.Transfer( m_TexData.width, "m_Width", kNotEditableMask );
+ transfer.Transfer( m_TexData.height, "m_Height", kNotEditableMask );
+ transfer.Transfer( m_TexData.imageSize, "m_CompleteImageSize", kNotEditableMask );
+ transfer.Transfer( m_TexData.format, "m_TextureFormat", kHideInEditorMask );
+ transfer.Transfer( m_MipMap, "m_MipMap", kNotEditableMask );
+
+ transfer.Transfer( m_IsReadable, "m_IsReadable");
+ transfer.Transfer( m_ReadAllowed, "m_ReadAllowed", kNotEditableMask );
+ transfer.Align();
+ transfer.Transfer( m_ImageCount, "m_ImageCount", kNotEditableMask );
+ transfer.Transfer( m_TextureDimension, "m_TextureDimension", kHideInEditorMask );
+ transfer.Transfer( m_TextureSettings, "m_TextureSettings");
+ transfer.Transfer( m_UsageMode, "m_LightmapFormat");
+ transfer.Transfer( m_ColorSpace, "m_ColorSpace");
+
+ unsigned imageSizeXImageCount = m_ImageCount * m_TexData.imageSize;
+
+ if (!transfer.IsWritingGameReleaseData ())
+ transfer.TransferTypeless (&imageSizeXImageCount, "image data", kHideInEditorMask);
+
+ if (transfer.IsReading ())
+ {
+ DestroyTexture ();
+ Assert(GetMemoryLabel().label != kMemTextureCacheId);
+ m_TexData.data = AllocateTextureData(imageSizeXImageCount, m_TexData.format);
+ m_glWidth = GetNextAllowedTextureSize(m_TexData.width, m_MipMap, TextureFormat(m_TexData.format));
+ m_glHeight = GetNextAllowedTextureSize(m_TexData.height, m_MipMap, TextureFormat(m_TexData.format));
+ SetTexelSize( 1.0f/m_glWidth, 1.0f/m_glHeight );
+ }
+
+ // write tex image
+ if (transfer.IsWriting())
+ {
+ const bool gpuBE = UNITY_EDITOR ? transfer.IsBuildingTargetPlatform(kBuildXBOX360) : false;
+
+ UInt8* convertedData = NULL;
+ if( transfer.ConvertEndianess() )
+ {
+ convertedData = AllocateTextureData(imageSizeXImageCount, m_TexData.format);
+ ConvertTextureEndianessWrite(m_TexData.format, m_TexData.data, convertedData, imageSizeXImageCount, gpuBE);
+ }
+
+ if (transfer.IsWritingGameReleaseData ())
+ transfer.TransferTypeless (&imageSizeXImageCount, "image data", kHideInEditorMask);
+ transfer.TransferTypelessData (imageSizeXImageCount, convertedData ? convertedData : m_TexData.data );
+
+ DeallocateTextureData(convertedData);
+ }
+ else
+ {
+ if( transfer.ConvertEndianess() )
+ {
+ transfer.TransferTypelessData (imageSizeXImageCount, m_TexData.data);
+ ConvertTextureEndianessRead (m_TexData.format, m_TexData.data, imageSizeXImageCount);
+ }
+ else
+ {
+ transfer.TransferTypelessData (imageSizeXImageCount, m_TexData.data);
+ }
+ }
+
+ AssertIf ( m_TexData.imageSize != 0 && m_TexData.data == NULL );
+
+ if( transfer.IsReading() )
+ UpdatePOTStatus();
+}
+
+void ConvertTextureEndianessWrite (int format, UInt8* src, UInt8* dst, int size, bool bBigEndianGPU)
+{
+ memcpy (dst, src, size);
+ if (format == kTexFormatARGBFloat)
+ SwapEndianArray (dst, 4, size / 4);
+ else if (format == kTexFormatRGB565 || format == kTexFormatARGB4444 || format == kTexFormatRGBA4444)
+ SwapEndianArray (dst, 2, size / 2);
+ else if (bBigEndianGPU && (format == kTexFormatDXT1 || format == kTexFormatDXT3 || format == kTexFormatDXT5))
+ SwapEndianArray (dst, 2, size / 2);
+
+}
+
+void ConvertTextureEndianessRead (int format, UInt8* dst, int size)
+{
+ if (format == kTexFormatARGBFloat)
+ SwapEndianArray (dst, 4, size / 4);
+ else if (format == kTexFormatRGB565 || format == kTexFormatARGB4444 || format == kTexFormatRGBA4444)
+ SwapEndianArray (dst, 2, size / 2);
+}
+
+
+void Texture2D::UpdateImageData ()
+{
+ RebuildMipMap ();
+ UploadTexture (false);
+ SetDirty ();
+}
+
+void Texture2D::UpdateImageDataDontTouchMipmap ()
+{
+ UploadTexture (false);
+ SetDirty ();
+}
+
+
+TextureID Texture2D::GetUnscaledTextureID() const
+{
+ return m_UnscaledTextureUploaded ? m_UnscaledTexID : m_TexID;
+}
+int Texture2D::GetDataWidth() const
+{
+ return m_TexData.width;
+}
+int Texture2D::GetDataHeight() const
+{
+ return m_TexData.height;
+}
+
+
+void Texture2D::InitTextureRepresentation( TextureRepresentation* rep, int format, const char* tag )
+{
+ Assert(rep);
+
+ rep->width = GetNextAllowedTextureSize(m_TexData.width, m_MipMap, TextureFormat(format));
+ rep->height = GetNextAllowedTextureSize(m_TexData.height, m_MipMap, TextureFormat(format));
+ rep->format = format;
+
+ if( m_MipMap )
+ rep->imageSize = CalculateImageMipMapSize( rep->width, rep->height, rep->format );
+ else
+ rep->imageSize = CalculateImageSize( rep->width, rep->height, rep->format );
+
+ rep->data = AllocateTextureData(rep->imageSize * m_ImageCount, rep->format);
+}
+
+void Texture2D::ExtractMipLevel( TextureRepresentation* dst, int frame, int mipLevel, bool checkCompression, bool scaleToSize )
+{
+ if (dst->width == 0 || dst->height == 0)
+ return;
+
+ UInt8* data = dst->data + frame * dst->imageSize + CalculateMipMapOffset(dst->width, dst->height, dst->format, mipLevel );
+
+
+ int width = std::max( dst->width >> mipLevel, 1 );
+ int height = std::max( dst->height >> mipLevel, 1 );
+
+ if( checkCompression && IsAnyCompressedTextureFormat(dst->format) )
+ {
+ ExtractCompressedImageInternal( data, width, height, frame );
+ }
+ else
+ {
+ ImageReference ref( width, height, width*GetBytesFromTextureFormat(dst->format), dst->format, data );
+ ExtractImageInternal( &ref, scaleToSize, frame );
+ }
+}
+
+
+void Texture2D::InitTextureRepresentations(Texture2D::TextureRepresentation* scaled, Texture2D::TextureRepresentation* padded)
+{
+ Assert(scaled);
+ Assert(padded);
+
+ if( m_TextureDimension == kTexDimDeprecated1D )
+ m_TextureDimension = kTexDim2D;
+
+ int multipleMask = GetTextureSizeAllowedMultiple(TextureFormat(m_TexData.format)) - 1;
+ bool isAllowedMultiple = (m_TexData.width & multipleMask) == 0 && (m_TexData.height & multipleMask) == 0;
+ if (isAllowedMultiple && (m_PowerOfTwo || IsNPOTTextureAllowed(m_MipMap)))
+ {
+ *scaled = *padded = m_TexData;
+ SetTexelSize( 1.0f / m_TexData.width, 1.0f / m_TexData.height );
+ return;
+ }
+
+ DebugAssertIf( m_PowerOfTwo );
+ DebugAssertIf( !m_TexData.data );
+
+ InitTextureRepresentation(scaled, IsAnyCompressedTextureFormat(m_TexData.format) ? kTexFormatRGBA32 : m_TexData.format, "tex.scaled");
+ InitTextureRepresentation(padded, m_TexData.format, "tex.padded");
+
+ int mipcount = CountMipmaps();
+ for( int frame = 0; frame < m_ImageCount; ++frame )
+ {
+ for( int mip = 0; mip < mipcount; ++mip )
+ {
+ ExtractMipLevel(scaled, frame, mip, false, true);
+ ExtractMipLevel(padded, frame, mip, true, false);
+ }
+ }
+}
+
+int Texture2D::GetRuntimeMemorySize() const
+{
+#if ENABLE_MEM_PROFILER
+ return Super::GetRuntimeMemorySize() +
+ GetMemoryProfiler()->GetRelatedIDMemorySize(m_TexID.m_ID) +
+ (m_UnscaledTextureUploaded?GetMemoryProfiler()->GetRelatedIDMemorySize(GetUnscaledTextureID().m_ID):0);
+#endif
+ return sizeof(Texture2D);
+}
+
+
+IMPLEMENT_CLASS (Texture2D)
+IMPLEMENT_OBJECT_SERIALIZE (Texture2D)
+INSTANTIATE_TEMPLATE_TRANSFER (Texture2D)
+
+bool Texture2D::CheckHasPixelData () const
+{
+ if (m_TexData.data != NULL)
+ return true;
+
+ if (!m_IsReadable)
+ {
+ ErrorString(Format("Texture '%s' is not readable, the texture memory can not be accessed from scripts. You can make the texture readable in the Texture Import Settings.", GetName()));
+ }
+ else
+ {
+ ErrorString(Format("Texture '%s' has no data", GetName()));
+ }
+ return false;
+}
+
+
+void Texture2D::SetPixel (int frame, int x, int y, const ColorRGBAf& c)
+{
+ if (!CheckHasPixelData())
+ return;
+
+ if (frame > m_ImageCount) {
+ ErrorString (Format ("SetPixel called on an undefined image (valid values are 0 - %d", m_ImageCount - 1));
+ return;
+ }
+
+ ImageReference image;
+ if (GetWriteImageReference(&image, frame, 0))
+ {
+ SetImagePixel (image, x, y, static_cast<TextureWrapMode>(GetSettings().m_WrapMode), c);
+ }
+ else
+ {
+ if( IsAnyCompressedTextureFormat(m_TexData.format) )
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ }
+ else
+ {
+ ErrorString("Unable to retrieve image reference");
+ }
+ }
+}
+
+void Texture2D::SetPixels( int x, int y, int width, int height, int pixelCount, const ColorRGBAf* pixels, int miplevel, int frame )
+{
+ if (width == 0 || height == 0)
+ return; // nothing to do
+
+ if (!CheckHasPixelData())
+ return;
+
+ int mipcount = CountMipmaps();
+ if (miplevel < 0 || miplevel >= mipcount)
+ {
+ ErrorString ("Invalid mip level");
+ return;
+ }
+
+ UInt8* data = m_TexData.data + frame * m_TexData.imageSize;
+ data += CalculateMipMapOffset (m_TexData.width, m_TexData.height, m_TexData.format, miplevel);
+ int dataWidth = std::max (m_TexData.width >> miplevel, 1);
+ int dataHeight = std::max (m_TexData.height >> miplevel, 1);
+
+ SetImagePixelBlock (data, dataWidth, dataHeight, m_TexData.format, x, y, width, height, pixelCount, pixels);
+}
+
+
+
+ColorRGBAf Texture2D::GetPixel (int frame,int x, int y) const
+{
+ if (!CheckHasPixelData())
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+
+ if (frame > m_ImageCount) {
+ ErrorString (Format ("GetPixel called on an undefined image (valid values are 0 - %d", m_ImageCount - 1));
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+ }
+
+ return GetImagePixel (m_TexData.data + frame*m_TexData.imageSize, m_TexData.width, m_TexData.height, m_TexData.format, static_cast<TextureWrapMode>(GetSettings().m_WrapMode), x, y);
+}
+
+ColorRGBAf Texture2D::GetPixelBilinear (int frame, float u, float v) const
+{
+ if (!CheckHasPixelData())
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+ if (frame > m_ImageCount) {
+ ErrorString (Format ("GetPixelBilinear called on an undefined image (valid values are 0 - %d", m_ImageCount - 1));
+ return ColorRGBAf(1.0F,1.0F,1.0F,1.0F);
+ }
+ return GetImagePixelBilinear (m_TexData.data + frame*m_TexData.imageSize, m_TexData.width, m_TexData.height, m_TexData.format, static_cast<TextureWrapMode>(GetSettings().m_WrapMode), u, v);
+}
+
+bool Texture2D::GetPixels( int x, int y, int width, int height, int miplevel, ColorRGBAf* colors, int frame ) const
+{
+ if (width == 0 || height == 0)
+ return true; // nothing to do
+
+ if (!CheckHasPixelData())
+ return false;
+
+ int mipcount = CountMipmaps();
+ if (miplevel < 0 || miplevel >= mipcount)
+ {
+ ErrorString ("Invalid mip level");
+ return false;
+ }
+
+ UInt8* data = m_TexData.data + frame * m_TexData.imageSize;
+ data += CalculateMipMapOffset (m_TexData.width, m_TexData.height, m_TexData.format, miplevel);
+ int dataWidth = std::max (m_TexData.width >> miplevel, 1);
+ int dataHeight = std::max (m_TexData.height >> miplevel, 1);
+
+ return GetImagePixelBlock (data, dataWidth, dataHeight, m_TexData.format, x, y, width, height, colors);
+}
+
+bool Texture2D::GetPixels32( int miplevel, ColorRGBA32* colors ) const
+{
+ AssertIf( miplevel < 0 || miplevel >= CountDataMipmaps() );
+
+ ImageReference texImage;
+ if( !GetImageReferenceInternal(&texImage, 0, miplevel) )
+ {
+ if (m_TexData.data != NULL && IsAnyCompressedTextureFormat(m_TexData.format))
+ {
+ UInt8* data = m_TexData.data + CalculateMipMapOffset( m_TexData.width, m_TexData.height, m_TexData.format, miplevel );
+ const int minSize = GetMinimumTextureMipSizeForFormat(m_TexData.format);
+ // the decompress size is at least minSize x minSize
+ int width = std::max (m_TexData.width >> miplevel, minSize);
+ int height = std::max (m_TexData.height >> miplevel, minSize);
+ // also, it should be a multiple of minSize
+ if (width % minSize == 0 && height % minSize == 0)
+ {
+ DecompressNativeTextureFormatWithMipLevel( m_TexData.format, width, height,miplevel, reinterpret_cast<const UInt32*>(data), width, height, reinterpret_cast<UInt32*>(colors) );
+ return true;
+ }
+ else
+ {
+ // make it an upper multiple of minSize
+ int decompWidth = (width + minSize - 1) / minSize * minSize;
+ int decompHeight = (height + minSize - 1) / minSize * minSize;
+ Image decompressed( decompWidth, decompHeight, kTexFormatRGBA32 );
+ DecompressNativeTextureFormatWithMipLevel( m_TexData.format, width, height,miplevel, reinterpret_cast<const UInt32*>(data), decompWidth, decompHeight, (UInt32*)decompressed.GetImageData() );
+ // clip it back to the original size
+ ImageReference clipped = decompressed.ClipImage( 0, 0, width, height );
+ // blit it over to the colors array
+ ImageReference resultImage( width, height, GetRowBytesFromWidthAndFormat(width,kTexFormatRGBA32), kTexFormatRGBA32, colors );
+ resultImage.BlitImage( clipped, ImageReference::BLIT_COPY );
+ return true;
+ }
+ }
+ AssertString( "Invalid texture" );
+ return false;
+ }
+
+ int texWidth = texImage.GetWidth();
+ int texHeight = texImage.GetHeight();
+
+ ImageReference resultImage( texWidth, texHeight, GetRowBytesFromWidthAndFormat(texWidth,kTexFormatRGBA32), kTexFormatRGBA32, colors );
+ resultImage.BlitImage( texImage, ImageReference::BLIT_COPY );
+
+ return true;
+}
+
+
+void Texture2D::SetPixels32( int miplevel, const ColorRGBA32* pixels, const int pixelCount )
+{
+ AssertIf( miplevel < 0 || miplevel >= CountMipmaps() );
+
+ ImageReference texImage;
+ if( !GetWriteImageReference(&texImage, 0, miplevel) )
+ {
+ AssertString( "Invalid texture format" );
+ return;
+ }
+
+ int texWidth = texImage.GetWidth();
+ int texHeight = texImage.GetHeight();
+
+ if(texWidth * texHeight != pixelCount)
+ {
+ AssertString( "SetPixels32 called with invalid number if pixels in the array" );
+ return;
+ }
+
+ ImageReference inputImage( texWidth, texHeight, GetRowBytesFromWidthAndFormat(texWidth,kTexFormatRGBA32), kTexFormatRGBA32, (void*)pixels );
+ texImage.BlitImage( inputImage, ImageReference::BLIT_COPY );
+}
+
+static const char* kUnsupportedColorPixelFormatMessage = "Unsupported image format - the texture needs to be ARGB32 or RGB24";
+
+static bool IsAllowedToReadPixels ()
+{
+#if UNITY_EDITOR
+ // from Player.h
+ bool IsInsidePlayerLoop ();
+ // Editor needs to read frame buffer outside player loop
+ if( !IsInsidePlayerLoop() )
+ return true;
+#elif WEBPLUG
+ // Allow old web content for compatibility
+ if( !IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_0_a1) )
+ return true;
+#endif
+ return GetGfxDevice().IsInsideFrame() || RenderTexture::GetActive();
+}
+
+// TODO: Check dimensions, make everything work.
+void Texture2D::ReadPixels (int frame, int left, int bottom, int width, int height, int destX, int destY, bool flipped, bool computeMipMap)
+{
+ //Prevent out of bounds reads (case 562067)
+ if (destX < 0 || destY < 0 || destX >= GetDataWidth() || destY >= GetDataHeight())
+ {
+ ErrorString("Trying to read pixels out of bounds");
+ return;
+ }
+ if (width < 0 || height < 0)
+ {
+ ErrorString("Negative read pixels rectangle width|height");
+ return;
+ }
+
+ if( !IsAllowedToReadPixels() )
+ {
+ ErrorString ("ReadPixels was called to read pixels from system frame buffer, while not inside drawing frame.");
+ // Our tests rely on calling ReadImage() during Update() so we allow it anyway...
+ }
+
+ if( frame >= m_ImageCount )
+ {
+ ErrorString (Format ("ReadPixels called on undefined image %d (valid values are 0 - %d", frame, m_ImageCount - 1));
+ return;
+ }
+
+ GfxDeviceRenderer renderer = GetGfxDevice().GetRenderer();
+ bool isGLES = (renderer == kGfxRendererOpenGLES20Mobile || renderer == kGfxRendererOpenGLES30);
+ int texFormat = GetTextureFormat();
+ bool validFormat = texFormat == kTexFormatARGB32 ||
+ texFormat == kTexFormatRGB24 ||
+ texFormat == (kTexFormatRGBA32 && isGLES) ||
+ texFormat == (kTexFormatRGB565 && isGLES);
+ if( !validFormat )
+ {
+ ErrorString(kUnsupportedColorPixelFormatMessage);
+ return;
+ }
+
+ ImageReference image;
+ if( !GetWriteImageReference(&image, frame, 0) )
+ {
+ ErrorString("Unable to retrieve image reference");
+ return;
+ }
+
+ if (RenderTexture::GetActive() == NULL)
+ {
+ Rectf rc = GetRenderManager().GetWindowRect();
+ left += rc.x;
+ bottom += rc.y;
+ }
+
+ if (left < 0) {
+ width += left;
+ left = 0;
+ }
+ if (bottom < 0) {
+ height += bottom;
+ bottom = 0;
+ }
+ if (destX + width > GetDataWidth())
+ width = GetDataWidth() - destX;
+ if (destY + height > GetDataHeight())
+ height = GetDataHeight() - destY;
+
+ GetGfxDevice().ReadbackImage( image, left, bottom, width, height, destX, destY );
+
+ if (flipped)
+ {
+ ImageReference subImage = image.ClipImage (destX, destY, width, height);
+ subImage.FlipImageY();
+ }
+
+ if( computeMipMap && m_MipMap )
+ RebuildMipMap();
+}
+
+
+#if ENABLE_PNG_JPG
+bool Texture2D::EncodeToPNG( dynamic_array<UInt8>& outBuffer )
+{
+ if( IsAnyCompressedTextureFormat(GetTextureFormat()) )
+ {
+ ErrorString(kUnsupportedSetPixelOpFormatMessage);
+ return false;
+ }
+ ImageReference image;
+ if( !GetWriteImageReference( &image, 0, 0 ) )
+ {
+ ErrorString( "Unable to retrieve image reference" );
+ return false;
+ }
+ if( !ConvertImageToPNGBuffer( image, outBuffer ) )
+ {
+ ErrorString( "Failed to encode to PNG" );
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+
+
+bool Texture2D::ResizeWithFormat (int width, int height, TextureFormat format, int flags)
+{
+ if (!m_IsReadable)
+ {
+ ErrorString ("Texture is not readable.");
+ return false;
+ }
+
+ if( IsAnyCompressedTextureFormat(format) )
+ {
+ ErrorStringObject ("Can't resize to a compressed texture format", this);
+ return false;
+ }
+
+ return InitTexture(width, height, format, flags);
+}
+
+
+void Texture2D::Compress (bool dither)
+{
+ if (!m_IsReadable)
+ {
+ ErrorString(Format("Texture '%s' is not readable, Compress will not work. You can make the texture readable in the Texture Import Settings.", GetName()));
+ return;
+ }
+
+ SET_ALLOC_OWNER(this);
+ // If hardware does not support DXT, then nothing to do.
+ if( !gGraphicsCaps.hasS3TCCompression )
+ return;
+
+ #if !GFX_SUPPORTS_DXT_COMPRESSION
+ Assert(!"GraphicsCaps.hasS3TCCompression is invalid");
+ return;
+ #else
+
+ TextureFormat format = GetTextureFormat();
+ // If already compressed, then nothing to do.
+ if( IsAnyCompressedTextureFormat(format) )
+ return;
+
+ // Copy out old data into RGBA32 format.
+ bool mipMaps = HasMipMap();
+ int width = GetDataWidth();
+ int height = GetDataHeight();
+ int rgbaByteSize = mipMaps ?
+ CalculateImageMipMapSize( width, height, kTexFormatRGBA32 ) :
+ CalculateImageSize( width, height, kTexFormatRGBA32 );
+ UInt8* rgbaData = new UInt8[rgbaByteSize];
+ int mipCount = CountDataMipmaps();
+ for( int mip = 0; mip < mipCount; ++mip )
+ {
+ UInt8* rgbaMipData = rgbaData + CalculateMipMapOffset(width, height, kTexFormatRGBA32, mip);
+ int mipWidth = std::max( width >> mip, 1 );
+ int mipHeight = std::max( height >> mip, 1 );
+ ImageReference mipDst( mipWidth, mipHeight, mipWidth * 4, kTexFormatRGBA32, rgbaMipData );
+ ExtractImageInternal( &mipDst, false, 0 );
+ }
+
+ // Reformat texture into DXT format
+ bool hasAlpha = HasAlphaTextureFormat(format);
+ TextureFormat compressedFormat = hasAlpha ? kTexFormatDXT5 : kTexFormatDXT1;
+ InitTexture( width, height, compressedFormat, mipMaps ? kMipmapMask : kNoMipmap );
+
+ // DXT compress RGBA data
+ for( int mip = 0; mip < mipCount; ++mip )
+ {
+ const UInt8* srcMipData = rgbaData + CalculateMipMapOffset(width, height, kTexFormatRGBA32, mip);
+ UInt8* dstMipData = GetRawImageData() + CalculateMipMapOffset(width, height, compressedFormat, mip);
+ int mipWidth = std::max( width >> mip, 1 );
+ int mipHeight = std::max( height >> mip, 1 );
+ FastCompressImage( mipWidth, mipHeight, srcMipData, dstMipData, hasAlpha, dither );
+ }
+
+ delete[] rgbaData;
+
+ UpdateImageDataDontTouchMipmap();
+ #endif //GFX_SUPPORTS_DXT_COMPRESSION
+}
+
+#if UNITY_EDITOR
+void Texture2D::WarnInstantiateDisallowed ()
+{
+ if (!m_IsReadable)
+ {
+ ErrorStringObject(Format("Instantiating a non-readable '%s' texture is not allowed! Please mark the texture readable in the inspector or don't instantiate it.", GetName()), this);
+ }
+}
+
+bool Texture2D::IgnoreMasterTextureLimit () const
+{
+ return m_IgnoreMasterTextureLimit;
+}
+
+void Texture2D::SetIgnoreMasterTextureLimit (bool ignore)
+{
+ m_IgnoreMasterTextureLimit = ignore;
+}
+
+bool Texture2D::GetAlphaIsTransparency() const
+{
+ return m_AlphaIsTransparency;
+}
+
+void Texture2D::SetAlphaIsTransparency(bool is)
+{
+ m_AlphaIsTransparency = is;
+}
+#endif // UNITY_EDITOR
+
+
+void Texture2D::Apply(bool updateMipmaps, bool makeNoLongerReadable)
+{
+ if( makeNoLongerReadable )
+ {
+ SetIsReadable(false);
+ SetIsUnreloadable(true);
+ }
+
+ if( IsAnyCompressedTextureFormat(GetTextureFormat()) )
+ updateMipmaps = false;
+
+ if (updateMipmaps) UpdateImageData();
+ else UpdateImageDataDontTouchMipmap();
+}
+
+
diff --git a/Runtime/Graphics/Texture2D.h b/Runtime/Graphics/Texture2D.h
new file mode 100644
index 0000000..e64a035
--- /dev/null
+++ b/Runtime/Graphics/Texture2D.h
@@ -0,0 +1,208 @@
+#pragma once
+
+#include "Texture.h"
+#include "Configuration/UnityConfigure.h"
+#include "Runtime/Modules/ExportModules.h"
+
+class ColorRGBAf;
+class ColorRGBA32;
+
+
+class EXPORT_COREMODULE Texture2D: public Texture
+{
+public:
+ // Should be called by MAIN thread in order to upload texture data immediately after texture asset was loaded
+ // On some platforms helps to avoid memory peak during level load
+ static void IntegrateLoadedImmediately();
+
+protected:
+
+ // See comments at Texture2D.cpp
+ struct TextureRepresentation {
+ TextureRepresentation();
+
+ UInt8* data; // data for all image frames
+ int width;
+ int height;
+ int format;
+ int imageSize; // size in bytes of one image frames, including mip levels
+ };
+ TextureRepresentation m_TexData; // original data
+ TextureID m_UnscaledTexID;
+
+ static bool s_ScreenReadAllowed;
+
+protected:
+ int m_ImageCount;
+ int m_TextureDimension;
+
+ int m_glWidth;
+ int m_glHeight;
+
+ int m_InitFlags;
+ bool m_MipMap;
+ bool m_PowerOfTwo;
+ bool m_TextureUploaded;
+ bool m_UnscaledTextureUploaded;
+ bool m_IsReadable;
+ bool m_ReadAllowed;
+ bool m_IsUnreloadable;
+
+ #if UNITY_EDITOR
+ bool m_EditorDontWriteTextureData;
+ bool m_IgnoreMasterTextureLimit;
+ bool m_AlphaIsTransparency;
+ #endif
+
+protected:
+ void DestroyTexture ();
+ void DeleteGfxTexture ();
+
+ virtual void UploadTexture (bool dontUseSubImage);
+ virtual void UnloadFromGfxDevice(bool forceUnloadAll);
+ virtual void UploadToGfxDevice();
+
+
+private:
+
+ void DestroyTextureRepresentation( TextureRepresentation* rep );
+ void DestroyTextureRepresentations( TextureRepresentation* scaled, TextureRepresentation* padded, bool freeSourceImage=true );
+
+ void InitTextureRepresentation( TextureRepresentation* rep, int format, const char* tag );
+ void InitTextureRepresentations( TextureRepresentation* scaled, TextureRepresentation* padded );
+
+ void UpdatePOTStatus();
+
+
+ void ExtractMipLevel( TextureRepresentation* dst, int frame, int mipLevel, bool checkCompression, bool scaleToSize );
+ bool ExtractImageInternal( ImageReference* image, bool scaleToSize, int imageIndex ) const;
+ void ExtractCompressedImageInternal( UInt8* dst, int dstWidth, int dstHeight, int imageIndex ) const;
+
+ bool GetImageReferenceInternal (ImageReference* image, int frame, int miplevel) const;
+
+ bool CheckHasPixelData () const;
+
+ MemLabelId GetTextureDataMemoryLabel() const { return ( GetMemoryLabel().label == kMemTextureCacheId ? GetMemoryLabel() : MemLabelId(kMemTextureId, GetMemoryLabel().GetRootHeader()) ); }
+public:
+ REGISTER_DERIVED_CLASS (Texture2D, Texture)
+ DECLARE_OBJECT_SERIALIZE (Texture2D)
+
+ Texture2D (MemLabelId label, ObjectCreationMode mode);
+ // ~Texture2D (); declared-by-macro
+
+ virtual bool MainThreadCleanup ();
+
+ virtual void Reset ();
+ virtual void AwakeFromLoadThreaded ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ virtual TextureDimension GetDimension () const { return static_cast<TextureDimension>(m_TextureDimension); }
+
+ virtual int GetGLWidth() const { return m_glWidth; }
+ virtual int GetGLHeight() const { return m_glHeight; }
+ virtual void ApplySettings();
+
+ virtual int GetDataWidth() const;
+ virtual int GetDataHeight() const;
+
+ virtual int GetRuntimeMemorySize() const;
+ #if ENABLE_PROFILER || UNITY_EDITOR
+ virtual int GetStorageMemorySize() const { return m_TexData.imageSize*m_ImageCount; }
+ #endif
+
+ virtual TextureID GetUnscaledTextureID() const;
+ int CountDataMipmaps () const;
+ bool IsNonPowerOfTwo() const { return !m_PowerOfTwo; }
+
+ enum {
+ kNoMipmap = 0,
+ kMipmapMask = 1 << 0,
+ kThreadedInitialize = 1 << 2,
+ kOSDrawingCompatible = 1 << 3,
+ };
+ virtual bool InitTexture (int width, int height, TextureFormat format, int flags=kMipmapMask, int imageCount=1, intptr_t nativeTex=0);
+
+ void InitTextureInternal (int width, int height, TextureFormat format, int imageSize, UInt8* buffer, int options, int imageCount);
+ UInt8* AllocateTextureData (int imageSize, TextureFormat format, bool initMemory = false);
+ void DeallocateTextureData (UInt8* memory);
+
+ virtual bool HasMipMap () const;
+
+ void SetIsReadable (bool readable) { m_IsReadable = readable; }
+ bool GetIsReadable () const { return m_IsReadable; }
+ void SetIsUnreloadable (bool value) { m_IsUnreloadable = value; }
+ bool GetIsUploaded () const { return m_TextureUploaded; }
+
+ #if UNITY_EDITOR
+ void SetEditorDontWriteTextureData (bool value) { m_EditorDontWriteTextureData = value; }
+ // directly load from an image, used in editor for gizmos/icons
+ void SetImage (const ImageReference& image, int flags = kMipmapMask);
+ virtual void WarnInstantiateDisallowed ();
+ virtual bool IgnoreMasterTextureLimit () const;
+ void SetIgnoreMasterTextureLimit (bool ignore);
+
+ bool GetAlphaIsTransparency() const;
+ void SetAlphaIsTransparency(bool is);
+
+ virtual TextureFormat GetEditorUITextureFormat () const { return GetTextureFormat(); }
+
+ #endif
+
+ virtual void UpdateImageData ();
+ virtual void UpdateImageDataDontTouchMipmap ();
+
+ // Returns the original (may be NPOT) data
+ UInt8 *GetRawImageData (int frame = 0) { return m_TexData.data + frame * m_TexData.imageSize; }
+ int GetRawImageDataSize () const { return m_TexData.imageSize; }
+
+ bool GetWriteImageReference (ImageReference* image, int frame, int miplevel);
+
+ int GetImageCount () const { return m_ImageCount; }
+
+ virtual bool ExtractImage (ImageReference* image, int imageIndex = 0) const;
+
+ bool ResizeWithFormat (int width, int height, TextureFormat format, int flags);
+ bool Resize (int width, int height) { return ResizeWithFormat (width, height, GetTextureFormat(), HasMipMap() ? kMipmapMask : kNoMipmap); }
+
+ int GetTextureFormat () const { return m_TexData.format; }
+
+ void Compress (bool dither);
+
+ virtual void RebuildMipMap ();
+
+ virtual int CountMipmaps () const;
+
+ ColorRGBAf GetPixelBilinear (int image, float u, float v) const;
+ ColorRGBAf GetPixel (int image, int x, int y) const;
+ void SetPixel (int image, int x, int y, const ColorRGBAf& c);
+
+ // Read pixels. Set reversed when reading into a cubemap
+ void ReadPixels (int frame, int left, int bottom, int width, int height, int destX, int destY, bool reversed, bool computeMipMap);
+
+ bool GetPixels (int x, int y, int width, int height, int mipLevel, ColorRGBAf* data, int frame = 0) const;
+ void SetPixels( int x, int y, int width, int height, int pixelCount, const ColorRGBAf* pixels, int miplevel, int frame = 0 );
+
+
+ // always whole mip level, into/from 32 bit RGBA colors
+ // GetPixels32 also supports getting pixels from DXT textures.
+ // For DXT textures the output width/height must have minimum of 4.
+ bool GetPixels32( int mipLevel, ColorRGBA32* data ) const;
+ void SetPixels32( int mipLevel, const ColorRGBA32* pixels, const int pixelCount );
+
+ // Encodes to PNG bytes
+ bool EncodeToPNG( dynamic_array<UInt8>& outBuffer );
+
+ // Is reading the data from this texture allowed by webplayer security
+ void SetReadAllowed (bool allowed) { m_ReadAllowed = allowed; if (!allowed) s_ScreenReadAllowed=false;}
+ bool GetReadAllowed () const { return m_ReadAllowed; }
+
+ void Apply(bool updateMipmaps, bool makeNoLongerReadable);
+
+ static bool GetScreenReadAllowed () { return s_ScreenReadAllowed; }
+
+
+ friend struct TemporaryTextureSerializationRevert;
+};
+
+void ConvertTextureEndianessWrite (int format, UInt8* src, UInt8* dst, int size, bool bBigEndianGPU);
+void ConvertTextureEndianessRead (int format, UInt8* src, int size);
diff --git a/Runtime/Graphics/Texture3D.cpp b/Runtime/Graphics/Texture3D.cpp
new file mode 100644
index 0000000..0c2f6c3
--- /dev/null
+++ b/Runtime/Graphics/Texture3D.cpp
@@ -0,0 +1,322 @@
+#include "UnityPrefix.h"
+#include "Texture3D.h"
+#include "Image.h"
+#include "Runtime/Utilities/BitUtility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+
+static UInt32 CalculateMipOffset3D (int mipCount, int width, int height, int depth, TextureFormat format)
+{
+ UInt32 imageSize = 0;
+ const int bpp = GetBytesFromTextureFormat(format);
+ for (int mip = 0; mip < mipCount; ++mip)
+ {
+ int mipWidth = std::max(width >> mip,1);
+ int mipHeight = std::max(height >> mip,1);
+ int mipDepth = std::max(depth >> mip,1);
+ imageSize += bpp * mipWidth * mipHeight * mipDepth;
+ }
+ return imageSize;
+}
+
+
+Texture3D::Texture3D (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_Width(0)
+, m_Height(0)
+, m_Depth(0)
+, m_Format(kTexFormatARGB32)
+, m_Data(NULL)
+, m_DataSize(0)
+, m_MipMap(false)
+, m_TextureUploaded(false)
+{
+}
+
+Texture3D::~Texture3D ()
+{
+ DestroyTexture();
+}
+
+bool Texture3D::MainThreadCleanup ()
+{
+ DeleteGfxTexture();
+ return Super::MainThreadCleanup();
+}
+
+
+
+void Texture3D::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+
+ if ((awakeMode == kDefaultAwakeFromLoad || awakeMode == kInstantiateOrCreateFromCodeAwakeFromLoad) && m_Data == NULL)
+ return;
+
+ UploadTexture (false);
+}
+
+
+void Texture3D::Reset ()
+{
+ Super::Reset();
+ m_TextureSettings.Reset();
+}
+
+
+bool Texture3D::InitTexture (int width, int height, int depth, TextureFormat format, bool mipMaps)
+{
+ if (!IsPowerOfTwo(width) || !IsPowerOfTwo(height) || !IsPowerOfTwo(depth))
+ {
+ ErrorStringObject ("Texture3D has non-power of two size", this);
+ return false;
+ }
+ if (format >= kTexFormatDXT1 || !IsValidTextureFormat(format))
+ {
+ ErrorStringObject ("Invalid texture format for Texture3D", this);
+ return false;
+ }
+
+ const int kMax3DTextureSize = 1024;
+ if (width < 0 || width > kMax3DTextureSize || height < 0 || height > kMax3DTextureSize || depth < 0 || depth > kMax3DTextureSize)
+ {
+ ErrorStringObject ("Texture3D has out of range width / height / depth", this);
+ return false;
+ }
+
+ m_Width = width;
+ m_Height = height;
+ m_Depth = depth;
+ m_Format = format;
+ m_MipMap = mipMaps;
+
+ const int mipCount = CountMipmaps();
+ UInt32 imageSize = CalculateMipOffset3D (mipCount, width, height, depth, format);
+
+ UInt8* buffer = AllocateTextureData(imageSize, m_Format, true);
+ if (!buffer)
+ return false;
+
+ // Cleanup any old memory
+ DestroyTexture ();
+
+ m_Data = buffer;
+ m_DataSize = imageSize;
+
+ SetTexelSize (1.0f/m_Width, 1.0f/m_Height);
+
+ SetDirty ();
+ return true;
+}
+
+UInt8* Texture3D::AllocateTextureData (int imageSize, TextureFormat format, bool initMemory)
+{
+ // Allocate one more pixel because software bi-linear filtering might require it.
+ int allocSize = imageSize + GetBytesFromTextureFormat(format);
+ void* buffer = UNITY_MALLOC_ALIGNED (kMemTexture, allocSize, 32);
+
+ // In the past the memory manager cleared textures created from script to 0xcd.
+ // Let's keep doing that even though a color like white makes more sense (case 564961).
+ if (initMemory && buffer)
+ memset(buffer, 0xcd, allocSize);
+
+ return static_cast<UInt8*>(buffer);
+}
+
+void Texture3D::DeleteGfxTexture()
+{
+ if (m_TextureUploaded)
+ {
+ ASSERT_RUNNING_ON_MAIN_THREAD
+ GetGfxDevice().DeleteTexture(GetTextureID());
+ m_TextureUploaded = false;
+ }
+}
+
+
+void Texture3D::DestroyTexture()
+{
+ UNITY_FREE (kMemTexture, m_Data);
+ m_Data = NULL;
+ m_DataSize = 0;
+
+ DeleteGfxTexture();
+}
+
+void Texture3D::UploadTexture (bool dontUseSubImage)
+{
+ if (!gGraphicsCaps.has3DTexture)
+ return;
+
+ const BuildSettings* buildSettings = GetBuildSettingsPtr();
+ if (buildSettings && !buildSettings->hasPROVersion)
+ return;
+
+ Assert (GetImageDataPointer() != NULL);
+
+ UInt32 uploadFlags = (dontUseSubImage || !m_TextureUploaded) ? GfxDevice::kUploadTextureDontUseSubImage : GfxDevice::kUploadTextureDefault;
+ GetGfxDevice().UploadTexture3D (GetTextureID(), GetImageDataPointer(), GetImageDataSize(), m_Width, m_Height, m_Depth, m_Format, CountMipmaps(), uploadFlags);
+ Texture::s_TextureIDMap.insert (std::make_pair(GetTextureID(),this));
+ ApplySettings();
+#if UNITY_XENON && !MASTER_BUILD
+ GetGfxDevice().SetTextureName( GetTextureID(), GetName() );
+#endif
+ m_TextureUploaded = true;
+}
+
+void Texture3D::UpdateImageData (bool rebuildMipMaps)
+{
+ if (rebuildMipMaps)
+ RebuildMipMap ();
+ UploadTexture (false);
+ SetDirty ();
+}
+
+
+void Texture3D::RebuildMipMap ()
+{
+ if (!m_MipMap || m_Data == NULL)
+ return;
+ if (IsAnyCompressedTextureFormat(m_Format))
+ {
+ ErrorStringObject ("Rebuilding mipmaps of compressed textures is not supported", this);
+ return;
+ }
+
+ CreateMipMap (m_Data, m_Width, m_Height, m_Depth, m_Format);
+}
+
+
+bool Texture3D::ExtractImage (ImageReference* image, int imageIndex) const
+{
+ if (!m_Data)
+ return false;
+
+ ImageReference source (m_Width, m_Height, m_Width * GetBytesFromTextureFormat(m_Format), m_Format, m_Data);
+ image->BlitImage (source, ImageReference::BLIT_BILINEAR_SCALE);
+ return true;
+}
+
+
+
+int Texture3D::CountMipmaps () const
+{
+ if (m_MipMap)
+ return CalculateMipMapCount3D (m_Width, m_Height, m_Depth);
+ else
+ return 1;
+}
+
+
+void Texture3D::UnloadFromGfxDevice(bool forceUnloadAll)
+{
+ DeleteGfxTexture ();
+}
+
+void Texture3D::UploadToGfxDevice()
+{
+ //@TODO: once texture3D gets the option to unload system memory copy,
+ // then we need to do needReloadFromDisk dance similar to Texture2D.
+
+ UploadTexture (true);
+}
+
+
+void Texture3D::SetPixels (int pixelCount, const ColorRGBAf* pixels, int miplevel)
+{
+ if (pixelCount == 0 || pixels == NULL)
+ return;
+
+ if (!m_Data)
+ {
+ ErrorStringObject("Texture has no data", this);
+ return;
+ }
+
+ int mipcount = CountMipmaps();
+ if (miplevel < 0 || miplevel >= mipcount)
+ {
+ ErrorStringObject ("Invalid mip level", this);
+ return;
+ }
+
+
+ UInt8* data = m_Data + CalculateMipOffset3D (miplevel, m_Width, m_Height, m_Depth, m_Format);
+ const int mipWidth = std::max(m_Width >> miplevel,1);
+ const int mipHeight = std::max(m_Height >> miplevel,1);
+ const int mipDepth = std::max(m_Depth >> miplevel,1);
+
+ SetImagePixelBlock (data, mipWidth, mipHeight*mipDepth, m_Format, 0, 0, mipWidth, mipHeight*mipDepth, pixelCount, pixels);
+}
+
+
+bool Texture3D::GetPixels (ColorRGBAf* dest, int miplevel) const
+{
+ if (!dest)
+ return true; // nothing to do
+
+ if (!m_Data)
+ {
+ ErrorStringObject("Texture has no data", this);
+ return false;
+ }
+
+ int mipcount = CountMipmaps();
+ if (miplevel < 0 || miplevel >= mipcount)
+ {
+ ErrorStringObject ("Invalid mip level", this);
+ return false;
+ }
+
+ UInt8* data = m_Data + CalculateMipOffset3D (miplevel, m_Width, m_Height, m_Depth, m_Format);
+ const int mipWidth = std::max(m_Width >> miplevel,1);
+ const int mipHeight = std::max(m_Height >> miplevel,1);
+ const int mipDepth = std::max(m_Depth >> miplevel,1);
+
+ return GetImagePixelBlock (data, mipWidth, mipHeight*mipDepth, m_Format, 0, 0, mipWidth, mipHeight*mipDepth, dest);
+}
+
+
+
+template<class TransferFunction>
+void Texture3D::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ transfer.Transfer (m_Width, "m_Width", kNotEditableMask);
+ transfer.Transfer (m_Height, "m_Height", kNotEditableMask);
+ transfer.Transfer (m_Depth, "m_Depth", kNotEditableMask);
+ transfer.Transfer (m_Format, "m_Format", kNotEditableMask);
+ transfer.Transfer (m_MipMap, "m_MipMap", kNotEditableMask);
+ transfer.Align();
+ transfer.Transfer (m_DataSize, "m_DataSize", kNotEditableMask);
+ transfer.Transfer (m_TextureSettings, "m_TextureSettings");
+
+ unsigned dataSize = m_DataSize;
+ transfer.TransferTypeless (&dataSize, "image data", kHideInEditorMask);
+
+ if (transfer.IsReading ())
+ {
+ DestroyTexture ();
+ Assert(GetMemoryLabel().label != kMemTextureCacheId);
+
+ m_DataSize = dataSize;
+ m_Data = AllocateTextureData(dataSize, m_Format, false);
+
+ SetTexelSize (1.0f/m_Width, 1.0f/m_Height);
+ }
+
+ // texture data
+ //@TODO: format / endianess conversions
+ transfer.TransferTypelessData (dataSize, m_Data);
+
+ Assert (m_DataSize == 0 || m_Data != NULL);
+}
+
+
+
+IMPLEMENT_CLASS (Texture3D)
+IMPLEMENT_OBJECT_SERIALIZE (Texture3D)
diff --git a/Runtime/Graphics/Texture3D.h b/Runtime/Graphics/Texture3D.h
new file mode 100644
index 0000000..8618e9b
--- /dev/null
+++ b/Runtime/Graphics/Texture3D.h
@@ -0,0 +1,67 @@
+#pragma once
+
+#include "Texture.h"
+
+
+class Texture3D : public Texture
+{
+public:
+ REGISTER_DERIVED_CLASS (Texture3D, Texture)
+ DECLARE_OBJECT_SERIALIZE (Texture3D)
+
+ Texture3D (MemLabelId label, ObjectCreationMode mode);
+
+ virtual bool MainThreadCleanup ();
+
+
+ virtual void Reset ();
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ void UploadTexture (bool dontUseSubImage);
+ bool InitTexture (int width, int height, int depth, TextureFormat format, bool mipMaps);
+ void UpdateImageData (bool rebuildMipMaps);
+
+ UInt8* GetImageDataPointer() { return m_Data; }
+ UInt32 GetImageDataSize() const { return m_DataSize; }
+ int GetDepth() const { return m_Depth; }
+ TextureFormat GetTextureFormat() const { return m_Format; }
+
+ bool GetPixels (ColorRGBAf* dest, int miplevel) const;
+ void SetPixels (int pixelCount, const ColorRGBAf* pixels, int miplevel);
+
+ // Texture
+ virtual TextureDimension GetDimension () const { return kTexDim3D; }
+ virtual bool ExtractImage (ImageReference* image, int imageIndex = 0) const;
+ virtual int GetDataWidth() const { return m_Width; }
+ virtual int GetDataHeight() const { return m_Height; }
+ virtual bool HasMipMap () const { return m_MipMap; }
+ virtual int CountMipmaps() const;
+ #if ENABLE_PROFILER
+ virtual int GetStorageMemorySize() const { return m_DataSize; }
+ #endif
+ #if UNITY_EDITOR
+ virtual TextureFormat GetEditorUITextureFormat () const { return GetTextureFormat(); }
+ #endif
+ virtual int GetRuntimeMemorySize() const { return m_DataSize; }
+
+protected:
+ // Texture
+ virtual void UnloadFromGfxDevice(bool forceUnloadAll);
+ virtual void UploadToGfxDevice();
+
+private:
+ UInt8* AllocateTextureData(int imageSize, TextureFormat format, bool initMemory);
+ void DestroyTexture();
+ void RebuildMipMap ();
+ void DeleteGfxTexture();
+
+private:
+ int m_Width;
+ int m_Height;
+ int m_Depth;
+ TextureFormat m_Format;
+ UInt8* m_Data;
+ UInt32 m_DataSize;
+ bool m_MipMap;
+ bool m_TextureUploaded;
+};
diff --git a/Runtime/Graphics/TextureFormat.cpp b/Runtime/Graphics/TextureFormat.cpp
new file mode 100644
index 0000000..27d60fa
--- /dev/null
+++ b/Runtime/Graphics/TextureFormat.cpp
@@ -0,0 +1,248 @@
+#include "UnityPrefix.h"
+#include "TextureFormat.h"
+#include "Runtime/Utilities/LogAssert.h"
+
+// NOTE: match indices in kTexFormat* enum!
+
+const static int kTextureByteTable[kTexFormatTotalCount] =
+{
+ 0,
+ 1, // kTexFormatAlpha8
+ 2, // kTexFormatARGB4444
+ 3, // kTexFormatRGB24
+ 4, // kTexFormatRGBA32
+ 4, // kTexFormatARGB32
+ 16, // kTexFormatARGBFloat
+ 2, // kTexFormatRGB565
+ 3, // kTexFormatBGR24
+ 2, // kTexFormatAlphaLum16
+ 0, // kTexFormatDXT1 (Depends on width, height, depth)
+ 0, // kTexFormatDXT3 (Depends on width, height, depth)
+ 0, // kTexFormatDXT5 (Depends on width, height, depth)
+ 2, // kTexFormatRGBA4444
+};
+
+UInt32 GetBytesFromTextureFormat (TextureFormat inFormat)
+{
+ AssertMsg (inFormat < kTexFormatDXT1 || inFormat == kTexFormatBGRA32 || inFormat == kTexFormatRGBA4444, "Invalid texture format: %d", (int)inFormat);
+ return (inFormat == kTexFormatBGRA32) ? 4 : kTextureByteTable[inFormat];
+}
+
+UInt32 GetMaxBytesPerPixel (TextureFormat inFormat)
+{
+ Assert (GetBytesFromTextureFormat (inFormat) <= kTextureByteTable[kTexFormatARGBFloat]);
+ return kTextureByteTable[kTexFormatARGBFloat];
+}
+
+int GetRowBytesFromWidthAndFormat (int width, TextureFormat inFormat)
+{
+ AssertMsg (inFormat < kTexFormatDXT1 || inFormat == kTexFormatBGRA32 || inFormat == kTexFormatRGBA4444, "Invalid texture format: %d", (int)inFormat);
+ return GetBytesFromTextureFormat (inFormat) * width;
+}
+
+bool IsValidTextureFormat (TextureFormat format)
+{
+ if ((format >= kTexFormatAlpha8 && format <= kTexFormatRGBA4444) ||
+ IsCompressedPVRTCTextureFormat(format) ||
+ IsCompressedETCTextureFormat(format) ||
+ IsCompressedATCTextureFormat(format) ||
+ IsCompressedFlashATFTextureFormat (format) ||
+ IsCompressedETC2TextureFormat(format) ||
+ IsCompressedASTCTextureFormat(format) ||
+ IsCompressedEACTextureFormat(format) ||
+ format == kTexFormatBGRA32)
+ return true;
+ else
+ return false;
+}
+
+int GetTextureSizeAllowedMultiple( TextureFormat format )
+{
+ if (IsCompressedDXTTextureFormat(format) || IsCompressedATCTextureFormat(format) || IsCompressedETCTextureFormat(format)
+ || IsCompressedETC2TextureFormat(format) || IsCompressedEACTextureFormat(format))
+ return 4;
+ else
+ return 1;
+}
+
+int GetMinimumTextureMipSizeForFormat( TextureFormat format )
+{
+ if (format == kTexFormatPVRTC_RGBA2 || format == kTexFormatPVRTC_RGB2)
+ return 16;
+ else if (format == kTexFormatPVRTC_RGBA4 || format == kTexFormatPVRTC_RGB4)
+ return 8;
+ else if (IsCompressedETCTextureFormat(format) || IsCompressedETC2TextureFormat(format) || IsCompressedEACTextureFormat(format))
+ return 4;
+ else if (IsCompressedATCTextureFormat(format))
+ return 4;
+ else if (IsCompressedASTCTextureFormat(format))
+ return 1;
+ else if (IsCompressedFlashATFTextureFormat(format))
+ return 1;
+ else if (IsCompressedDXTTextureFormat(format))
+ return 4;
+ else
+ return 1;
+}
+
+bool IsAlphaOnlyTextureFormat( TextureFormat format )
+{
+ return format == kTexFormatAlpha8;
+}
+
+
+TextureFormat ConvertToAlphaTextureFormat (TextureFormat format)
+{
+ if (format == kTexFormatRGB24 || format == kTexFormatBGR24)
+ return kTexFormatARGB32;
+ else if (format == kTexFormatRGB565)
+ return kTexFormatARGB4444;
+ else if (format == kTexFormatDXT1)
+ return kTexFormatDXT5;
+ else if (format == kTexFormatPVRTC_RGB2)
+ return kTexFormatPVRTC_RGBA2;
+ else if (format == kTexFormatPVRTC_RGB4)
+ return kTexFormatPVRTC_RGBA4;
+ else if (format == kTexFormatETC_RGB4)
+ return kTexFormatARGB4444;
+ else if (format == kTexFormatATC_RGB4)
+ return kTexFormatATC_RGBA8;
+ else if (IsCompressedFlashATFTextureFormat(format))
+ return kTexFormatFlashATF_RGBA_JPG;
+ else if (format == kTexFormatETC2_RGB)
+ return kTexFormatETC2_RGBA8;
+ else
+ return format;
+}
+
+bool HasAlphaTextureFormat( TextureFormat format )
+{
+ return format == kTexFormatAlpha8 || format == kTexFormatARGB4444 || format == kTexFormatRGBA4444 || format == kTexFormatRGBA32 || format == kTexFormatARGB32
+ || format == kTexFormatARGBFloat || format == kTexFormatAlphaLum16 || format == kTexFormatDXT5 || format == kTexFormatDXT3
+ || format == kTexFormatPVRTC_RGBA2 || format == kTexFormatPVRTC_RGBA4 || format == kTexFormatATC_RGBA8 || format == kTexFormatBGRA32 || format == kTexFormatFlashATF_RGBA_JPG
+ || format == kTexFormatETC2_RGBA1 || format == kTexFormatETC2_RGBA8 || (format >= kTexFormatASTC_RGBA_4x4 && format <= kTexFormatASTC_RGBA_12x12);
+}
+
+bool IsDepthRTFormat( RenderTextureFormat format )
+{
+ return format == kRTFormatDepth || format == kRTFormatShadowMap;
+}
+
+bool IsHalfRTFormat( RenderTextureFormat format )
+{
+ return format == kRTFormatARGBHalf || format == kRTFormatRGHalf || format == kRTFormatRHalf || IsDepthRTFormat(format);
+}
+
+const char* GetCompressionTypeString (TextureFormat format)
+{
+ // Shortcut here, no sense in typing all block sizes in switchcase below
+ if(IsCompressedASTCTextureFormat(format))
+ return "ASTC";
+
+ switch (format)
+ {
+ case kTexFormatDXT1: return "DXT1";
+ case kTexFormatDXT3: return "DXT3";
+ case kTexFormatDXT5: return "DXT5";
+ case kTexFormatETC_RGB4: return "ETC1";
+ case kTexFormatETC2_RGB:
+ case kTexFormatETC2_RGBA1:
+ case kTexFormatETC2_RGBA8: return "ETC2";
+ case kTexFormatEAC_R:
+ case kTexFormatEAC_R_SIGNED:
+ case kTexFormatEAC_RG:
+ case kTexFormatEAC_RG_SIGNED: return "EAC";
+ case kTexFormatPVRTC_RGB2:
+ case kTexFormatPVRTC_RGBA2:
+ case kTexFormatPVRTC_RGB4:
+ case kTexFormatPVRTC_RGBA4: return "PVRTC";
+ case kTexFormatATC_RGB4:
+ case kTexFormatATC_RGBA8: return "ATC";
+ case kTexFormatFlashATF_RGB_DXT1: return "DXT1";
+ case kTexFormatFlashATF_RGBA_JPG: return "JPG";
+ case kTexFormatFlashATF_RGB_JPG: return "JPG";
+ default:
+ return "Uncompressed";
+ }
+}
+
+const char* GetTextureFormatString (TextureFormat format)
+{
+ switch (format)
+ {
+ case kTexFormatAlpha8: return "Alpha 8";
+ case kTexFormatARGB4444: return "ARGB 16 bit";
+ case kTexFormatRGBA4444: return "RGBA 16 bit";
+ case kTexFormatRGB24: return "RGB 24 bit";
+ case kTexFormatRGBA32: return "RGBA 32 bit";
+ case kTexFormatARGB32: return "ARGB 32 bit";
+ case kTexFormatARGBFloat: return "ARGB float";
+ case kTexFormatRGB565: return "RGB 16 bit";
+ case kTexFormatBGR24: return "BGR 24 bit";
+ case kTexFormatAlphaLum16: return "Alpha 16 bit";
+ case kTexFormatDXT1: return "RGB Compressed DXT1";
+ case kTexFormatDXT3: return "RGBA Compressed DXT3";
+ case kTexFormatDXT5: return "RGBA Compressed DXT5";
+
+ // gles
+ case kTexFormatPVRTC_RGB2: return "RGB Compressed PVRTC 2 bits";
+ case kTexFormatPVRTC_RGBA2: return "RGBA Compressed PVRTC 2 bits";
+ case kTexFormatPVRTC_RGB4: return "RGB Compressed PVRTC 4 bits";
+ case kTexFormatPVRTC_RGBA4: return "RGBA Compressed PVRTC 4 bits";
+ case kTexFormatETC_RGB4: return "RGB Compressed ETC 4 bits";
+ case kTexFormatATC_RGB4: return "RGB Compressed ATC 4 bits";
+ case kTexFormatATC_RGBA8: return "RGBA Compressed ATC 8 bits";
+
+ case kTexFormatETC2_RGB: return "RGB Compressed ETC2 4 bits";
+ case kTexFormatETC2_RGBA1: return "RGB + 1-bit Alpha Compressed ETC2 4 bits";
+ case kTexFormatETC2_RGBA8: return "RGBA Compressed ETC2 8 bits";
+ case kTexFormatEAC_R: return "11-bit R Compressed EAC 4 bit";
+ case kTexFormatEAC_R_SIGNED: return "11-bit signed R Compressed EAC 4 bit";
+ case kTexFormatEAC_RG: return "11-bit RG Compressed EAC 8 bit";
+ case kTexFormatEAC_RG_SIGNED: return "11-bit signed RG Compressed EAC 8 bit";
+
+#define STR_(x) #x
+#define STR(x) STR_(x)
+#define DO_ASTC(bx,by) case kTexFormatASTC_RGB_##bx##x##by : return "RGB Compressed ASTC " STR(bx) "x" STR(by) " block"; case kTexFormatASTC_RGBA_##bx##x##by : return "RGBA Compressed ASTC " STR(bx) "x" STR(by) " block"
+
+ DO_ASTC(4, 4);
+ DO_ASTC(5, 5);
+ DO_ASTC(6, 6);
+ DO_ASTC(8, 8);
+ DO_ASTC(10, 10);
+ DO_ASTC(12, 12);
+
+#undef DO_ASTC
+#undef STR
+#undef STR_
+
+ // Flash
+ case kTexFormatFlashATF_RGB_DXT1: return "RGB Compressed DXT1";
+ case kTexFormatFlashATF_RGBA_JPG: return "RGBA JPG Compressed";
+ case kTexFormatFlashATF_RGB_JPG: return "RGB JPG Compressed";
+
+ case kTexFormatBGRA32: return "BGRA 32 bit";
+
+ default:
+ return "Unsupported";
+ }
+}
+
+const char* GetTextureColorSpaceString (TextureColorSpace colorSpace)
+{
+ switch (colorSpace)
+ {
+ case kTexColorSpaceLinear: return "Linear";
+ case kTexColorSpaceSRGB: return "sRGB";
+ case kTexColorSpaceSRGBXenon: return "sRGB (Xenon)";
+ default: return "Unsupported";
+ }
+}
+
+TextureColorSpace ColorSpaceToTextureColorSpace(BuildTargetPlatform platform, ColorSpace colorSpace)
+{
+ if (colorSpace == kGammaColorSpace)
+ return (platform == kBuildXBOX360) ? kTexColorSpaceSRGBXenon : kTexColorSpaceSRGB;
+ else
+ return kTexColorSpaceLinear;
+}
diff --git a/Runtime/Graphics/TextureFormat.h b/Runtime/Graphics/TextureFormat.h
new file mode 100644
index 0000000..5d6eed0
--- /dev/null
+++ b/Runtime/Graphics/TextureFormat.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+#include "Runtime/Math/ColorSpaceConversion.h"
+
+/* Important note about endianess.
+ Endianess needs to be swapped for the following formats:
+ kTexFormatARGBFloat, kTexFormatRGB565, kTexFormatARGB4444, (assuming for this too: kTexFormatRGBA4444)
+*/
+
+UInt32 GetBytesFromTextureFormat( TextureFormat inFormat );
+UInt32 GetMaxBytesPerPixel( TextureFormat inFormat );
+int GetRowBytesFromWidthAndFormat( int width, TextureFormat format );
+bool IsValidTextureFormat (TextureFormat format);
+
+
+inline bool IsCompressedDXTTextureFormat( TextureFormat format )
+{
+ return format >= kTexFormatDXT1 && format <= kTexFormatDXT5;
+}
+
+inline bool IsCompressedPVRTCTextureFormat( TextureFormat format )
+{
+ return format >= kTexFormatPVRTC_RGB2 && format <= kTexFormatPVRTC_RGBA4;
+}
+
+inline bool IsCompressedETCTextureFormat( TextureFormat format )
+{
+ return format == kTexFormatETC_RGB4;
+}
+
+inline bool IsCompressedEACTextureFormat( TextureFormat format )
+{
+ return format >= kTexFormatEAC_R && format <= kTexFormatEAC_RG_SIGNED;
+}
+
+inline bool IsCompressedETC2TextureFormat( TextureFormat format )
+{
+ return format >= kTexFormatETC2_RGB && format <= kTexFormatETC2_RGBA8;
+}
+
+inline bool IsCompressedATCTextureFormat( TextureFormat format )
+{
+ return format == kTexFormatATC_RGB4 || format == kTexFormatATC_RGBA8;
+}
+inline bool IsCompressedFlashATFTextureFormat( TextureFormat format )
+{
+ return format == kTexFormatFlashATF_RGB_DXT1 || format == kTexFormatFlashATF_RGBA_JPG || format == kTexFormatFlashATF_RGB_JPG;
+}
+inline bool Is16BitTextureFormat(TextureFormat format )
+{
+ return format == kTexFormatARGB4444 || format == kTexFormatRGBA4444 || format == kTexFormatRGB565;
+}
+
+inline bool IsCompressedASTCTextureFormat( TextureFormat format)
+{
+ return format >= kTexFormatASTC_RGB_4x4 && format <= kTexFormatASTC_RGBA_12x12;
+}
+
+inline bool IsTextureFormatSupportedOnFlash(TextureFormat format )
+{
+ return IsCompressedFlashATFTextureFormat(format) ||
+ format == kTexFormatARGB32 ||
+ format == kTexFormatRGBA32 ||
+ format == kTexFormatRGB24 ||
+ format == kTexFormatAlpha8;
+}
+
+inline bool IsAnyCompressedTextureFormat( TextureFormat format )
+{
+ return IsCompressedDXTTextureFormat(format) || IsCompressedPVRTCTextureFormat(format)
+ || IsCompressedETCTextureFormat(format) || IsCompressedATCTextureFormat(format)
+ || IsCompressedFlashATFTextureFormat (format) || IsCompressedEACTextureFormat(format)
+ || IsCompressedETC2TextureFormat(format) || IsCompressedASTCTextureFormat(format);
+}
+
+bool IsAlphaOnlyTextureFormat( TextureFormat format );
+
+int GetTextureSizeAllowedMultiple( TextureFormat format );
+int GetMinimumTextureMipSizeForFormat( TextureFormat format );
+bool IsAlphaOnlyTextureFormat( TextureFormat format );
+
+TextureFormat ConvertToAlphaTextureFormat (TextureFormat format);
+
+bool HasAlphaTextureFormat( TextureFormat format );
+
+bool IsDepthRTFormat( RenderTextureFormat format );
+bool IsHalfRTFormat( RenderTextureFormat format );
+
+const char* GetCompressionTypeString (TextureFormat format);
+const char* GetTextureFormatString (TextureFormat format);
+const char* GetTextureColorSpaceString (TextureColorSpace colorSpace);
+
+TextureColorSpace ColorSpaceToTextureColorSpace(BuildTargetPlatform platform, ColorSpace colorSpace);
+
+std::pair<int,int> RoundTextureDimensionsToBlocks (TextureFormat fmt, int w, int h);
diff --git a/Runtime/Graphics/TextureGenerator.h b/Runtime/Graphics/TextureGenerator.h
new file mode 100644
index 0000000..13e9dac
--- /dev/null
+++ b/Runtime/Graphics/TextureGenerator.h
@@ -0,0 +1,114 @@
+#ifndef TEXTUREGENERATOR_H
+#define TEXTUREGENERATOR_H
+
+#include "TextureFormat.h"
+#include "Texture3D.h"
+#include "Texture.h"
+#include "Runtime/Math/Vector3.h"
+#include "CubemapTexture.h"
+
+template<typename T, class ColorFunctor>
+void GenerateTexture (Texture2D *tex, const ColorFunctor &fun) {
+ AssertIf (!tex);
+ int maxX = tex->GetGLWidth (), maxY = tex->GetGLHeight();
+ T *data = (T*)tex->GetRawImageData();
+ int bpp = GetBytesFromTextureFormat (tex->GetTextureFormat()) / sizeof(T);
+ for (int y = 0; y < maxY; y++)
+ for (int x = 0; x < maxX; x++) {
+ fun (tex, data, x, y, maxX, maxY);
+ data+=bpp;
+ }
+}
+
+template<typename T, class ColorFunctor>
+void Generate3DTexture (Texture3D *tex, const ColorFunctor &fun) {
+ AssertIf (!tex);
+ int maxX = tex->GetGLWidth (), maxY = tex->GetGLHeight(), maxZ = tex->GetDepth();
+ T *data = (T*)tex->GetImageDataPointer();
+ int bpp = GetBytesFromTextureFormat (tex->GetTextureFormat()) / sizeof(T);
+ for (int z = 0; z < maxZ; z++) {
+ for (int y = 0; y < maxY; y++) {
+ for (int x = 0; x < maxX; x++) {
+ fun (data, x, y, z, maxX, maxY, maxZ);
+ data += bpp;
+ }
+ }
+ }
+}
+
+template<typename T, class ColorFunctor>
+void GenerateCubeTexture (Cubemap *cubemap, const ColorFunctor &fun) {
+ AssertIf (!cubemap);
+ for (int direction = 0; direction < 6; direction++) {
+ const int kCubeXRemap[6] = { 2, 2, 0, 0, 0, 0 };
+ const int kCubeYRemap[6] = { 1, 1, 2, 2, 1, 1 };
+ const int kCubeZRemap[6] = { 0, 0, 1, 1, 2, 2 };
+ const float kCubeXSign[6] = { -1.0F, 1.0F, 1.0F, 1.0F, 1.0F, -1.0F };
+ const float kCubeYSign[6] = { -1.0F, -1.0F, 1.0F, -1.0F, -1.0F, -1.0F };
+ const float kCubeZSign[6] = { 1.0F, -1.0F, 1.0F, -1.0F, 1.0F, -1.0F };
+
+ int width = cubemap->GetGLWidth ();
+ int height = cubemap->GetGLHeight ();
+
+ // do the sign scale according to the cubemap specs then flip y sign
+ Vector3f signScale = Vector3f (kCubeXSign[direction], kCubeYSign[direction], kCubeZSign[direction]);
+ int byteSize = GetBytesFromTextureFormat (cubemap->GetTextureFormat ());
+
+ Vector2f invSize (1.0F / (float)width, 1.0F / (float)height);
+ UInt8* dest = cubemap->GetRawImageData (direction);
+ for (int y=0;y<height;y++)
+ {
+ for (int x=0;x<width;x++)
+ {
+ Vector2f uv = Scale (Vector2f (x, y), invSize) * 2.0F - Vector2f (1.0F, 1.0F);
+ Vector3f uvDir = Vector3f (uv.x, uv.y, 1.0F);
+
+ uvDir.Scale (signScale);
+ Vector3f worldDir;
+ // Rotate the uv to the world direction using a table lookup
+ worldDir[kCubeXRemap[direction]] = uvDir[0];
+ worldDir[kCubeYRemap[direction]] = uvDir[1];
+ worldDir[kCubeZRemap[direction]] = uvDir[2];
+
+ fun ((T*)dest, worldDir);
+ dest += byteSize;
+ }
+ }
+ }
+}
+
+template<typename T, typename ColorFunctor>
+Texture2D *BuildTexture (int width, int height, TextureFormat format, const ColorFunctor &fun, bool mipmaps = false) {
+ Texture2D* tex = CreateObjectFromCode<Texture2D>();
+ tex->SetHideFlags(Object::kHideAndDontSave);
+ tex->InitTexture (width, height, format, mipmaps ? Texture2D::kMipmapMask : Texture2D::kNoMipmap, 1);
+ tex->GetSettings().m_Aniso = 0; // disable aniso
+ GenerateTexture<T> (tex, fun);
+ mipmaps ? tex->UpdateImageData() : tex->UpdateImageDataDontTouchMipmap();
+ return tex;
+}
+
+template<typename T, class ColorFunctor>
+Texture3D *Build3DTexture (int width, int height, int depth, TextureFormat format, const ColorFunctor &fun) {
+ Texture3D *tex = CreateObjectFromCode<Texture3D>();
+ tex->SetHideFlags(Object::kHideAndDontSave);
+ tex->InitTexture (width, height, depth, format, false);
+ Generate3DTexture<T> (tex, fun);
+ tex->UpdateImageData(false);
+ return tex;
+}
+
+template<typename T, class ColorFunctor>
+Cubemap *BuildCubeTexture (int size, TextureFormat format, const ColorFunctor &fun) {
+ Cubemap *tex = CreateObjectFromCode<Cubemap>();
+ tex->SetHideFlags(Object::kHideAndDontSave);
+ tex->InitTexture (size, size, format, 0, 6);
+ GenerateCubeTexture<T> (tex, fun);
+ tex->UpdateImageDataDontTouchMipmap();
+ tex->GetSettings ().m_WrapMode = kTexWrapClamp;
+ tex->ApplySettings ();
+ return tex;
+}
+
+
+#endif
diff --git a/Runtime/Graphics/TextureSettings.cpp b/Runtime/Graphics/TextureSettings.cpp
new file mode 100644
index 0000000..a11b565
--- /dev/null
+++ b/Runtime/Graphics/TextureSettings.cpp
@@ -0,0 +1,64 @@
+#include "UnityPrefix.h"
+#include "TextureSettings.h"
+#include "Runtime/Utilities/Utility.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+
+static int gUserMinAniso = 1;
+static int gUserMaxAniso = 16;
+
+
+void TextureSettings::SetAnisoLimits (int minAniso, int maxAniso)
+{
+ gUserMinAniso = minAniso;
+ gUserMaxAniso = maxAniso;
+ ErrorIf (gUserMinAniso < 1);
+ ErrorIf (gUserMaxAniso > 16);
+}
+
+void TextureSettings::GetAnisoLimits (int& minAniso, int& maxAniso)
+{
+ minAniso = gUserMinAniso;
+ maxAniso = gUserMaxAniso;
+}
+
+
+void TextureSettings::Reset ()
+{
+ m_FilterMode = kTexFilterBilinear;
+ m_Aniso = 1;
+ m_MipBias = 0.0f;
+ m_WrapMode = 0;
+}
+
+void TextureSettings::CheckConsistency()
+{
+ m_FilterMode = clamp<int> (m_FilterMode, 0, kTexFilterCount-1);
+ m_WrapMode = clamp<int> (m_WrapMode, 0, kTexWrapCount-1);
+}
+
+
+void TextureSettings::Apply (TextureID texture, TextureDimension texDim, bool hasMipMap, TextureColorSpace colorSpace) const
+{
+ GfxDevice& device = GetGfxDevice();
+
+ int aniso;
+ // Never use anisotropic on textures where we certainly don't want it,
+ // and on Point filtered textures.
+ if (m_Aniso == 0 || m_FilterMode == kTexFilterNearest)
+ aniso = 1;
+ else
+ aniso = clamp (m_Aniso, gUserMinAniso, gUserMaxAniso);
+
+ device.SetTextureParams (texture, texDim, (TextureFilterMode)m_FilterMode,
+ (TextureWrapMode)m_WrapMode, aniso, hasMipMap, colorSpace);
+}
+
+#if UNITY_EDITOR
+void TextureSettings::Invalidate ()
+{
+ m_FilterMode = -1;
+ m_Aniso = -1;
+ m_MipBias = -1.0f;
+ m_WrapMode = -1;
+}
+#endif
diff --git a/Runtime/Graphics/TextureSettings.h b/Runtime/Graphics/TextureSettings.h
new file mode 100644
index 0000000..3082a5a
--- /dev/null
+++ b/Runtime/Graphics/TextureSettings.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/GfxDevice/GfxDeviceTypes.h"
+
+
+// Texture filter, anisotropy, wrap mode settings.
+struct TextureSettings
+{
+ DECLARE_SERIALIZE_NO_PPTR (GLTextureSettings) // keep the name GLTextureSettings here, it's for serialized stuff!
+
+ int m_FilterMode; ///< enum { Nearest, Bilinear, Trilinear } Texture filter mode
+ int m_Aniso; ///< Anisotropy factor (1 = None, 0 = Always disabled)
+ float m_MipBias; ///< Bias used for LOD-selection (0 = none)
+ int m_WrapMode; ///< enum {Repeat, Clamp} Texture wrapping mode.
+
+ TextureSettings () { Reset (); }
+
+ // Set default values
+ void Reset();
+
+ void CheckConsistency();
+
+ #if UNITY_EDITOR
+ // Set all numbers to -1, marking them as invalid
+ void Invalidate();
+ #endif
+
+ void Apply (TextureID texture, TextureDimension texDim, bool hasMipMap, TextureColorSpace colorSpace) const;
+
+ static void SetAnisoLimits (int minAniso, int maxAniso);
+ static void GetAnisoLimits (int& minAniso, int& maxAniso);
+};
+
+template<class TransferFunc>
+void TextureSettings::Transfer (TransferFunc& transfer)
+{
+ TRANSFER (m_FilterMode);
+ TRANSFER (m_Aniso);
+ TRANSFER (m_MipBias);
+ TRANSFER (m_WrapMode);
+}
diff --git a/Runtime/Graphics/Transform.cpp b/Runtime/Graphics/Transform.cpp
new file mode 100644
index 0000000..eaa9bdb
--- /dev/null
+++ b/Runtime/Graphics/Transform.cpp
@@ -0,0 +1,1695 @@
+#include "UnityPrefix.h"
+#include "Transform.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/BaseClasses/SupportedMessageOptimization.h"
+#include "Runtime/Serialize/SerializationMetaFlags.h"
+#include "Runtime/Utilities/Word.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Utilities/RecursionLimit.h"
+#include "Runtime/Utilities/Prefetch.h"
+#include "Runtime/Utilities/ValidateArgs.h"
+#include "Runtime/Misc/BuildSettings.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/SceneInspector.h"
+static Transform::HierarchyChangedCallback* gHierarchyChangedCallback = NULL;
+static Transform::HierarchyChangedCallbackSetParent* gHierarchyChangedSetParentCallback = NULL;
+#endif
+
+static Transform* FindActiveTransformWithPathImpl (const char* path, GameObject& go, bool needsToBeRoot);
+
+// parentTransform * (translation * rotation * scale)
+
+#if DEBUGMODE
+#define ASSERT_ROTATION if (!CompareApproximately (SqrMagnitude (m_LocalRotation), 1.0F)) { AssertStringObject(Format("transform.rotation of '%s' is no longer valid, due to a bad input value. Input rotation is %f, %f, %f, %f.", GetName(), q.x, q.y, q.z, q.w), this); }
+#else
+#define ASSERT_ROTATION
+#endif
+
+#pragma message ("Move this away")
+inline float InverseSafe (float f)
+{
+ if (Abs (f) > Vector3f::epsilon)
+ return 1.0F / f;
+ else
+ return 0.0F;
+
+}
+
+inline Vector3f InverseSafe (const Vector3f& v)
+{
+ return Vector3f (InverseSafe (v.x), InverseSafe (v.y), InverseSafe (v.z));
+}
+
+static float MakeNiceAbs(float f)
+{
+ union
+ {
+ float f;
+ UInt32 n;
+ } u;
+ u.f = f;
+
+ int exponent = ((u.n & 0x7F800000) >> 23) - 127;
+
+ if(exponent >= 24)
+ return f;
+
+ if(exponent <= -24)
+ return 0;
+
+ UInt32 v = (UInt32)f;
+ float fraction = f - v;
+
+ if(fraction < 0.00001f)
+ return v;
+
+ if(fraction > 0.99999f)
+ return v + 1;
+
+ return f;
+}
+
+static float MakeNice(float f)
+{
+ if(f >= 0)
+ return MakeNiceAbs(f);
+ else
+ return -MakeNiceAbs(-f);
+}
+
+Vector3f MakeNice(const Vector3f& v)
+{
+ return Vector3f(MakeNice(v[0]), MakeNice(v[1]), MakeNice(v[2]));
+}
+
+
+
+
+Transform::Transform (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+//, m_Children (Transform::TransformComList::allocator_type(*baseAllocator))
+#if UNITY_EDITOR
+, m_VisibleRootValid(false)
+#endif
+, m_CachedTransformType(kNoScaleTransform)
+, m_HasCachedTransformMatrix(false)
+, m_HasChanged(false)
+{
+ m_InternalTransformType = kNoScaleTransform;
+ m_SupportsTransformChanged = 0;
+
+#if UNITY_EDITOR
+ // Create hint that will make euler angles at editor time match those in play mode
+ //
+ m_LocalEulerAnglesHint.x = 179.999f;
+ m_LocalEulerAnglesHint.y = 179.999f;
+ m_LocalEulerAnglesHint.z = 179.999f;
+ #endif
+}
+
+void Transform::Reset ()
+{
+ Super::Reset ();
+ m_LocalRotation = Quaternionf::identity ();
+
+ m_LocalPosition = Vector3f::zero;
+ m_LocalScale = Vector3f::one;
+
+#if UNITY_EDITOR
+ m_LocalEulerAnglesHint.x = 0;
+ m_LocalEulerAnglesHint.y = 0;
+ m_LocalEulerAnglesHint.z = 0;
+#endif
+
+ RecalculateTransformType ();
+
+
+ m_HasCachedTransformMatrix = false;
+ m_HasChanged = true;
+
+ if (GetGameObjectPtr())
+ SendTransformChanged (kPositionChanged | kRotationChanged | kScaleChanged | kParentingChanged);
+
+#if ENABLE_EDITOR_HIERARCHY_ORDERING
+ m_Order = 0;
+#endif
+}
+
+Transform::~Transform ()
+{
+#if UNITY_EDITOR
+ if (m_VisibleRootValid)
+ GetSceneTracker().GetVisibleRootTransforms().erase(m_VisibleRootIterator);
+#endif
+}
+
+Transform::iterator Transform::Find( const Transform* child )
+{
+ iterator it, itEnd = end();
+ for( it = begin(); it != itEnd; ++it )
+ {
+ if( *it == child )
+ return it;
+ }
+ return itEnd;
+}
+
+void Transform::RemoveFromParent ()
+{
+// Assert(!IsPersistent());
+
+ // If it has an father, remove this from fathers children
+ Transform* father = GetParent ();
+ if (father != NULL)
+ {
+// Assert(!father->IsPersistent());
+
+ TransformComList& children = father->m_Children;
+ // Fastpath try back of children array
+ if (!children.empty() != 0 && &*children.back() == this)
+ {
+ children.pop_back();
+ }
+ // Find this in fathers children list
+ else
+ {
+ iterator i = father->Find(this);
+ if (i != children.end ())
+ children.erase (i);
+ }
+
+ father->SetDirty ();
+ }
+}
+
+void Transform::ClearChildrenParentPointer ()
+{
+ for (int i=0;i<m_Children.size();i++)
+ {
+ Transform* cur = m_Children[i];
+
+ AssertIf(cur == NULL || cur->GetParent() != this);
+ if (cur && cur->GetParent() == this)
+ cur->m_Father = NULL;
+ }
+}
+
+void Transform::ClearChild (Transform* child)
+{
+ iterator found = Find(child);
+ if (found != m_Children.end())
+ m_Children.erase(found);
+}
+
+Matrix3x3f Transform::GetWorldScale () const
+{
+ Matrix3x3f invRotation;
+ QuaternionToMatrix (Inverse (GetRotation ()), invRotation);
+ Matrix3x3f scaleAndRotation = GetWorldRotationAndScale ();
+ return invRotation * scaleAndRotation;
+}
+
+Vector3f Transform::GetWorldScaleLossy () const
+{
+ Matrix3x3f rot = GetWorldScale ();
+ return Vector3f (rot.Get (0, 0), rot.Get (1, 1), rot.Get (2, 2));
+}
+
+
+Matrix3x3f Transform::GetWorldRotationAndScale () const
+{
+ Matrix3x3f scale;
+ scale.SetScale (m_LocalScale);
+
+ Matrix3x3f rotation;
+ QuaternionToMatrix (m_LocalRotation, rotation);
+
+ Transform* parent = GetParent ();
+ if (parent)
+ {
+ ///@TODO optimize: Special case multiplication
+ Matrix3x3f parentTransform = parent->GetWorldRotationAndScale ();
+ return parentTransform * rotation * scale;
+ }
+ else
+ {
+ return rotation * scale;
+ }
+}
+
+bool Transform::SetParent (Transform* newFather, SetParentOption options)
+{
+ if (GetGameObject().IsDestroying() || (newFather && newFather->GetGameObject().IsDestroying()))
+ return false;
+
+ if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_0_a1))
+ {
+ if ( (GetParent () && GetParent ()->GetGameObject().IsActivating()) || (newFather && newFather->GetGameObject().IsActivating()))
+ {
+ ErrorStringObject ("Cannot change GameObject hierarchy while activating or deactivating the parent.", this);
+ return false;
+ }
+ }
+
+ // Make sure that the new father is not a child of this transform.
+ Transform* cur;
+ for (cur=newFather;cur != NULL;cur = cur->m_Father)
+ {
+ if (this == cur)
+ return false;
+ }
+
+ if ((options & kAllowParentingFromPrefab) == 0)
+ {
+ if (IsPrefabParent() || (newFather && newFather->IsPrefabParent()))
+ {
+ ErrorString("Setting the parent of a transform which resides in a prefab is disabled to prevent data corruption.");
+ return false;
+ }
+ }
+
+// if (IsPersistent() || (newFather && newFather->IsPersistent()))
+// {
+// ErrorString("Setting the parent of a transform on an asset is not allowed!");
+// return false;
+// }
+
+ // Save the old position in worldspace
+ Vector3f worldPosition = GetPosition ();
+ Quaternionf worldRotation = GetRotation ();
+ Matrix3x3f worldScale = GetWorldRotationAndScale ();
+
+ // If it already has an father, remove this from fathers children
+ Transform* father = GetParent ();
+ if (father != NULL)
+ {
+ // Find this in fathers children list
+ iterator i = father->Find( this );
+ AssertIf (i == father->end ());
+ father->m_Children.erase (i);
+ father->SetDirty ();
+ }
+
+ #if UNITY_EDITOR
+ if (newFather)
+ {
+ ///@TODO: This can use binary search because we should be able to assume that the children array is already sorted
+ iterator i = newFather->begin();
+ iterator end = newFather->end();
+
+ for (;i!=end;i++)
+ {
+ #if ENABLE_EDITOR_HIERARCHY_ORDERING
+ if (CompareDepths(this, (*i)))
+ #else
+ if (SemiNumericCompare(GetName(), (**i).GetName()) < 0)
+ #endif
+ {
+ newFather->m_Children.insert (i, this);
+ break;
+ }
+ }
+ if (i == end)
+ newFather->m_Children.push_back (this);
+
+ newFather->SetDirty ();
+ }
+ #else
+ if (newFather)
+ {
+ newFather->m_Children.push_back (this);
+ newFather->SetDirty ();
+ }
+ #endif
+
+ m_Father = newFather;
+
+ if (!(options & kDisableTransformMessage))
+ {
+ if (options & kWorldPositionStays)
+ {
+ // Restore old position so they stay at the same position in worldspace
+ SetRotationSafe (worldRotation);
+ SetPosition (worldPosition);
+ SetWorldRotationAndScale ( worldScale );
+ SendTransformChanged (kParentingChanged);
+ }
+ else
+ SendTransformChanged (kPositionChanged | kRotationChanged | kScaleChanged | kParentingChanged);
+ }
+
+ #if UNITY_EDITOR
+ if (gHierarchyChangedSetParentCallback)
+ gHierarchyChangedSetParentCallback (this, father, newFather);
+ #endif
+ SetDirty ();
+ SetCacheDirty();
+
+ return true;
+}
+
+
+#if UNITY_EDITOR
+
+#if ENABLE_EDITOR_HIERARCHY_ORDERING
+bool Transform::CompareVisibleRoots::operator() (const VisibleRootKey& lhs, const VisibleRootKey& rhs) const
+{
+ bool orderCompare = lhs.first != rhs.first;
+ if (orderCompare)
+ return lhs.first < rhs.first;
+ else
+ {
+ VisibleRootSecondaryKey lhsSecondaryKey = lhs.second;
+ VisibleRootSecondaryKey rhsSecondaryKey = rhs.second;
+ int compare = SemiNumericCompare(lhsSecondaryKey.first, rhsSecondaryKey.first);
+ if (compare != 0)
+ return compare < 0;
+ return lhsSecondaryKey.second < rhsSecondaryKey.second;
+ }
+}
+#else
+bool Transform::CompareVisibleRoots::operator() (const VisibleRootKey& lhs, const VisibleRootKey& rhs) const
+{
+ int compare = SemiNumericCompare(lhs.first, rhs.first);
+ if (compare != 0)
+ return compare < 0;
+ return lhs.second < rhs.second;
+}
+#endif
+
+// Cannot be called Repeat as there is already another function with that name (which does currently not work for negative numbers)
+inline float RepeatWorking (float t, float length)
+{
+ return (t - (floor (t / length) * length));
+}
+
+void Transform::SyncLocalEulerAnglesHint ()
+{
+ if (IsWorldPlaying())
+ return;
+
+ Vector3f newEuler = QuaternionToEuler(m_LocalRotation) * Rad2Deg(1);
+
+ newEuler.x = RepeatWorking(newEuler.x - m_LocalEulerAnglesHint.x + 180.0F, 360.0F) + m_LocalEulerAnglesHint.x - 180.0F;
+ newEuler.y = RepeatWorking(newEuler.y - m_LocalEulerAnglesHint.y + 180.0F, 360.0F) + m_LocalEulerAnglesHint.y - 180.0F;
+ newEuler.z = RepeatWorking(newEuler.z - m_LocalEulerAnglesHint.z + 180.0F, 360.0F) + m_LocalEulerAnglesHint.z - 180.0F;
+
+ m_LocalEulerAnglesHint = MakeNice(newEuler);
+}
+#endif
+
+void Transform::SetLocalEulerAngles (const Vector3f& eulerAngles)
+{
+ ABORT_INVALID_VECTOR3 (eulerAngles, localEulerAngles, transform)
+
+ SetLocalRotationSafe (EulerToQuaternion (eulerAngles * Deg2Rad (1)));
+
+ #if UNITY_EDITOR
+ if (!IsWorldPlaying())
+ {
+ m_LocalEulerAnglesHint = MakeNice(eulerAngles);
+ }
+ #endif
+}
+
+Vector3f Transform::GetLocalEulerAngles ()
+{
+ #if UNITY_EDITOR
+ if (!IsWorldPlaying())
+ {
+ if ( !CompareApproximately (EulerToQuaternion (m_LocalEulerAnglesHint * Deg2Rad(1)), m_LocalRotation) )
+ SyncLocalEulerAnglesHint ();
+
+ return m_LocalEulerAnglesHint;
+ }
+ else
+ {
+ Quaternionf rotation = NormalizeSafe (m_LocalRotation);
+ return QuaternionToEuler (rotation) * Rad2Deg (1);
+ }
+ #else
+ Quaternionf rotation = NormalizeSafe (m_LocalRotation);
+ return QuaternionToEuler (rotation) * Rad2Deg (1);
+ #endif
+}
+
+
+void Transform::SetLocalPosition (const Vector3f& inTranslation)
+{
+ ABORT_INVALID_VECTOR3 (inTranslation, localPosition, transform);
+ m_LocalPosition = inTranslation;
+ SetDirty ();
+ SendTransformChanged (kPositionChanged);
+}
+
+void Transform::SetLocalRotation (const Quaternionf& q)
+{
+ ABORT_INVALID_QUATERNION (q, localRotation, transform);
+ m_LocalRotation = q;
+
+#if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+#endif
+ ASSERT_ROTATION
+
+ SetDirty ();
+ SendTransformChanged (kRotationChanged);
+}
+
+void Transform::SetLocalRotationSafe (const Quaternionf& q)
+{
+ SetLocalRotation (NormalizeSafe(q));
+}
+
+void Transform::SetRotation (const Quaternionf& q)
+{
+ Transform* father = GetParent ();
+ if (father != NULL)
+ SetLocalRotation (Inverse (father->GetRotation ()) * q);
+ else
+ SetLocalRotation (q);
+}
+
+void Transform::SetRotationSafe(const Quaternionf& q)
+{
+ ABORT_INVALID_QUATERNION (q, rotation, transform);
+ Transform* father = GetParent ();
+ if (father != NULL)
+ SetLocalRotation (NormalizeSafe (Inverse (father->GetRotation ()) * q));
+ else
+ SetLocalRotation (NormalizeSafe (q));
+}
+
+void Transform::SetPosition (const Vector3f& p)
+{
+ ABORT_INVALID_VECTOR3 (p, position, transform);
+
+ Vector3f newPosition = p;
+ Transform* father = GetParent ();
+ if (father != NULL)
+ newPosition = father->InverseTransformPoint (newPosition);
+
+ SetLocalPosition (newPosition);
+}
+
+void Transform::SetPositionWithLocalOffset (const Vector3f& p, const Vector3f& localOffset)
+{
+ ABORT_INVALID_VECTOR3 (p, positionWithLocalOffset, transform);
+ ABORT_INVALID_VECTOR3 (localOffset, positionWithLocalOffset, transform);
+ Vector3f newPosition = p - TransformPoint (localOffset) + GetPosition ();
+ SetPosition (newPosition);
+}
+
+Vector3f Transform::TransformPointWithLocalOffset (const Vector3f& p, const Vector3f& localOffset) const
+{
+ return p - TransformPoint (localOffset) + GetPosition ();
+}
+
+void Transform::SetWorldRotationAndScale (const Matrix3x3f& scale)
+{
+ m_LocalScale = Vector3f::one;
+
+ Matrix3x3f inverseRS = GetWorldRotationAndScale ();
+ inverseRS.Invert ();
+
+ inverseRS = inverseRS * scale;
+
+ m_LocalScale.x = inverseRS.Get (0, 0);
+ m_LocalScale.y = inverseRS.Get (1, 1);
+ m_LocalScale.z = inverseRS.Get (2, 2);
+
+ RecalculateTransformType ();
+ SetDirty ();
+ SendTransformChanged (kScaleChanged | kRotationChanged | kPositionChanged);
+}
+
+void Transform::SetLocalScale (const Vector3f& scale)
+{
+ ABORT_INVALID_VECTOR3 (scale, localScale, transform);
+ m_LocalScale = scale;
+ RecalculateTransformType ();
+ SetDirty ();
+ SendTransformChanged (kScaleChanged | kRotationChanged | kPositionChanged);
+}
+
+template<bool Safe, bool Notify>
+void Transform::SetPositionAndRotationInternal ( const Vector3f& p, const Quaternionf& q )
+{
+ ABORT_INVALID_VECTOR3 (p, position, transform);
+ ABORT_INVALID_QUATERNION (q, rotation, transform);
+ Transform* father = GetParent ();
+ if (father != NULL)
+ {
+ m_LocalPosition = father->InverseTransformPoint (p);
+ if ( Safe )
+ m_LocalRotation = NormalizeSafe (Inverse (father->GetRotation ()) * q);
+ else
+ m_LocalRotation = Inverse (father->GetRotation ()) * q;
+ }
+ else
+ {
+ m_LocalPosition = p;
+ if ( Safe )
+ m_LocalRotation = NormalizeSafe (q);
+ else
+ m_LocalRotation = q;
+ }
+#if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+#endif
+
+ ASSERT_ROTATION
+ if ( Notify )
+ {
+ SetDirty ();
+ SendTransformChanged ( kPositionChanged | kRotationChanged );
+ }
+}
+
+void Transform::SetPositionAndRotationWithoutNotification (const Vector3f& p, const Quaternionf& q)
+{
+ SetPositionAndRotationInternal<false, false>( p, q );
+}
+
+void Transform::SetPositionAndRotationSafeWithoutNotification (const Vector3f& p, const Quaternionf& q)
+{
+ SetPositionAndRotationInternal<true, false>( p, q );
+}
+
+void Transform::SetPositionAndRotation (const Vector3f& p, const Quaternionf& q)
+{
+ SetPositionAndRotationInternal<false, true>( p, q );
+}
+
+void Transform::SetPositionAndRotationSafe (const Vector3f& p, const Quaternionf& q)
+{
+ SetPositionAndRotationInternal<true, true>( p, q );
+}
+
+void Transform::SetPositionWithoutNotification (const Vector3f& p)
+{
+ ABORT_INVALID_VECTOR3 (p, position, transform);
+ Transform* father = GetParent ();
+ if (father != NULL)
+ m_LocalPosition = father->InverseTransformPoint (p);
+ else
+ m_LocalPosition = p;
+}
+
+void Transform::SetRotationWithoutNotification (const Quaternionf& q)
+{
+ Transform* father = GetParent ();
+ if (father != NULL)
+ m_LocalRotation = Inverse (father->GetRotation ()) * q;
+ else
+ m_LocalRotation = q;
+}
+
+void Transform::SetLocalPositionAndRotation (const Vector3f& p, const Quaternionf& q)
+{
+ ABORT_INVALID_VECTOR3 (p, localPosition, transform);
+ ABORT_INVALID_QUATERNION (q, localRotation, transform);
+ m_LocalPosition = p;
+ m_LocalRotation = q;
+
+ #if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+ #endif
+
+ ASSERT_ROTATION
+
+ SetDirty ();
+ SendTransformChanged (kPositionChanged | kRotationChanged);
+}
+#if UNITY_EDITOR
+void Transform::RegisterHierarchyChangedCallback (HierarchyChangedCallback* callback)
+{
+ gHierarchyChangedCallback = callback;
+}
+
+void Transform::RegisterHierarchyChangedSetParentCallback (HierarchyChangedCallbackSetParent* callback)
+{
+ gHierarchyChangedSetParentCallback = callback;
+}
+#endif
+
+UInt32 Transform::CalculateSupportedMessages ()
+{
+ if (GetGameObject().WillHandleMessage(kTransformChanged))
+ return kSupportsTransformChanged;
+ else
+ return 0;
+}
+
+void Transform::MakeEditorValuesLookNice()
+{
+ m_LocalScale = MakeNice(m_LocalScale);
+ m_LocalPosition = MakeNice(m_LocalPosition);
+}
+
+void Transform::SupportedMessagesDidChange (int mask)
+{
+ Super::SupportedMessagesDidChange(mask);
+ m_SupportsTransformChanged = mask & kSupportsTransformChanged;
+}
+
+void Transform::SendTransformChanged (int changeMask)
+{
+ bool parentingChanged = changeMask & kParentingChanged;
+
+ // Fastpath if we don't support any TransformChanged callbacks on this game object
+ if (!m_SupportsTransformChanged && !parentingChanged)
+ {
+ m_HasCachedTransformMatrix = false;
+ m_HasChanged = true;
+
+ TransformComList::iterator i;
+ TransformComList::iterator end = m_Children.end ();
+ for (i = m_Children.begin ();i != end;i++)
+ {
+ Transform* child = *i;
+ child->SendTransformChanged (changeMask | kPositionChanged);
+ }
+
+ return;
+ }
+
+ m_HasCachedTransformMatrix = false;
+ m_HasChanged = true;
+
+ // NOTE: SetCacheDirty() doesn't need to be called here since SendTransformChanged traverses the transform hierarchy anyway
+
+ GameObject& go = GetGameObject ();
+ if (m_SupportsTransformChanged)
+ {
+ MessageData data;
+ data.SetData (changeMask, ClassID (int));
+ go.SendMessageAny (kTransformChanged, data);
+ }
+
+ if (parentingChanged)
+ go.TransformParentHasChanged ();
+
+ TransformComList::iterator i;
+ TransformComList::iterator end = m_Children.end ();
+ for (i = m_Children.begin ();i != end;i++)
+ {
+ Transform* child = *i;
+ child->SendTransformChanged (changeMask | kPositionChanged);
+ }
+}
+
+void Transform::SetCacheDirty()
+{
+ m_HasCachedTransformMatrix = false;
+ m_HasChanged = true;
+
+ TransformComList::iterator end = m_Children.end ();
+ for (TransformComList::iterator i = m_Children.begin (); i != end; ++i)
+ (*i)->SetCacheDirty();
+}
+
+void Transform::BroadcastMessageAny(const MessageIdentifier& messageID, MessageData& data)
+{
+ GameObject* go = GetGameObjectPtr ();
+ if (go)
+ go->SendMessageAny (messageID, data);
+
+ TransformComList::iterator i;
+ for (i = m_Children.begin ();i != m_Children.end ();i++)
+ (**i).BroadcastMessageAny (messageID, data);
+}
+
+void Transform::RotateAroundLocal (const Vector3f& localAxis, float rad)
+{
+ AssertIf (!CompareApproximately (Magnitude (localAxis), 1.0F));
+
+ Quaternionf q = AxisAngleToQuaternion (localAxis, rad);
+ m_LocalRotation = Normalize (q * m_LocalRotation);
+ #if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+ #endif
+
+ SetDirty ();
+ SendTransformChanged (kRotationChanged);
+}
+
+void Transform::RotateAroundLocalSafe (const Vector3f& localAxis, float rad)
+{
+ if (SqrMagnitude (localAxis) > Vector3f::epsilon)
+ RotateAroundLocal (Normalize (localAxis), rad);
+}
+
+void Transform::RotateAround (const Vector3f& worldAxis, float rad)
+{
+ AssertIf (!CompareApproximately (Magnitude (worldAxis), 1.0F));
+
+ Vector3f localAxis = InverseTransformDirection(worldAxis);
+
+ Quaternionf q = AxisAngleToQuaternion (localAxis, rad);
+ m_LocalRotation = Normalize (m_LocalRotation * q);
+ #if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+ #endif
+
+ SetDirty ();
+ SendTransformChanged (kRotationChanged);
+}
+
+void Transform::RotateAroundSafe (const Vector3f& worldAxis, float rad)
+{
+ Vector3f localAxis = InverseTransformDirection(worldAxis);
+ if (SqrMagnitude (localAxis) > Vector3f::epsilon)
+ {
+ localAxis = Normalize (localAxis);
+ Quaternionf q = AxisAngleToQuaternion (localAxis, rad);
+ m_LocalRotation = Normalize (m_LocalRotation * q);
+ #if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+ #endif
+
+ SetDirty ();
+ SendTransformChanged (kRotationChanged);
+ }
+}
+
+Vector3f Transform::GetPosition () const
+{
+ Vector3f worldPos = m_LocalPosition;
+ Transform* cur = GetParent ();
+ while (cur)
+ {
+ worldPos.Scale (cur->m_LocalScale);
+ worldPos = RotateVectorByQuat (cur->m_LocalRotation, worldPos);
+ worldPos += cur->m_LocalPosition;
+
+ cur = cur->GetParent ();
+ }
+
+ return worldPos;
+}
+
+Quaternionf Transform::GetRotation ()const
+{
+ Quaternionf worldRot = m_LocalRotation;
+ Transform* cur = GetParent ();
+ while (cur)
+ {
+ worldRot = cur->m_LocalRotation * worldRot;
+ cur = cur->GetParent ();
+ }
+
+ return worldRot;
+}
+
+
+void Transform::GetPositionAndRotation (Vector3f& pos, Quaternionf& q) const
+{
+ Vector3f worldPos = m_LocalPosition;
+ Quaternionf worldRot = m_LocalRotation;
+ Transform* cur = GetParent ();
+ while (cur)
+ {
+ worldPos.Scale (cur->m_LocalScale);
+ worldPos = RotateVectorByQuat (cur->m_LocalRotation, worldPos);
+ worldPos += cur->m_LocalPosition;
+
+ worldRot = cur->m_LocalRotation * worldRot;
+
+ cur = cur->GetParent ();
+ }
+
+ pos = worldPos;
+ q = worldRot;
+}
+
+static TransformType DetectActualNegativeScale (int type, const Transform* transform)
+{
+ type &= ~kOddNegativeScaleTransform;
+
+ // Calculate if we need to flip the back facing when rendering
+ // We need to use backface rendering if one or three scale axes are negative (odd count)
+ // In this case we enable kOddNegativeScaleTransform flag
+
+ Transform* cur = (Transform *)transform;
+ while (cur)
+ {
+ TransformType parentType = (TransformType)cur->m_InternalTransformType;
+ // kOddNegativeScaleTransform is XOR against parent (odd+odd=even, odd+even=odd, even+even=even), other bits are OR
+ type = ((type | parentType) & ~kOddNegativeScaleTransform) | ((type ^ parentType) & kOddNegativeScaleTransform);
+ cur = cur->GetParent ();
+ }
+
+ return (TransformType)type;
+}
+
+static TransformType UpdateTransformType (TransformType type, const Transform* transform)
+{
+ if ((type & kOddNegativeScaleTransform) != 0)
+ type = DetectActualNegativeScale (type, transform);
+
+ // kNonUniformScaleTransform 'overwrites' kUniformScaleTransform
+ if ((type & kNonUniformScaleTransform) != 0)
+ type &= ~kUniformScaleTransform;
+
+ Assert ((type & (kNonUniformScaleTransform|kUniformScaleTransform)) != (kNonUniformScaleTransform|kUniformScaleTransform));
+ return type;
+}
+
+
+TransformType Transform::GetPositionAndRotationWithTransformType (Vector3f& worldPos, Quaternionf& worldRot) const
+{
+ TransformType type = (TransformType)m_InternalTransformType;
+
+ worldPos = m_LocalPosition;
+ worldRot = m_LocalRotation;
+ Transform* cur = GetParent ();
+ while (cur)
+ {
+ TransformType parentType = (TransformType)cur->m_InternalTransformType;
+ // kOddNegativeScaleTransform is XOR against parent (odd+odd=even, odd+even=odd, even+even=even), other bits are OR
+ type = ((type | parentType) & ~kOddNegativeScaleTransform) | ((type ^ parentType) & kOddNegativeScaleTransform);
+
+ worldPos.Scale (cur->m_LocalScale);
+ worldPos = RotateVectorByQuat (cur->m_LocalRotation, worldPos);
+ worldPos += cur->m_LocalPosition;
+
+ worldRot = cur->m_LocalRotation * worldRot;
+
+ cur = cur->GetParent ();
+ }
+
+ // kNonUniformScaleTransform 'overwrites' kUniformScaleTransform
+ if ((type & kNonUniformScaleTransform) != 0)
+ type &= ~kUniformScaleTransform;
+ Assert ((type & (kNonUniformScaleTransform|kUniformScaleTransform)) != (kNonUniformScaleTransform|kUniformScaleTransform));
+
+ return type;
+}
+
+TransformType Transform::CalculateTransformMatrix (Matrix4x4f& transform) const
+{
+ //@TODO: Does this give any performance gain??
+ Prefetch(m_CachedTransformMatrix.GetPtr());
+ if (m_HasCachedTransformMatrix)
+ {
+ CopyMatrix(m_CachedTransformMatrix.GetPtr(), transform.GetPtr());
+ return (TransformType)m_CachedTransformType;
+ }
+
+ const Transform* transforms[32];
+ int transformCount = 1;
+ TransformType type = (TransformType)0;
+ Matrix4x4f temp;
+
+ {
+ // collect all transform that need CachedTransformMatrix update
+ transforms[0] = this;
+ Transform* parent = NULL;
+ for (parent = GetParent(); parent != NULL && !parent->m_HasCachedTransformMatrix; parent = parent->GetParent())
+ {
+ transforms[transformCount++] = parent;
+ // reached maximum of transforms that we can calculate - fallback to old method
+ if (transformCount == 31)
+ {
+ parent = parent->GetParent();
+ if (parent)
+ {
+ type = parent->CalculateTransformMatrixIterative(temp);
+ Assert(parent->m_HasCachedTransformMatrix);
+ }
+ break;
+ }
+ }
+
+ // storing parent of last transform (can be null), the transform itself won't be updated
+ transforms[transformCount] = parent;
+ Assert(transformCount <= 31);
+ }
+
+ // iterate transforms from lowest parent
+ for (int i = transformCount - 1; i >= 0; --i)
+ {
+ const Transform* t = transforms[i];
+ const Transform* parent = transforms[i + 1];
+ if (parent)
+ {
+ Assert(parent->m_HasCachedTransformMatrix);
+ // Build the local transform into temp
+ type |= t->CalculateLocalTransformMatrix(temp);
+ type |= (TransformType)parent->m_CachedTransformType;
+ MultiplyMatrices4x4(&parent->m_CachedTransformMatrix, &temp, &t->m_CachedTransformMatrix);
+ }
+ else
+ {
+ // Build the local transform into m_CachedTransformMatrix
+ type |= t->CalculateLocalTransformMatrix(t->m_CachedTransformMatrix);
+ }
+ // store cached transform
+ t->m_CachedTransformType = UpdateTransformType(type, t);
+ t->m_HasCachedTransformMatrix = true;
+ }
+
+ Assert(m_HasCachedTransformMatrix);
+ CopyMatrix(m_CachedTransformMatrix.GetPtr(), transform.GetPtr());
+ return (TransformType)m_CachedTransformType;
+}
+
+
+// This method doesn't cache all transforms - just the last one, but can calculate
+// more than 32 transforms. CalculateTransformMatrix caches all results
+TransformType Transform::CalculateTransformMatrixIterative (Matrix4x4f& transform) const
+{
+ if (m_HasCachedTransformMatrix)
+ {
+ CopyMatrix(m_CachedTransformMatrix.GetPtr(), transform.GetPtr());
+ return (TransformType)m_CachedTransformType;
+ }
+
+ // Build the local transform
+ TransformType type = CalculateLocalTransformMatrix(transform);
+
+ Transform* parent = GetParent ();
+ Matrix4x4f temp;
+ while (parent != NULL)
+ {
+ if (parent->m_HasCachedTransformMatrix)
+ {
+ type |= (TransformType)parent->m_CachedTransformType;
+ MultiplyMatrices4x4 (&parent->m_CachedTransformMatrix, &transform, &temp);
+ // no need to iterate further - we got world transform
+ parent = NULL;
+ }
+ else
+ {
+ Matrix4x4f parentTransform;
+ type |= parent->CalculateLocalTransformMatrix(parentTransform);
+ MultiplyMatrices4x4 (&parentTransform, &transform, &temp);
+ parent = parent->GetParent ();
+ }
+ CopyMatrix (temp.GetPtr(), transform.GetPtr());
+ }
+
+ CopyMatrix(transform.GetPtr(), m_CachedTransformMatrix.GetPtr()) ;
+ m_CachedTransformType = UpdateTransformType(type, this);
+ m_HasCachedTransformMatrix = true;
+ return type;
+}
+
+
+TransformType Transform::CalculateLocalTransformMatrix(Matrix4x4f& matrix) const
+{
+ TransformType type = (TransformType)m_InternalTransformType;
+ if (type == kNoScaleTransform)
+ matrix.SetTR(m_LocalPosition, m_LocalRotation);
+ else
+ matrix.SetTRS(m_LocalPosition, m_LocalRotation, m_LocalScale);
+ return type;
+}
+
+
+TransformType Transform::CalculateTransformMatrixDisableNonUniformScale (Matrix4x4f& transform, Matrix4x4f& scaleOnly, float& uniformScale) const
+{
+ // Use CalculateTransformMatrix in order to take advantage of the cached transform.
+ // Only non-uniform scaled meshes need to take the slower code path.
+/* TransformType cachedTransformType = CalculateTransformMatrix (transform);
+ if (!IsNonUniformScaleTransform(cachedTransformType))
+ {
+ uniformScale = Magnitude(transform.GetAxisX());
+ scaleOnly.SetIdentity();
+ return cachedTransformType;
+ }
+*/
+
+ // Build the local transform!
+ TransformType type = (TransformType)m_InternalTransformType;
+ uniformScale = m_LocalScale.x;
+ if (type == kNoScaleTransform)
+ transform.SetTR (m_LocalPosition, m_LocalRotation);
+ else
+ transform.SetTRS (m_LocalPosition, m_LocalRotation, m_LocalScale);
+
+ // @TBD: cache parent transform and reuse it across CalculateTransformXXX funcs
+ Transform* parent = GetParent ();
+ Matrix4x4f temp;
+ while (parent != NULL)
+ {
+ Matrix4x4f parentTransform;
+
+ TransformType parentType = (TransformType)parent->m_InternalTransformType;
+ // kOddNegativeScaleTransform is XOR against parent (odd+odd=even, odd+even=odd, even+even=even), other bits are OR
+ type = ((type | parentType) & ~kOddNegativeScaleTransform) | ((type ^ parentType) & kOddNegativeScaleTransform);
+
+ if (parentType == kNoScaleTransform)
+ parentTransform.SetTR (parent->m_LocalPosition, parent->m_LocalRotation);
+ else
+ parentTransform.SetTRS (parent->m_LocalPosition, parent->m_LocalRotation, parent->m_LocalScale);
+ uniformScale *= parent->m_LocalScale.x;
+
+ MultiplyMatrices4x4 (&parentTransform, &transform, &temp);
+ CopyMatrix (temp.GetPtr(), transform.GetPtr());
+ parent = parent->GetParent ();
+ }
+
+ // kNonUniformScaleTransform 'overwrites' kUniformScaleTransform
+ if ((type & kNonUniformScaleTransform) != 0)
+ type &= ~kUniformScaleTransform;
+ DebugAssert ((type & (kNonUniformScaleTransform|kUniformScaleTransform)) != (kNonUniformScaleTransform|kUniformScaleTransform));
+
+ // uniform or no scale
+ if (!IsNonUniformScaleTransform(type))
+ {
+ scaleOnly.SetIdentity();
+ return type;
+ }
+ // non-uniform scale
+ else
+ {
+ // Calculate scaleOnlyMatrix (In order to scale the mesh with the non-uniform scale)
+ Matrix4x4f worldToLocalMatrixNoScale;
+ GetWorldToLocalMatrixNoScale (worldToLocalMatrixNoScale);
+ MultiplyMatrices4x4(&worldToLocalMatrixNoScale, &transform, &scaleOnly);
+ scaleOnly.Get (0,3) = 0.0F;
+ scaleOnly.Get (1,3) = 0.0F;
+ scaleOnly.Get (2,3) = 0.0F;
+
+ // Calculate the matrix without any scale applied
+ GetLocalToWorldMatrixNoScale (transform);
+ uniformScale = 1.0F;
+
+ return type;
+ }
+}
+
+
+TransformType Transform::CalculateTransformMatrixDisableScale (Matrix4x4f& matrix) const
+{
+ Vector3f worldPos;
+ Quaternionf worldRot;
+ TransformType type = GetPositionAndRotationWithTransformType(worldPos, worldRot);
+
+ matrix.SetTR (worldPos, worldRot);
+ return type;
+}
+
+TransformType Transform::CalculateTransformMatrixScaleDelta (Matrix4x4f& m) const
+{
+ Matrix4x4f scaledMatrix;
+ TransformType type = CalculateTransformMatrix (scaledMatrix);
+ // Affine matrix?
+ if ((type & (kUniformScaleTransform | kNonUniformScaleTransform)) == 0)
+ {
+ m.SetIdentity ();
+ return type;
+ }
+ else
+ {
+ Matrix4x4f tmp;
+ GetWorldToLocalMatrixNoScale (tmp);
+ MultiplyMatrices4x4(&tmp, &scaledMatrix, &m);
+ m.Get (0,3) = 0.0F;
+ m.Get (1,3) = 0.0F;
+ m.Get (2,3) = 0.0F;
+ return type;
+ }
+}
+
+Matrix4x4f Transform::GetWorldToLocalMatrixNoScale () const
+{
+ Matrix4x4f m;
+ GetWorldToLocalMatrixNoScale(m);
+ return m;
+}
+
+const Matrix4x4f& Transform::GetWorldToLocalMatrixNoScale (Matrix4x4f& m) const
+{
+ Vector3f pos;
+ Quaternionf rot;
+ GetPositionAndRotation(pos, rot);
+ m.SetTRInverse (pos, rot);
+ return m;
+}
+
+Matrix4x4f Transform::GetLocalToWorldMatrixNoScale () const
+{
+ Matrix4x4f m;
+ GetLocalToWorldMatrixNoScale(m);
+ return m;
+}
+
+const Matrix4x4f& Transform::GetLocalToWorldMatrixNoScale (Matrix4x4f& m) const
+{
+ Quaternionf rot; Vector3f pos;
+ GetPositionAndRotation(pos, rot);
+ m.SetTR (pos, rot);
+ return m;
+}
+
+Matrix4x4f Transform::GetWorldToLocalMatrix () const
+{
+ Matrix4x4f m, temp;
+ m.SetTRInverse (m_LocalPosition, m_LocalRotation);
+ if (m_InternalTransformType != kNoScaleTransform)
+ {
+ Matrix4x4f scale;
+ scale.SetScale (InverseSafe (m_LocalScale));
+ MultiplyMatrices4x4 (&scale, &m, &temp);
+ CopyMatrix (temp.GetPtr(), m.GetPtr());
+ }
+
+ Transform* father = GetParent ();
+ if (father != NULL)
+ {
+ Matrix4x4f parentMat = father->GetWorldToLocalMatrix();
+ MultiplyMatrices4x4 (&m, &parentMat, &temp);
+ CopyMatrix (temp.GetPtr(), m.GetPtr());
+ }
+
+ return m;
+}
+
+
+Matrix4x4f Transform::GetLocalToWorldMatrix () const
+{
+ Matrix4x4f m;
+ CalculateTransformMatrix (m);
+ return m;
+}
+
+Vector3f Transform::TransformDirection (const Vector3f& inDirection) const
+{
+ return RotateVectorByQuat (GetRotation (), inDirection);
+}
+
+Vector3f Transform::InverseTransformDirection (const Vector3f& inDirection) const
+{
+ return RotateVectorByQuat (Inverse(GetRotation()), inDirection);
+}
+
+Vector3f Transform::InverseTransformPoint (const Vector3f& inPosition) const
+{
+ Vector3f newPosition, localPosition;
+ Transform* father = GetParent ();
+ if (father)
+ localPosition = father->InverseTransformPoint (inPosition);
+ else
+ localPosition = inPosition;
+
+ localPosition -= m_LocalPosition;
+ newPosition = RotateVectorByQuat (Inverse(m_LocalRotation), localPosition);
+ if (m_InternalTransformType != kNoScaleTransform)
+ newPosition.Scale (InverseSafe (m_LocalScale));
+
+ return newPosition;
+}
+
+Vector3f Transform::TransformPoint (const Vector3f& inPoint) const
+{
+ Vector3f worldPos = inPoint;
+
+ const Transform* cur = this;
+ while (cur)
+ {
+ worldPos.Scale (cur->m_LocalScale);
+ worldPos = RotateVectorByQuat (cur->m_LocalRotation, worldPos);
+ worldPos += cur->m_LocalPosition;
+
+ cur = cur->GetParent ();
+ }
+ return worldPos;
+}
+
+template<class TransferFunction> inline
+void Transform::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ TRANSFER_SIMPLE (m_LocalRotation);
+ TRANSFER_SIMPLE (m_LocalPosition);
+ TRANSFER_SIMPLE (m_LocalScale);
+
+ //TRANSFER_EDITOR_ONLY_HIDDEN (m_LocalEulerAnglesHint);
+
+ // This needs to be here since eg. Mesh collider queries the recalculate transform type.
+ // and awakefromload might not have been called already.
+ if (transfer.IsReading())
+ RecalculateTransformType ();
+
+ // When cloning objects for prefabs and instantiate, we don't use serialization to duplicate the hierarchy,
+ // we duplicate the hierarchy directly
+ if (SerializePrefabIgnoreProperties(transfer))
+ {
+ transfer.Transfer (m_Children, "m_Children", kHideInEditorMask | kStrongPPtrMask | kIgnoreWithInspectorUndoMask);
+ transfer.Transfer (m_Father, "m_Father", kHideInEditorMask | kIgnoreWithInspectorUndoMask);
+ }
+
+#if ENABLE_EDITOR_HIERARCHY_ORDERING
+ TRANSFER_EDITOR_ONLY_HIDDEN(m_Order);
+#endif
+}
+
+void Transform::RecalculateTransformType ()
+{
+ // #pragma message ("Compare approximately is bad due to epsilon changing with the size of the value")
+ if (CompareApproximately (m_LocalScale.x, m_LocalScale.y, 0.0001F) && CompareApproximately (m_LocalScale.y, m_LocalScale.z, 0.0001F))
+ {
+ if (CompareApproximately( m_LocalScale.x, 1.0F, 0.0001F ))
+ {
+ m_InternalTransformType = kNoScaleTransform;
+ }
+ else
+ {
+ m_InternalTransformType = kUniformScaleTransform;
+ if (m_LocalScale.x < 0.0F)
+ {
+ m_InternalTransformType = kOddNegativeScaleTransform | kNonUniformScaleTransform;
+ }
+ }
+ }
+ else
+ {
+ m_InternalTransformType = kNonUniformScaleTransform;
+
+ int hasOddNegativeScale = m_LocalScale.x * m_LocalScale.y * m_LocalScale.z < 0.0F ? 1 : 0;
+ m_InternalTransformType |= (TransformType)(hasOddNegativeScale * kOddNegativeScaleTransform);
+ }
+}
+
+void Transform::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+ SetCacheDirty();
+
+ // Only call SendTransformChanged if it was really changed eg.
+ // by a propepertyeditor or datatemplate propagation but not if it was loaded from disk
+
+ if ((awakeMode & kDidLoadFromDisk) == 0)
+ {
+ // This is for all kinds of non-serialization Awakes.
+ // eg. animation. Because Transfer already recalculates
+ RecalculateTransformType ();
+
+ SendTransformChanged (kPositionChanged | kRotationChanged | kScaleChanged);
+ }
+
+ #if UNITY_EDITOR
+ if (gHierarchyChangedCallback)
+ gHierarchyChangedCallback (this);
+ #endif
+}
+
+inline void MakeValidFloat (float* f)
+{
+ if (!IsFinite (*f))
+ *f = 0.0F;
+}
+
+void Transform::CheckConsistency ()
+{
+ Super::CheckConsistency ();
+ MakeValidFloat (&m_LocalRotation.x);
+ MakeValidFloat (&m_LocalRotation.y);
+ MakeValidFloat (&m_LocalRotation.z);
+ MakeValidFloat (&m_LocalRotation.w);
+ MakeValidFloat (&m_LocalPosition.x);
+ MakeValidFloat (&m_LocalPosition.y);
+ MakeValidFloat (&m_LocalPosition.z);
+ MakeValidFloat (&m_LocalScale.x);
+ MakeValidFloat (&m_LocalScale.y);
+ MakeValidFloat (&m_LocalScale.z);
+ m_LocalRotation = NormalizeSafe (m_LocalRotation);
+ #if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+ #endif
+
+ // Check if father has this as child
+ Transform* father = m_Father;
+ if (father)
+ {
+ if ( father->Find(this) == father->m_Children.end ())
+ father->m_Children.push_back (this);
+ }
+
+ // Check if all children are available and have this as father. Also make
+ // sure that any of our children is on the list exactly once.
+ for (int i=0;i<m_Children.size ();i++)
+ {
+ Transform* child = m_Children[i];
+ Assert(child != this);
+
+ if (child == NULL)
+ {
+ ErrorStringObject ("CheckConsistency: Transform child can't be loaded", this);
+ iterator it = m_Children.begin () + i;
+ m_Children.erase (it);
+ i--;
+ continue;
+ }
+
+ Transform* parent = child->m_Father;
+
+ #if UNITY_EDITOR
+ // We only try to fix the parent pointer in the Editor as we don't want to risk breaking existing players.
+ if (parent == NULL)
+ {
+ child->m_Father = this;
+ ErrorStringObject ("CheckConsistency: Restored Transform child parent pointer from NULL", child);
+ continue;
+ }
+ #endif
+
+ if (parent != this)
+ {
+ iterator it = m_Children.begin () + i;
+ m_Children.erase (it);
+ i--;
+ ErrorStringObject ("CheckConsistency: Transform child has another parent", child);
+ continue;
+ }
+
+ // Look for and remove multiple occurrences.
+ bool occursMultipleTimes = false;
+ for (int j = i + 1; j < m_Children.size ();)
+ {
+ Transform* otherChild = m_Children[j];
+ if (otherChild == child)
+ {
+ occursMultipleTimes = true;
+ m_Children.erase (m_Children.begin () + j);
+ }
+ else
+ ++j;
+ }
+ if (occursMultipleTimes)
+ {
+ ErrorStringObject ("CheckConsistency: Transform child is linked multiple times to parent; removed extraneous links from parent", child);
+ }
+ }
+}
+#if ENABLE_EDITOR_HIERARCHY_ORDERING
+void Transform::SetOrder(SInt32 order)
+{
+ m_Order = order;
+ SetDirty();
+
+ if (gHierarchyChangedCallback)
+ gHierarchyChangedCallback (this);
+
+ SendTransformChanged (kParentingChanged);
+}
+
+int Transform::CompareDepths(Transform* lhs, Transform* rhs)
+ {
+ bool orderCompare = lhs->GetOrder() != rhs->GetOrder();
+ if (orderCompare)
+ return lhs->GetOrder() < rhs->GetOrder();
+ else
+ {
+ const char* lhsName = lhs ? lhs->GetName() : "";
+ const char* rhsName = rhs ? rhs->GetName() : "";
+ return SemiNumericCompare(lhsName, rhsName) < 0;
+ }
+ }
+
+void Transform::OrderChildrenRecursively()
+{
+ std::sort(m_Children.begin(), m_Children.end(), Transform::CompareDepths);
+
+ for (int i = 0; i < m_Children.size(); ++i)
+ {
+ Transform* childTrans = m_Children[i];
+ childTrans->OrderChildrenRecursively();
+ }
+}
+#endif
+
+IMPLEMENT_OBJECT_SERIALIZE (Transform)
+IMPLEMENT_CLASS (Transform)
+
+static inline int FindSeperator (const char* in)
+{
+ const char* c = in;
+ while (*c != '/' && *c != '\0')
+ c++;
+ return c - in;
+}
+
+Transform* FindRelativeTransformWithPath (Transform& transform, const char* path)
+{
+ LIMIT_RECURSION (2000, NULL);
+
+ if (path[0] == '\0')
+ return &transform;
+
+ int seperator = FindSeperator (path);
+
+ if (path[0] == '/')
+ return FindActiveTransformWithPath (path);
+ else if (path[0] == '.' && path[1] == '.')
+ {
+ Transform* parent = transform.GetParent();
+ if (path[2] == '/')
+ {
+ if (parent)
+ return FindRelativeTransformWithPath (*parent, path + 3);
+ else
+ return NULL;
+ }
+ else if (path[2] == '\0')
+ return parent;
+ }
+
+ for (Transform::iterator i=transform.begin ();i != transform.end ();i++)
+ {
+ Transform& child = **i;
+
+ const char* name = child.GetName();
+
+ // Early out if size is not the same
+ if (strlen(name) != seperator)
+ continue;
+
+ // continue if the name isn't the same
+ const char* n = name;
+ int j;
+ for (j=0;j<seperator;j++,n++)
+ if (path[j] != *n)
+ break;
+ if (j != seperator)
+ continue;
+
+ // We found the transform we were searching for
+ if (path[seperator] == '\0')
+ return &child;
+ // Recursively find in the children
+ else
+ {
+ Transform* result = FindRelativeTransformWithPath (child, path + seperator + 1);
+ if (result != NULL)
+ return result;
+ }
+ }
+ return NULL;
+}
+
+string CalculateTransformPath (const Transform& transform, const Transform* to)
+{
+ string path;
+ const Transform* cur = &transform;
+ while (cur != to && cur != NULL)
+ {
+ if (!path.empty ())
+ path = cur->GetName () + ('/' + path);
+ else
+ path = cur->GetName ();
+ cur = cur->GetParent ();
+ }
+ return path;
+}
+
+void AppendTransformPath (string& path, const char* appendName)
+{
+ if (path.empty())
+ path = appendName;
+ else
+ {
+ path += '/';
+ path += appendName;
+ }
+}
+
+void AppendTransformPath (UnityStr& path, const char* appendName)
+{
+ if (path.empty())
+ path = appendName;
+ else
+ {
+ path += '/';
+ path += appendName;
+ }
+}
+
+
+
+Transform* FindActiveTransformWithPath (const char* path)
+{
+ bool needsToBeRoot = path[0] == '/';
+ if (path[0] == '/')
+ path++;
+
+ if (path[0] == 0)
+ return NULL;
+
+ GameObjectList::iterator i;
+
+ GameObjectList& tagged = GetGameObjectManager().m_TaggedNodes;
+ for (i=tagged.begin();i != tagged.end();i++)
+ {
+ Transform* transform = FindActiveTransformWithPathImpl(path, **i, needsToBeRoot);
+ if (transform)
+ return transform;
+ }
+
+ GameObjectList& active = GetGameObjectManager().m_ActiveNodes;
+ for (i=active.begin();i != active.end();i++)
+ {
+ Transform* transform = FindActiveTransformWithPathImpl(path, **i, needsToBeRoot);
+ if (transform)
+ return transform;
+ }
+
+ return NULL;
+/*
+
+ for (int i=0;i<gos.size ();i++)
+ {
+ if (!gos[i]->IsActive ())
+ continue;
+
+ const string& name = gos[i]->GetName ();
+ if (name.find (path, 0, name.size ()) == 0)
+ {
+ path += name.size ();
+ if (path[0] == '/')
+ path ++;
+
+ Transform* transform = gos[i]->QueryComponent (Transform);
+ if (transform)
+ {
+ if (needsToBeRoot && transform->GetParent())
+ continue;
+
+ if (path[0] == 0)
+ return transform;
+ transform = FindRelativeTransformWithPath (*transform, path);
+ if (transform)
+ return transform;
+ }
+ }
+ }
+
+ return NULL;
+*/
+}
+
+static Transform* FindActiveTransformWithPathImpl (const char* path, GameObject& go, bool needsToBeRoot)
+{
+ AssertIf(!go.IsActive());
+
+ const char* name = go.GetName ();
+ size_t size = strlen(name);
+
+ if (strncmp(name, path, size) == 0)
+ {
+ path += size;
+ if (path[0] == '/')
+ path ++;
+
+ Transform* transform = go.QueryComponent (Transform);
+ if (transform)
+ {
+ if (needsToBeRoot && transform->GetParent())
+ return NULL;
+
+ if (path[0] == 0 && transform->IsActive())
+ return transform;
+
+ transform = FindRelativeTransformWithPath (*transform, path);
+ if (transform && transform->IsActive())
+ return transform;
+ }
+ }
+ return NULL;
+}
+
+Transform& Transform::GetRoot ()
+{
+ Transform* cur = this;
+ Transform* curParent = NULL;
+ while ((curParent = cur->GetParent ()) != NULL)
+ cur = curParent;
+
+ return *cur;
+}
+
+bool IsChildOrSameTransform(Transform& transform, Transform& inParent)
+{
+ Transform* child = &transform;
+ while (child)
+ {
+ if (child == &inParent)
+ return true;
+ child = child->GetParent();
+ }
+ return false;
+}
+
+#if ENABLE_EDITOR_HIERARCHY_ORDERING
+void Transform::GetSortedChildList (Transform::TransformComList& sortedChildren) const
+{
+ sortedChildren.assign(m_Children.begin(), m_Children.end());
+ std::sort(sortedChildren.begin(), sortedChildren.end(), Transform::CompareDepths);
+}
+#endif
+
+void Transform::SetLocalTRS (const Vector3f& pos, const Quaternionf& rot, const Vector3f& scale)
+{
+ ABORT_INVALID_VECTOR3 (pos, localPosition, transform);
+ ABORT_INVALID_QUATERNION (rot, localRotation, transform);
+ m_LocalRotation = NormalizeSafe(rot);
+ m_LocalPosition = pos;
+ m_LocalScale = scale;
+ #if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+ #endif
+ RecalculateTransformType();
+ SendTransformChanged(kPositionChanged | kRotationChanged | kScaleChanged);
+}
+
+int GetTransformDepth(Transform& transform)
+{
+ Transform* parent = transform.GetParent();
+ int depth = 0;
+ while (parent)
+ {
+ depth++;
+ parent = parent->GetParent();
+ }
+ return depth;
+}
+
+Transform* FindTransformWithName(Transform* root, const char* name)
+{
+ if (strcmp(root->GetName(), name) == 0)
+ return root;
+ else
+ {
+ for (int i = 0; i < root->GetChildrenCount(); i++)
+ {
+ Transform* ret = FindTransformWithName(&(root->GetChild(i)), name);
+ if (ret)
+ return ret;
+ }
+ return NULL;
+ }
+}
diff --git a/Runtime/Graphics/Transform.h b/Runtime/Graphics/Transform.h
new file mode 100644
index 0000000..2e1cf66
--- /dev/null
+++ b/Runtime/Graphics/Transform.h
@@ -0,0 +1,327 @@
+#ifndef TRANSFORM_H
+#define TRANSFORM_H
+
+#include "Runtime/Math/Quaternion.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Utilities/dynamic_array.h"
+#include "Runtime/Utilities/EnumFlags.h"
+#include "Runtime/Modules/ExportModules.h"
+using std::vector; //TODO:refactor
+
+/** Transformcomponent stores rotation and position of objects
+ * A Transformcomponent can also have child Transformcomponent's,
+ * which then rotate and translate relative to their father.
+
+ * Local space is the space which is relative to its father while world space is an absolute space
+ * Local and Worldspace is the same if the transformcomponent does not have a father.
+ * The localspace functions are faster than the worldspace functions
+ */
+
+class EXPORT_COREMODULE Transform : public Unity::Component
+{
+ public:
+
+ typedef dynamic_array<ImmediatePtr<Transform> > TransformComList;
+ typedef TransformComList::iterator iterator;
+
+ typedef void TransformChangedCallback (Transform* t);
+ typedef void HierarchyChangedCallback (Transform* t);
+ typedef void HierarchyChangedCallbackSetParent (Transform* obj, Transform* oldParent, Transform* newParent);
+
+private:
+ Quaternionf m_LocalRotation;
+ Vector3f m_LocalPosition;
+ Vector3f m_LocalScale;
+
+ mutable Matrix4x4f m_CachedTransformMatrix;
+ mutable UInt8 m_CachedTransformType;
+ mutable UInt8 m_HasCachedTransformMatrix;
+ mutable UInt8 m_HasChanged;
+
+public:
+ // This tracks kNoScaleTransform, kUniformScaleTransform and kNonUniformScaleTransform, kHasNegativeScale
+ UInt8 m_InternalTransformType;
+ UInt8 m_SupportsTransformChanged;
+
+ TransformComList m_Children;
+ ImmediatePtr<Transform> m_Father;
+
+ #if UNITY_EDITOR
+
+ #if ENABLE_EDITOR_HIERARCHY_ORDERING
+ typedef std::pair<std::string, int> VisibleRootSecondaryKey;
+ typedef std::pair<SInt32, VisibleRootSecondaryKey> VisibleRootKey;
+ #else
+ typedef std::pair<std::string, int> VisibleRootKey;
+ #endif
+
+ struct CompareVisibleRoots
+ {
+ bool operator() (const VisibleRootKey& lhs, const VisibleRootKey& rhs) const;
+ };
+ typedef std::map<VisibleRootKey, Transform*, CompareVisibleRoots> VisibleRootMap;
+ VisibleRootMap::iterator m_VisibleRootIterator;
+ bool m_VisibleRootValid;
+ Vector3f m_LocalEulerAnglesHint;
+ #endif
+
+ #if ENABLE_EDITOR_HIERARCHY_ORDERING
+ SInt32 m_Order;
+ #endif
+
+public:
+
+ REGISTER_DERIVED_CLASS (Transform, Component)
+ DECLARE_OBJECT_SERIALIZE (Transform)
+
+ Transform (MemLabelId label, ObjectCreationMode mode);
+ // virtual ~Transform (); declared-by-macro
+
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+ virtual void CheckConsistency ();
+ virtual void SupportedMessagesDidChange (int mask);
+
+ // Tag class as sealed, this makes QueryComponent faster.
+ static bool IsSealedClass () { return true; }
+
+ /// Returns a ptr to the father transformcomponent (NULL if no father)
+ Transform *GetParent () const { return m_Father; }
+ /// Returns a reference to the root transform (top most transform with no parent)
+ Transform& GetRoot ();
+
+ /// Finds given transform
+ iterator Find( const Transform* child );
+
+ /// access to the children
+ int GetChildrenCount ()const { return m_Children.size (); }
+ Transform &GetChild (int i) const { Assert (i < (int)m_Children.size()); return *m_Children[i]; }
+ iterator begin () { return m_Children.begin (); }
+ iterator end () { return m_Children.end (); }
+
+ /// Sets the father to p(if p is invalid the Transformcomponent will have no father)
+ /// Returns false if the father could not be set
+ /// This happens only if you are trying to set the father to one of its direct/indirect children.
+ enum SetParentOption { kWorldPositionStays = 1 << 0, kLocalPositionStays = 1 << 1, kAllowParentingFromPrefab = 1 << 2, kDisableTransformMessage = 1 << 3 };
+ bool SetParent (Transform * parent, SetParentOption options = kWorldPositionStays);
+
+ /// Sets the rotation in local space
+ void SetLocalRotation (const Quaternionf& rotation);
+ /// Sets the Rotation in world space
+ void SetRotation (const Quaternionf& rotation);
+ /// Sets the local euler angles
+ void SetLocalEulerAngles (const Vector3f& eulerAngles);
+
+ /// Sets the position in local space
+ /// (If the object has no father, localspace is basically the same as world space)
+ void SetLocalPosition (const Vector3f& inTranslation);
+ /// Sets the position in world space
+ void SetPosition (const Vector3f& position);
+
+ /// Sets the position - a local space offset will be scaled, rotated and subtracted from the position.
+ /// Used to set the position For CharacterController and NavMeshAgent that have baseOffset / center.
+ /// For retreiving the position with a local offset use: TransformPoint (localOffset)
+ void SetPositionWithLocalOffset (const Vector3f& p, const Vector3f& localOffset);
+
+ /// Transforms local space position to world space - while applying an offset which is scaled and rotated accordingly.
+ Vector3f TransformPointWithLocalOffset (const Vector3f& p, const Vector3f& localOffset) const;
+
+ /// Same as above but normalizes the quaternion
+ void SetLocalRotationSafe (const Quaternionf& rotation);
+ void SetRotationSafe (const Quaternionf& rotation);
+
+ /// Sets the scale in local space
+ void SetLocalScale (const Vector3f& scale);
+ /// Sets the scale from a rotation * scale
+ /// The transform can not hold the full scale if it contains skew
+ void SetWorldRotationAndScale (const Matrix3x3f& worldRotationAndScale);
+
+ /// Sets the world position and rotation
+ void SetPositionAndRotation (const Vector3f& position, const Quaternionf& rotation);
+ void SetLocalPositionAndRotation (const Vector3f& position, const Quaternionf& rotation);
+
+ /// Return matrix that converts a point from World To Local space
+ Matrix4x4f GetWorldToLocalMatrix () const;
+ /// Return matrix that converts a point from Local To World space
+ Matrix4x4f GetLocalToWorldMatrix () const;
+
+ Matrix4x4f GetWorldToLocalMatrixNoScale () const;
+ const Matrix4x4f& GetWorldToLocalMatrixNoScale (Matrix4x4f& m) const;
+ Matrix4x4f GetLocalToWorldMatrixNoScale () const;
+ const Matrix4x4f& GetLocalToWorldMatrixNoScale (Matrix4x4f& m) const;
+
+ TransformType CalculateTransformMatrix (Matrix4x4f& matrix) const;
+
+ TransformType CalculateTransformMatrixScaleDelta (Matrix4x4f& matrix) const;
+
+ TransformType CalculateTransformMatrixDisableNonUniformScale (Matrix4x4f& matrix, Matrix4x4f& scaleOnly, float& scale) const;
+ TransformType CalculateTransformMatrixDisableScale (Matrix4x4f& matrix) const;
+
+ /// Whether the transform has changed since the last time this flag was set to 'false'.
+ bool GetChangedFlag () { return m_HasChanged; }
+ /// Sets the flag indicating whether the transform has changed. Most commonly used to simply set it to 'false'.
+ void SetChangedFlag (bool val) { m_HasChanged = val; }
+
+ /// Gets the rotation from local to world space
+ Quaternionf GetRotation () const;
+ /// Gets the local rotation
+ Quaternionf GetLocalRotation () const { return m_LocalRotation; }
+ /// Gets the local euler angles (in the editor it is first ensures that they are in sync with the local rotation quaternion)
+ Vector3f GetLocalEulerAngles ();
+
+ /// Gets the local position relative to the father
+ Vector3f GetLocalPosition () const {return m_LocalPosition;}
+ /// Gets the position in world space
+ Vector3f GetPosition () const;
+
+ /// Returns the local scale
+ Vector3f GetLocalScale () const { return m_LocalScale; }
+ /// Returns the world rotation and scale.
+ /// (It is impossible to return a Vector3 because the scale might be skewed)
+ Matrix3x3f GetWorldRotationAndScale () const;
+
+ Matrix3x3f GetWorldScale () const;
+
+ Vector3f GetWorldScaleLossy () const;
+
+ /// Rotates the transform around axis by rad
+ void RotateAroundLocal (const Vector3f& localAxis, float rad);
+ /// Same, but normalizes the axis for you
+ void RotateAroundLocalSafe (const Vector3f& localAxis, float rad);
+ /// Rotates the transform around axis by rad
+ void RotateAround (const Vector3f& worldAxis, float rad);
+ /// Same, but normalizes the axis for you
+ void RotateAroundSafe (const Vector3f& worldAxis, float rad);
+
+
+ /// transforms a point from localspace to worldspace
+ Vector3f TransformPoint (const Vector3f& inPoint) const;
+ /// Transforms a direction from localspace to worldspace
+ /// (Ignores scale)
+ Vector3f TransformDirection (const Vector3f& inDirection) const;
+
+ /// Transforms a point from worldspace to localspace
+ Vector3f InverseTransformPoint (const Vector3f& inDirection) const;
+ /// Transforms a direction from worldspace to localspace
+ /// (Ignores scale)
+ Vector3f InverseTransformDirection (const Vector3f& inDirection) const;
+
+ #if UNITY_EDITOR
+ /// Register a function which is called whenever a transformcomponent is changed
+ static void RegisterHierarchyChangedCallback (HierarchyChangedCallback* callback);
+ static void RegisterHierarchyChangedSetParentCallback (HierarchyChangedCallbackSetParent* callback);
+
+ VisibleRootMap::iterator* GetVisibleRootIterator () { return m_VisibleRootValid ? &m_VisibleRootIterator : NULL; }
+ void SetVisibleRootIterator (const VisibleRootMap::iterator& it) { m_VisibleRootIterator = it; m_VisibleRootValid = true; }
+ void ClearVisibleRootIterator () { m_VisibleRootValid = false; }
+ #endif // End UNITY_EDITOR
+
+ #if ENABLE_EDITOR_HIERARCHY_ORDERING
+ static int CompareDepths(Transform* lhs, Transform* rhs);
+
+ SInt32 GetOrder () const { return m_Order; }
+ void SetOrder (SInt32 order);
+ void OrderChildrenRecursively();
+ void GetSortedChildList (TransformComList& sortedChildren) const;
+ #endif
+
+ TransformComList& GetChildrenInternal () { return m_Children; }
+ const TransformComList& GetChildrenInternal () const { return m_Children; }
+ ImmediatePtr<Transform>& GetParentPtrInternal () { return m_Father; }
+
+ /// Reset position&rotation
+ void Reset ();
+
+ /// Sets the world position and rotation without sending out a TransformChanged message to the gameobject and without setting dirty
+ /// (Not sending TransformChanged will result in the Renderer
+ /// not updating the AABB to reflect the transform change)
+ void SetPositionAndRotationWithoutNotification (const Vector3f& position, const Quaternionf& q);
+ void SetPositionWithoutNotification (const Vector3f& position);
+ void SetRotationWithoutNotification (const Quaternionf& q);
+ void SetPositionAndRotationSafeWithoutNotification (const Vector3f& p, const Quaternionf& q);
+
+ void SetPositionAndRotationSafe (const Vector3f& p, const Quaternionf& q);
+
+ void GetPositionAndRotation (Vector3f& pos, Quaternionf& q)const;
+ TransformType GetPositionAndRotationWithTransformType (Vector3f& worldPos, Quaternionf& worldRot) const;
+
+ /// You seldomly want to call this function yourself.
+ /// Sends the transform changed message to itself and all children.
+ /// A bitmask specifies which components have changed!
+ /// kParentingChanged is also called when SetOrder(SInt32) is called. (Editor Only)
+ enum { kPositionChanged = 1 << 0, kRotationChanged = 1 << 1, kScaleChanged = 1 << 3, kAnimatePhysics = 1 << 4, kParentingChanged = 1 << 5};
+ void SendTransformChanged (int mask);
+
+ /// private but used by datatemplates
+ void RemoveFromParent ();
+ void ClearChildrenParentPointer ();
+ void ClearChild (Transform* child);
+
+ /// Broadcasts a message to this and all child transforms
+ void BroadcastMessageAny(const MessageIdentifier& message, MessageData& data);
+
+ void SetLocalTRS (const Vector3f& pos, const Quaternionf& rot, const Vector3f& scale);
+
+ inline void SetLocalRotationWithoutNotification (const Quaternionf& rotation)
+ {
+ m_LocalRotation = rotation;
+ #if UNITY_EDITOR
+ SyncLocalEulerAnglesHint ();
+ #endif
+ }
+
+ inline void SetLocalPositionWithoutNotification (const Vector3f& inTranslation)
+ {
+ m_LocalPosition = inTranslation;
+ }
+
+ inline void SetLocalScaleWithoutNotification (const Vector3f& scale)
+ {
+ m_LocalScale = scale;
+ RecalculateTransformType ();
+ }
+
+ // For use only by the animation system
+ void RecalculateTransformType ();
+
+ UInt32 CalculateSupportedMessages ();
+
+ void MakeEditorValuesLookNice();
+
+private:
+
+ friend class AnimationBinder;
+ friend void SampleAnimation (GameObject& go, class AnimationClip& clip, float inTime, int wrapMode, float deltaTime);
+
+ TransformType CalculateLocalTransformMatrix(Matrix4x4f& matrix) const;
+ TransformType CalculateTransformMatrixIterative (Matrix4x4f& matrix) const;
+
+ void SetCacheDirty();
+
+ template<bool Safe, bool Notify>
+ void SetPositionAndRotationInternal( const Vector3f& position, const Quaternionf& rotation );
+
+ #if UNITY_EDITOR
+ /// Makes the local euler angles be in sync with the quaternion rotation
+ void SyncLocalEulerAnglesHint ();
+ #endif
+};
+
+ENUM_FLAGS(Transform::SetParentOption);
+
+Transform* FindRelativeTransformWithPath (Transform& transform, const char* path);
+
+Transform* FindActiveTransformWithPath (const char* path);
+
+Transform* FindTransformWithName (Transform* root, const char* name);
+
+std::string CalculateTransformPath (const Transform& transform, const Transform* to);
+void AppendTransformPath (string& path, const char* appendName);
+void AppendTransformPath (UnityStr& path, const char* appendName);
+
+/// Is transform a child of parent? Or is the transform the same.
+bool IsChildOrSameTransform(Transform& transform, Transform& parent);
+
+EXPORT_COREMODULE int GetTransformDepth(Transform& transform);
+
+
+#endif
diff --git a/Runtime/Graphics/TransformTests.cpp b/Runtime/Graphics/TransformTests.cpp
new file mode 100644
index 0000000..7d6e870
--- /dev/null
+++ b/Runtime/Graphics/TransformTests.cpp
@@ -0,0 +1,102 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "Transform.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Testing/TestFixtures.h"
+#include "Runtime/Testing/Testing.h"
+#include "Editor/Src/Prefabs/Prefab.h"
+
+
+class Prefab;
+
+SUITE (TransformTests)
+{
+ class TransformFixture : TestFixtureBase
+ {
+ protected:
+ Transform* MakeTransform ()
+ {
+ Transform* tf = NewTestObject<Transform> ();
+ GameObject* go = NewTestObject<GameObject> ();
+ go->AddComponentInternal (tf);
+ return tf;
+ }
+
+ Prefab* MakePrefabParent ()
+ {
+ Prefab* prefab = NewTestObject<Prefab> ();
+ prefab->m_IsPrefabParent = true;
+ return prefab;
+ }
+ };
+
+ TEST_FIXTURE (TransformFixture, Has_Null_Parrent_By_Default)
+ {
+ Transform* transform = MakeTransform ();
+ CHECK_EQUAL ((Transform*)0, transform->GetParent ());
+ }
+
+ TEST_FIXTURE (TransformFixture, SetParent_Returns_True_If_New_Parent_Is_Null)
+ {
+ Transform* transform = MakeTransform ();
+ CHECK_EQUAL (true, transform->SetParent (0));
+ }
+
+ TEST_FIXTURE (TransformFixture, SetParent_Returns_False_When_GameObject_Is_Being_Destroyed)
+ {
+ Transform* transform = MakeTransform ();
+ Transform* parent = MakeTransform ();
+ transform->GetGameObject().WillDestroyGameObject ();
+ CHECK_EQUAL (false, transform->SetParent (parent));
+ }
+
+ TEST_FIXTURE (TransformFixture, SetParent_Returns_False_When_GameObject_Of_New_Parent_Is_Being_Destroyed)
+ {
+ Transform* transform = MakeTransform ();
+ Transform* parent = MakeTransform ();
+ parent->GetGameObject().WillDestroyGameObject ();
+ CHECK_EQUAL (false, transform->SetParent (parent));
+ }
+
+ TEST_FIXTURE (TransformFixture, SetParent_Returns_False_If_New_Parent_Is_A_Child)
+ {
+ Transform* parent = MakeTransform ();
+ Transform* child = MakeTransform ();
+ child->SetParent (parent);
+ CHECK_EQUAL (false, parent->SetParent (child));
+ }
+
+ #if UNITY_EDITOR
+ TEST_FIXTURE (TransformFixture, SetParent_With_Disable_Parenting_From_Prefab_Returns_False_If_Tranform_Resides_In_Prefab_EditorOnly)
+ {
+ Transform* transform = MakeTransform ();
+ transform->m_Prefab = PPtr<Prefab> (MakePrefabParent ());
+
+ EXPECT (Error, "Setting the parent of a transform which resides in a prefab is disabled");
+ CHECK_EQUAL (false, transform->SetParent (MakeTransform(), Transform::kWorldPositionStays));
+ }
+ #endif
+
+ TEST (ChildPosition_Test)
+ {
+ GameObject& go = CreateGameObject ("parent", "Transform", NULL);
+ go.GetComponent (Transform).SetLocalScale (Vector3f (1.0F, 1.0F, 0.1F));
+
+ GameObject& child = CreateGameObject ("child", "Transform", NULL);
+
+ child.GetComponent (Transform).SetParent (&go.GetComponent (Transform), Transform::kLocalPositionStays);
+
+ child.GetComponent (Transform).SetLocalPosition (Vector3f (0.0F, 0.0F, 10.0F));
+ child.GetComponent (Transform).SetLocalScale (Vector3f (1.0F, 1.0F, 1.0F));
+ child.GetComponent (Transform).SetLocalRotation (Quaternionf::identity ());
+
+ CHECK ( CompareApproximately (child.GetComponent (Transform).GetPosition (), Vector3f (0.0F,0.0F,1.0F)) );
+
+ DestroyObjectHighLevel (&go);
+ }
+}
+
+#endif // ENABLE_UNIT_TESTS
diff --git a/Runtime/Graphics/TriStripper.cpp b/Runtime/Graphics/TriStripper.cpp
new file mode 100644
index 0000000..89d6c1b
--- /dev/null
+++ b/Runtime/Graphics/TriStripper.cpp
@@ -0,0 +1,112 @@
+#include "UnityPrefix.h"
+#include "External/Tristripper/Striper.h"
+#include "TriStripper.h"
+#include "Runtime/Filters/Mesh/Mesh.h"
+#include "Runtime/Utilities/vector_utility.h"
+#include "Runtime/Utilities/LogAssert.h"
+
+using namespace std;
+
+
+bool Stripify (const UInt32* faces, int count, UNITY_TEMP_VECTOR(UInt32)& strip)
+{
+ Assert (faces != NULL || !count);
+ strip.clear ();
+
+ // Fail on empty input.
+ if (!count)
+ return false;
+
+ STRIPERCREATE stripinput;
+ stripinput.DFaces = const_cast<UInt32*> (faces);
+ stripinput.NbFaces = count / 3;
+
+ STRIPERRESULT stripresult;
+ Striper striper;
+ if (striper.Init (stripinput) && striper.Compute (stripresult) && stripresult.NbStrips == 1)
+ {
+ int stripSize = stripresult.StripLengths[0];
+ UInt32* stripData = reinterpret_cast<UInt32*> (stripresult.StripRuns);
+ reserve_trimmed (strip, stripSize);
+ strip.assign (stripData, stripData + stripSize);
+ return true;
+ }
+ return false;
+}
+
+template<typename T>
+int CountTrianglesInStrip (const T* strip, int length)
+{
+ int count = 0;
+ // de-stripify :
+ int n = length;
+ for(int i=0;i<(n-2);i++)
+ {
+ T a = strip[i];
+ T b = strip[i+1];
+ T c = strip[i+2];
+
+ // skip degenerates
+ if ( a == b || a == c || b == c )
+ continue;
+
+ count++;
+ }
+ return count;
+}
+
+template int CountTrianglesInStrip<UInt16> (const UInt16* strip, int length);
+template int CountTrianglesInStrip<UInt32> (const UInt32* strip, int length);
+
+// WARNING: You have to make sure you have space for all indices in trilist (you can use CountTrianglesInStrip for that)
+template<typename Tin, typename Tout>
+void Destripify(const Tin* strip, int length, Tout* trilist, int capacity)
+{
+ // de-stripify :
+ int n = length;
+ int triIndex = 0;
+ for(int i=0;i<(n-2);i++)
+ {
+ Tin a = strip[i];
+ Tin b = strip[i+1];
+ Tin c = strip[i+2];
+
+ // skip degenerates
+ if ( a == b || a == c || b == c )
+ continue;
+
+ // do the winding flip-flop of strips :
+ if ( (i&1) == 1 )
+ std::swap (a,b);
+
+ trilist[triIndex++] = a;
+ trilist[triIndex++] = b;
+ trilist[triIndex++] = c;
+ }
+}
+
+
+template void Destripify<UInt32, UInt32> (const UInt32* strip, int length, UInt32* trilist, int capacity);
+template void Destripify<UInt16, UInt32> (const UInt16* strip, int length, UInt32* trilist, int capacity);
+template void Destripify<UInt16, UInt16> (const UInt16* strip, int length, UInt16* trilist, int capacity);
+
+void Destripify (const UInt16* strip, int length, UNITY_TEMP_VECTOR(UInt16)& trilist)
+{
+ int oldSize = trilist.size();
+ trilist.resize (oldSize + CountTrianglesInStrip(strip, length) * 3);
+ Destripify(strip, length, &trilist[oldSize], trilist.size());
+}
+
+void Destripify (const UInt16* strip, int length, UNITY_TEMP_VECTOR(UInt32)& trilist)
+{
+ int oldSize = trilist.size();
+ trilist.resize (oldSize + CountTrianglesInStrip(strip, length) * 3);
+ Destripify(strip, length, &trilist[oldSize], trilist.size());
+}
+
+void Destripify (const UInt32* strip, int length, UNITY_TEMP_VECTOR(UInt32)& trilist)
+{
+ int oldSize = trilist.size();
+ trilist.resize (oldSize + CountTrianglesInStrip(strip, length) * 3);
+ Destripify(strip, length, &trilist[oldSize], trilist.size());
+}
diff --git a/Runtime/Graphics/TriStripper.h b/Runtime/Graphics/TriStripper.h
new file mode 100644
index 0000000..63d02fe
--- /dev/null
+++ b/Runtime/Graphics/TriStripper.h
@@ -0,0 +1,22 @@
+#ifndef TRISTRIPPER_H
+#define TRISTRIPPER_H
+
+#include <vector>
+#include "Runtime/Allocator/MemoryMacros.h"
+#include "Runtime/Modules/ExportModules.h"
+
+bool EXPORT_COREMODULE Stripify (const UInt32* faces, int count, UNITY_TEMP_VECTOR(UInt32)& strip);
+
+/// Destrips a triangle strip into a face list.
+/// Adds the triangles from the strip into the trilist
+void EXPORT_COREMODULE Destripify (const UInt32* strip, int length, UNITY_TEMP_VECTOR(UInt32)& trilist);
+void EXPORT_COREMODULE Destripify (const UInt16* strip, int length, UNITY_TEMP_VECTOR(UInt32)& trilist);
+void EXPORT_COREMODULE Destripify (const UInt16* strip, int length, UNITY_TEMP_VECTOR(UInt16)& trilist);
+
+template<typename Tin, typename Tout>
+void EXPORT_COREMODULE Destripify(const Tin* strip, int length, Tout* trilist, int capacity);
+
+template<typename T>
+int EXPORT_COREMODULE CountTrianglesInStrip (const T* strip, int length);
+
+#endif