BVB Source Codes

CRYENGINE Show CharacterInstance_Render.cpp Source code

Return Download CRYENGINE: download CharacterInstance_Render.cpp Source code - Download CRYENGINE Source code - Type:.cpp
  1. // Copyright 2001-2016 Crytek GmbH / Crytek Group. All rights reserved.
  2.  
  3. #include "stdafx.h"
  4.  
  5. #include <Cry3DEngine/I3DEngine.h>
  6. #include <CryRenderer/IShader.h>
  7. #include "ModelMesh.h"
  8. #include "CharacterInstance.h"
  9. #include <CryRenderer/IRenderAuxGeom.h>
  10. #include "CharacterManager.h"
  11.  
  12. CLodValue CCharInstance::ComputeLod(int wantedLod, const SRenderingPassInfo& passInfo)
  13. {
  14.         // If the character instance has software vertex animation and ca_vaSkipVertexAnimationLod is set
  15.         // we skip the first LOD to disable software skinning
  16.         if (m_bHasVertexAnimation && wantedLod == 0 && Console::GetInst().ca_vaSkipVertexAnimationLOD == 1)
  17.         {
  18.                 return CLodValue(1);
  19.         }
  20.  
  21.         return CLodValue(wantedLod);
  22. }
  23.  
  24. //! Render object ( register render elements into renderer )
  25. void CCharInstance::Render(const struct SRendParams& RendParams, const QuatTS& Offset, const SRenderingPassInfo& passInfo)
  26. {
  27.         if (GetFlags() & CS_FLAG_COMPOUND_BASE)
  28.         {
  29.                 if (Console::GetInst().ca_DrawCC == 0)
  30.                         return;
  31.         }
  32.  
  33.         if (m_pDefaultSkeleton->m_ObjectType == CGA)
  34.         {
  35.                 if (Console::GetInst().ca_DrawCGA == 0)
  36.                         return;
  37.         }
  38.  
  39.         if (m_pDefaultSkeleton->m_ObjectType == CHR)
  40.         {
  41.                 if (Console::GetInst().ca_DrawCHR == 0)
  42.                         return;
  43.         }
  44.  
  45.         if (!(m_rpFlags & CS_FLAG_DRAW_MODEL))
  46.                 return;
  47.  
  48.         assert(RendParams.pMatrix);
  49.  
  50.         Vec3 position;
  51.         position = RendParams.pMatrix->GetTranslation();
  52.         if (m_SkeletonAnim.m_AnimationDrivenMotion == 0)
  53.                 position += Offset.t;
  54.  
  55.         Matrix33 orientation;
  56.         orientation = Matrix33(*RendParams.pMatrix);
  57.         //if (m_SkeletonPose.m_physics.m_bPhysicsRelinquished)
  58.         //      orientation = Matrix33(m_location.q);
  59.  
  60.         Matrix34 RenderMat34(orientation, position);
  61.  
  62.         //f32 axisX = RenderMat34.GetColumn0().GetLength();
  63.         //f32 axisY = RenderMat34.GetColumn1().GetLength();
  64.         //f32 axisZ = RenderMat34.GetColumn2().GetLength();
  65.         //f32 fScaling = 0.333333333f*(axisX+axisY+axisZ);
  66.         //RenderMat34.OrthonormalizeFast();
  67.  
  68.         uint32 nFrameID = g_pCharacterManager->m_nUpdateCounter;
  69.         if (m_LastRenderedFrameID != nFrameID)
  70.                 m_LastRenderedFrameID = nFrameID;
  71.  
  72.         g_pAuxGeom->SetRenderFlags(e_Def3DPublicRenderflags);
  73.  
  74.         if (!passInfo.IsShadowPass())
  75.         {
  76.                 m_nAnimationLOD = RendParams.lodValue.LodA();
  77.                 if (m_nAnimationLOD == -1)
  78.                         m_nAnimationLOD = RendParams.lodValue.LodB();
  79.         }
  80.  
  81.         //      float fColor[4] = {1,0,1,1};
  82.         //      g_pAuxGeom->Draw2dLabel( 1,g_YLine, 1.2f, fColor, false,"fDistance: %f m_nAnimationLOD: %d   m_nRenderLOD: %d   numLODs: %d  m_pDefaultSkeleton->m_nBaseLOD: %d  Model: %s",RendParams.fDistance,m_nAnimationLOD,m_nRenderLOD,numLODs,m_pDefaultSkeleton->m_nBaseLOD,m_pDefaultSkeleton->GetFilePath().c_str() );
  83.         //      g_YLine+=16.0f;
  84.  
  85.         //------------------------------------------------------------------------
  86.         //------------   Debug-Draw of the final Render Location     -------------
  87.         //------------------------------------------------------------------------
  88.         if (Console::GetInst().ca_DrawPositionPost)
  89.         {
  90.                 Vec3 wpos = RenderMat34.GetTranslation();
  91.                 g_pAuxGeom->SetRenderFlags(e_Def3DPublicRenderflags);
  92.                 static Ang3 angle(0, 0, 0);
  93.                 angle += Ang3(0.01f, 0.02f, 0.03f);
  94.                 AABB aabb = AABB(Vec3(-0.055f, -0.055f, -0.055f), Vec3(+0.055f, +0.055f, +0.055f));
  95.                 OBB obb = OBB::CreateOBBfromAABB(Matrix33::CreateRotationXYZ(angle), aabb);
  96.                 g_pAuxGeom->DrawOBB(obb, wpos, 0, RGBA8(0x00, 0xff, 0x00, 0xff), eBBD_Extremes_Color_Encoded);
  97.  
  98.                 Vec3 axisX = RenderMat34.GetColumn0();
  99.                 Vec3 axisY = RenderMat34.GetColumn1();
  100.                 Vec3 axisZ = RenderMat34.GetColumn2();
  101.                 g_pAuxGeom->DrawLine(wpos, RGBA8(0x7f, 0x00, 0x00, 0x00), wpos + axisX, RGBA8(0xff, 0x00, 0x00, 0x00));
  102.                 g_pAuxGeom->DrawLine(wpos, RGBA8(0x00, 0x7f, 0x00, 0x00), wpos + axisY, RGBA8(0x00, 0xff, 0x00, 0x00));
  103.                 g_pAuxGeom->DrawLine(wpos, RGBA8(0x00, 0x00, 0x7f, 0x00), wpos + axisZ, RGBA8(0x00, 0x00, 0xff, 0x00));
  104.         }
  105.  
  106.         //------------------------------------------------------------------------
  107.  
  108.         SRendParams attachmentRendParams(RendParams);
  109.         {
  110.                 uint64 uAdditionalObjFlags = 0;
  111.                 if (m_rpFlags & CS_FLAG_DRAW_NEAR)
  112.                         uAdditionalObjFlags |= FOB_NEAREST;
  113.  
  114.                 attachmentRendParams.pMaterial = NULL;    // this is required to avoid the attachments using the parent character material (this is the material that overrides the default material in the attachment)
  115.                 attachmentRendParams.dwFObjFlags |= uAdditionalObjFlags;
  116.         }
  117.  
  118.         const f32 fFOV = g_pIRenderer->GetCamera().GetFov();
  119.         const f32 fZoomFactor = 0.0f + 1.0f * (RAD2DEG(fFOV) / 60.f);
  120.         const f32 attachmentCullingRation = (gEnv->bMultiplayer) ? Console::GetInst().ca_AttachmentCullingRationMP : Console::GetInst().ca_AttachmentCullingRation;
  121.         const f32 fZoomDistanceSq = sqr(RendParams.fDistance * fZoomFactor / attachmentCullingRation);
  122.  
  123.         m_AttachmentManager.DrawMergedAttachments(attachmentRendParams, RenderMat34, passInfo, fZoomFactor, fZoomDistanceSq);
  124.        
  125.         if (m_pDefaultSkeleton->m_ObjectType == CGA)
  126.         {
  127.                 Matrix34 mRendMat34 = RenderMat34 * Matrix34(Offset);
  128.                 RenderCGA(RendParams, mRendMat34, passInfo);
  129.         }
  130.         else
  131.         {
  132.                 pe_params_flags pf;
  133.                 IPhysicalEntity* pCharPhys = m_SkeletonPose.GetCharacterPhysics();
  134.                 if (pCharPhys && pCharPhys->GetType() == PE_ARTICULATED && pCharPhys->GetParams(&pf) && pf.flags & aef_recorded_physics)
  135.                         RenderMat34 = RenderMat34 * Matrix34(Offset);
  136.  
  137.                 RenderCHR(RendParams, RenderMat34, passInfo);
  138.         }
  139.  
  140.         // draw weapon and binded objects
  141.         m_AttachmentManager.DrawAttachments(attachmentRendParams, RenderMat34, passInfo, fZoomFactor, fZoomDistanceSq);
  142.  
  143. }
  144.  
  145. void CCharInstance::RenderCGA(const struct SRendParams& RendParams, const Matrix34& RenderMat34, const SRenderingPassInfo& passInfo)
  146. {
  147.         int nList = (int)CharacterManager::GetRendererMainThreadId();
  148.  
  149.         if (GetSkinningTransformationCount())
  150.                 CryFatalError("CryAnimation: CGA should not have Dual-Quaternions for Skinning");
  151.  
  152.         SRendParams nodeRP = RendParams;
  153.  
  154.         Matrix34 orthoTm34 = RenderMat34;
  155.         IMaterial* pMaterial = nodeRP.pMaterial;
  156.         uint32 numJoints = m_SkeletonPose.GetPoseData().GetJointCount();
  157.         assert(numJoints > 0);
  158.         if (Console::GetInst().ca_DrawBaseMesh)
  159.         {
  160.                 for (uint32 i = 0; i < numJoints; i++)
  161.                 {
  162.                         if (m_SkeletonPose.m_arrCGAJoints.size() && m_SkeletonPose.m_arrCGAJoints[i].m_CGAObjectInstance)
  163.                         {
  164.                                 const Skeleton::CPoseData& poseData = m_SkeletonPose.GetPoseData();
  165.  
  166.                                 Matrix34 tm34 = orthoTm34 * Matrix34(poseData.GetJointAbsolute(i));
  167.                                 tm34 *= poseData.GetJointAbsoluteS(i);
  168.  
  169.                                 nodeRP.pMatrix = &tm34;
  170.                                 nodeRP.dwFObjFlags |= FOB_TRANS_MASK;
  171.                                 nodeRP.pInstance = &m_SkeletonPose.m_arrCGAJoints[i];
  172.  
  173.                                 // apply additional depth sort offset, if set
  174.                                 const Vec3 depthSortOffset = (orthoTm34 * Matrix34(poseData.GetJointAbsolute(i))).TransformVector(m_SkeletonPose.m_arrCGAJoints[i].m_CGAObjectInstance->GetDepthSortOffset());
  175.                                 // TODO: ^ Dot me against the camera's forward vector. Is orthoTm34 already operating in the camera-space?
  176.  
  177.                                 // apply custom joint material, if set
  178.                                 nodeRP.pMaterial = m_SkeletonPose.m_arrCGAJoints[i].m_pMaterial ? m_SkeletonPose.m_arrCGAJoints[i].m_pMaterial.get() : pMaterial;
  179.  
  180.                                 if (tm34.IsValid())
  181.                                 {
  182.                                         m_SkeletonPose.m_arrCGAJoints[i].m_CGAObjectInstance->Render(nodeRP, passInfo);
  183.                                 }
  184.                                 else
  185.                                 {
  186.                                         gEnv->pLog->LogError("CCharInstance::RenderCGA: object has invalid matrix: %s", m_pDefaultSkeleton->GetModelFilePath());
  187.                                 }
  188.                         }
  189.                 }
  190.         }
  191.  
  192.         if (Console::GetInst().ca_DrawSkeleton || Console::GetInst().ca_DrawBBox)
  193.         {
  194.                 Matrix34 wsRenderMat34(RenderMat34);
  195.  
  196.                 // Convert "Camera Space" to "World Space"
  197.                 if (RendParams.dwFObjFlags & FOB_NEAREST)
  198.                 {
  199.                         wsRenderMat34.AddTranslation(gEnv->pRenderer->GetCamera().GetPosition());
  200.                 }
  201.  
  202.                 if (Console::GetInst().ca_DrawSkeleton)
  203.                         m_SkeletonPose.DrawSkeleton(wsRenderMat34);
  204.  
  205.                 if (Console::GetInst().ca_DrawBBox)
  206.                         m_SkeletonPose.DrawBBox(wsRenderMat34);
  207.         }
  208. }
  209.  
  210. void CCharInstance::RenderCHR(const SRendParams& RendParams, const Matrix34& rRenderMat34, const SRenderingPassInfo& passInfo)
  211. {
  212.         CRenderObject* pObj = g_pIRenderer->EF_GetObject_Temp(passInfo.ThreadID());
  213.         if (!pObj)
  214.                 return;
  215.  
  216.         //#TODO Extracted from RenderProxy needed for WrinkleMap, should be implemented during character instance rendering
  217.         /*
  218.         {
  219.                 // Make a local copy of render params as some of the parameters will be modified.
  220.                 SRendParams rParams(inRenderParams);
  221.  
  222.                 if (m_Callbacks.size() > 0)
  223.                 {
  224.                         // if we have callbacks be sure the shader params are properly initialized
  225.                         IShaderPublicParams* pParams = GetShaderPublicParams(true);
  226.  
  227.                         // get the current active material
  228.                         IMaterial* pMaterial = m_pEntity->GetMaterial();
  229.                         if (pMaterial == NULL)
  230.                                 pMaterial = GetSlotMaterial(0);
  231.  
  232.                         TCallbackVector::iterator itEnd = m_Callbacks.end();
  233.                         for (TCallbackVector::iterator it = m_Callbacks.begin(); it != itEnd; ++it)
  234.                         {
  235.                                 (*it)->SetupShaderParams(pParams, pMaterial);
  236.                         }
  237.                 }
  238.  
  239.                 if (m_pShaderPublicParams)
  240.                         m_pShaderPublicParams->AssignToRenderParams(rParams);
  241.         }
  242.         */
  243.  
  244.         pObj->m_pRenderNode = RendParams.pRenderNode;
  245.         pObj->m_ObjFlags |= FOB_TRANS_MASK;
  246.         pObj->m_editorSelectionID = RendParams.nEditorSelectionID;
  247.  
  248.         //check if it should be drawn close to the player
  249.         // For nearest geometry (weapons/arms) - make sure its rendered really at beginning (before water passes)
  250.         if ((RendParams.dwFObjFlags & FOB_NEAREST) || (m_rpFlags & CS_FLAG_DRAW_NEAR))
  251.         {
  252.                 pObj->m_ObjFlags |= FOB_NEAREST;
  253.                 ((SRendParams&)RendParams).nAfterWater = 1;
  254.         }
  255.         else
  256.                 pObj->m_ObjFlags &= ~FOB_NEAREST;
  257.  
  258.         pObj->m_fAlpha = RendParams.fAlpha;
  259.         pObj->m_fDistance = RendParams.fDistance;
  260.  
  261.         pObj->m_II.m_AmbColor = RendParams.AmbientColor;
  262.  
  263.         pObj->m_ObjFlags |= RendParams.dwFObjFlags;
  264.         SRenderObjData* pD = pObj->GetObjData();
  265.  
  266.         // copy the shaderparams into the render object data from the render params
  267.         if (RendParams.pShaderParams && RendParams.pShaderParams->size() > 0)
  268.         {
  269.                 pD->SetShaderParams(RendParams.pShaderParams);
  270.         }
  271.  
  272.         pObj->m_II.m_Matrix = rRenderMat34;
  273.  
  274.         pObj->m_nClipVolumeStencilRef = RendParams.nClipVolumeStencilRef;
  275.         pObj->m_nTextureID = RendParams.nTextureID;
  276.  
  277.         bool bCheckMotion = MotionBlurMotionCheck(pObj->m_ObjFlags);
  278.         pD->m_uniqueObjectId = reinterpret_cast<uintptr_t>(RendParams.pInstance);
  279.         if (bCheckMotion)
  280.                 pObj->m_ObjFlags |= FOB_MOTION_BLUR;
  281.  
  282.         pD->m_nVisionParams = RendParams.nVisionParams;
  283.         pD->m_nHUDSilhouetteParams = RendParams.nHUDSilhouettesParams;
  284.  
  285.         pObj->m_nMaterialLayers = RendParams.nMaterialLayersBlend;
  286.  
  287.         pD->m_nCustomData = RendParams.nCustomData;
  288.         pD->m_nCustomFlags = RendParams.nCustomFlags;
  289.         if (RendParams.nCustomFlags & COB_CLOAK_HIGHLIGHT)
  290.         {
  291.                 pD->m_fTempVars[5] = RendParams.fCustomData[0];
  292.         }
  293.         else if (RendParams.nCustomFlags & COB_POST_3D_RENDER)
  294.         {
  295.                 memcpy(&pD->m_fTempVars[5], &RendParams.fCustomData[0], sizeof(float) * 4);
  296.                 pObj->m_fAlpha = 1.0f;     // Use the alpha in the post effect instead of here
  297.                 pD->m_fTempVars[9] = RendParams.fAlpha;
  298.         }
  299.         pObj->m_DissolveRef = RendParams.nDissolveRef;
  300.  
  301.         pD->m_pSkinningData = GetSkinningData();
  302.         pObj->m_ObjFlags |= FOB_SKINNED | FOB_INSHADOW;
  303.  
  304.         Vec3 skinOffset(ZERO);
  305.         if (m_pDefaultSkeleton->GetModelMesh())
  306.                 skinOffset = m_pDefaultSkeleton->GetModelMesh()->m_vRenderMeshOffset;
  307.         pD->m_pSkinningData->vecPrecisionOffset[0] = skinOffset.x;
  308.         pD->m_pSkinningData->vecPrecisionOffset[1] = skinOffset.y;
  309.         pD->m_pSkinningData->vecPrecisionOffset[2] = skinOffset.z;
  310.  
  311.         if (g_pI3DEngine->IsTessellationAllowed(pObj, passInfo))
  312.         {
  313.                 // Allow this RO to be tessellated, however actual tessellation will be applied if enabled in material
  314.                 pObj->m_ObjFlags |= FOB_ALLOW_TESSELLATION;
  315.         }
  316.  
  317.         pObj->m_nSort = fastround_positive(RendParams.fDistance * 2.0f);
  318.  
  319.         if (!m_bHideMaster)
  320.         {
  321.                 if (Console::GetInst().ca_DrawSkeleton)
  322.                         m_SkeletonPose.DrawSkeleton(rRenderMat34);
  323.                 if (Console::GetInst().ca_DrawBBox)
  324.                         m_SkeletonPose.DrawBBox(rRenderMat34);
  325.  
  326.                 if (CModelMesh* pModelMesh = m_pDefaultSkeleton->GetModelMesh())
  327.                 {
  328.                         pModelMesh->m_stream.nFrameId = passInfo.GetMainFrameID();
  329.  
  330.                         if (IRenderMesh* pIRenderMesh = pModelMesh->m_pIRenderMesh)
  331.                         {
  332.                                 // MichaelS - use the instance's material if there is one, and if no override material has
  333.                                 // already been specified.
  334.                                 IMaterial* pMaterial = RendParams.pMaterial;
  335.                                 if (pMaterial == 0)
  336.                                         pMaterial = this->GetIMaterial_Instance();
  337.                                 if (pMaterial == 0)
  338.                                         pMaterial = m_pDefaultSkeleton->GetIMaterial();
  339.                                 pObj->m_pCurrMaterial = pMaterial;
  340. #ifndef _RELEASE
  341.                                 static ICVar* p_e_debug_draw = gEnv->pConsole->GetCVar("e_DebugDraw");
  342.                                 if (p_e_debug_draw && p_e_debug_draw->GetIVal() != 0)
  343.                                         pModelMesh->DrawDebugInfo(this->m_pDefaultSkeleton, 0, rRenderMat34, p_e_debug_draw->GetIVal(), pMaterial, pObj, RendParams, passInfo.IsGeneralPass(), (IRenderNode*)RendParams.pRenderNode, m_SkeletonPose.GetAABB());
  344. #endif
  345.                                 //      float fColor[4] = {1,0,1,1};
  346.                                 //      extern f32 g_YLine;
  347.                                 //      g_pAuxGeom->Draw2dLabel( 1,g_YLine, 1.3f, fColor, false,"m_nRenderLOD: %d  %d",RendParams.nLodLevel, pObj->m_nLod  ); g_YLine+=0x10;
  348.                                 if (Console::GetInst().ca_DrawBaseMesh)
  349.                                 {
  350.                                         pIRenderMesh->Render(pObj, passInfo);
  351.                                 }
  352.  
  353.                                 if (Console::GetInst().ca_DrawDecalsBBoxes)
  354.                                 {
  355.                                         Matrix34 wsRenderMat34(rRenderMat34);
  356.                                         // Convert "Camera Space" to "World Space"
  357.                                         if (pObj->m_ObjFlags & FOB_NEAREST)
  358.                                                 wsRenderMat34.AddTranslation(gEnv->pRenderer->GetCamera().GetPosition());
  359.                                         g_pAuxGeom->SetRenderFlags(e_Def3DPublicRenderflags);
  360.                                 }
  361.                         }
  362.                 }
  363.         }
  364. }
  365.  
  366. //-----------------------------------------------------------------------------------
  367. //-----------------------------------------------------------------------------------
  368. //-----------------------------------------------------------------------------------
  369.  
  370. bool CCharInstance::MotionBlurMotionCheck(uint64 nObjFlags) const
  371. {
  372.         if (m_skinningTransformationsMovement > Console::GetInst().ca_MotionBlurMovementThreshold)
  373.                 return true;
  374.  
  375.         return false;
  376. }
  377.  
  378. SSkinningData* CCharInstance::GetSkinningData()
  379. {
  380.         DEFINE_PROFILER_FUNCTION();
  381.  
  382.         CAttachmentManager* pAttachmentManager = static_cast<CAttachmentManager*>(GetIAttachmentManager());
  383.         uint32 numSkinningBones = GetSkinningTransformationCount() + pAttachmentManager->GetExtraBonesCount();
  384.  
  385.         bool bNeedJobSyncVar = true;
  386.  
  387.         // get data to fill
  388.         int nFrameID = gEnv->pRenderer->EF_GetSkinningPoolID();
  389.         int nList = nFrameID % 3;
  390.         int nPrevList = (nFrameID - 1) % 3;
  391.  
  392.         // before allocating new skinning date, check if we already have for this frame
  393.         if (arrSkinningRendererData[nList].nFrameID == nFrameID && arrSkinningRendererData[nList].pSkinningData)
  394.         {
  395.                 return arrSkinningRendererData[nList].pSkinningData;
  396.         }
  397.  
  398.         SSkinningData* pSkinningData = gEnv->pRenderer->EF_CreateSkinningData(numSkinningBones, bNeedJobSyncVar);
  399.         pSkinningData->pCustomTag = this;
  400.         arrSkinningRendererData[nList].pSkinningData = pSkinningData;
  401.         arrSkinningRendererData[nList].nFrameID = nFrameID;
  402.  
  403.         assert(pSkinningData);
  404.         PREFAST_ASSUME(pSkinningData);
  405.  
  406.         // set data for motion blur
  407.         if (arrSkinningRendererData[nPrevList].nFrameID == (nFrameID - 1) && arrSkinningRendererData[nPrevList].pSkinningData)
  408.         {
  409.                 pSkinningData->nHWSkinningFlags |= eHWS_MotionBlured;
  410.                 pSkinningData->pPreviousSkinningRenderData = arrSkinningRendererData[nPrevList].pSkinningData;
  411.                 if (pSkinningData->pPreviousSkinningRenderData->pAsyncJobs)
  412.                         gEnv->pJobManager->WaitForJob(*pSkinningData->pPreviousSkinningRenderData->pAsyncJobs);
  413.         }
  414.         else
  415.         {
  416.                 // if we don't have motion blur data, use the same as for the current frame
  417.                 pSkinningData->pPreviousSkinningRenderData = pSkinningData;
  418.         }
  419.  
  420.         BeginSkinningTransformationsComputation(pSkinningData);
  421.         pSkinningData->pRenderMesh = GetRenderMesh();
  422.         return pSkinningData;
  423. }
  424.  
downloadCharacterInstance_Render.cpp Source code - Download CRYENGINE Source code
Related Source Codes/Software:
postal - 2017-06-11
reactide - Reactide is the first dedicated IDE for React web ... 2017-06-11
rkt - rkt is a pod-native container engine for Linux. It... 2017-06-11
uWebSockets - Tiny WebSockets https://for... 2017-06-11
realworld - TodoMVC for the RealWorld - Exemplary fullstack Me... 2017-06-11
CRYENGINE - CRYENGINE is a powerful real-time game development... 2017-06-11
goreplay - GoReplay is an open-source tool for capturing and ... 2017-06-10
pyenv - Simple Python version management 2017-06-10
redux-saga - An alternative side effect model for Redux apps ... 2017-06-10
angular-starter - 2017-06-10

 Back to top