BVB Source Codes

CRYENGINE Show AICollision.h Source code

Return Download CRYENGINE: download AICollision.h Source code - Download CRYENGINE Source code - Type:.h
  1. // Copyright 2001-2016 Crytek GmbH / Crytek Group. All rights reserved.
  2.  
  3. #ifndef WORLD_COLLISION_H
  4. #define WORLD_COLLISION_H
  5.  
  6. #if _MSC_VER > 1000
  7.         #pragma once
  8. #endif
  9.  
  10. extern const float WalkabilityFloorUpDist;
  11. extern const float WalkabilityFloorDownDist;
  12. // assume character is this fat
  13. extern const float WalkabilityRadius;
  14. // radius of the swept sphere down to find the floor
  15. extern const float WalkabilityDownRadius;
  16. // assume character is this tall
  17. extern const float WalkabilityTotalHeight;
  18. extern const float WalkabilityCritterTotalHeight;
  19. extern const float WalkabilityTorsoOffset;
  20. extern const float WalkabilityCritterTorsoOffset;
  21.  
  22. // maximum allowed floor height change from one to the next
  23. extern const float WalkabilityMaxDeltaZ;
  24. // height of the torso capsule above foot
  25. extern const Vec3 WalkabilityTorsoBaseOffset;
  26.  
  27. // Simple auto_ptr to allow thread-safe and efficient use of GetEntitiesInBox while not encouraging memory leaks
  28. class PhysicalEntityListAutoPtr
  29. {
  30. public:
  31.         PhysicalEntityListAutoPtr()
  32.         { m_pList = 0; }
  33.  
  34.         ~PhysicalEntityListAutoPtr()
  35.         { *this = 0; }
  36.  
  37.         // Perform cleanup of memory allocated by physics
  38.         void              operator=(IPhysicalEntity** pList);
  39.  
  40.         IPhysicalEntity*  operator[](size_t i) { return m_pList[i]; }
  41.         IPhysicalEntity** GetPtr()             { return m_pList; }
  42.  
  43. private:
  44.         // Leave undefined
  45.         PhysicalEntityListAutoPtr(const PhysicalEntityListAutoPtr&);
  46.         void operator=(const PhysicalEntityListAutoPtr&);
  47.  
  48.         IPhysicalEntity** m_pList;
  49. };
  50.  
  51. // Easy to use timers - useful for quick performance comparisons
  52. struct AccurateStopTimer
  53. {
  54.         enum State
  55.         {
  56.                 Running,
  57.                 Stopped,
  58.         };
  59.  
  60.         AccurateStopTimer()
  61.                 : start_((int64)0ll)
  62.                 , total_((int64)0ll)
  63.                 , state_(Stopped)
  64.         {
  65.         }
  66.  
  67.         ILINE void Reset()
  68.         {
  69.                 total_.SetValue(0);
  70.                 start_.SetValue(0);
  71.         }
  72.  
  73.         ILINE void Start()
  74.         {
  75.                 assert(state_ != Running);
  76.                 state_ = Running;
  77.                 start_ = gEnv->pTimer->GetAsyncTime();
  78.         }
  79.  
  80.         ILINE void Stop()
  81.         {
  82.                 total_ += gEnv->pTimer->GetAsyncTime() - start_;
  83.                 assert(state_ != Stopped);
  84.                 state_ = Stopped;
  85.         }
  86.  
  87.         ILINE State GetState() const
  88.         {
  89.                 return state_;
  90.         }
  91.  
  92.         ILINE CTimeValue GetTime() const
  93.         {
  94.                 if (state_ == Running)
  95.                         return gEnv->pTimer->GetAsyncTime() - start_;
  96.                 else
  97.                         return total_;
  98.         }
  99.  
  100. private:
  101.         CTimeValue start_;
  102.         CTimeValue total_;
  103.         State      state_;
  104. };
  105.  
  106. struct ScopedAutoTimer
  107. {
  108.         explicit ScopedAutoTimer(AccurateStopTimer& timer, bool reset = false)
  109.                 : timer_(timer)
  110.         {
  111.                 if (reset)
  112.                         timer_.Reset();
  113.                 timer_.Start();
  114.         }
  115.  
  116.         ~ScopedAutoTimer()
  117.         {
  118.                 timer_.Stop();
  119.         }
  120.  
  121. private:
  122.         AccurateStopTimer& timer_;
  123. };
  124.  
  125. #if 0
  126. /// Generates 2D convex hull from ptsIn using Graham's scan.
  127. void ConvexHull2DGraham(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn);
  128. #endif
  129.  
  130. /// Generates 2D convex hull from ptsIn using Andrew's algorithm.
  131. void ConvexHull2DAndrew(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn);
  132.  
  133. /// Generates 2D convex hull from ptsIn
  134. inline void ConvexHull2D(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn)
  135. {
  136.         // [Mikko] Note: The convex hull calculation is bound by the sorting.
  137.         // The sort in Andrew's seems to be about 3-4x faster than Graham's--using Andrew's for now.
  138.         ConvexHull2DAndrew(ptsOut, ptsIn);
  139. }
  140.  
  141. /// Return the closest intersection point on surface of the swept sphere
  142. /// and distance along the linesegment. hitPos can be zero - may be faster
  143. bool IntersectSweptSphere(Vec3* hitPos, float& hitDist, const Lineseg& lineseg,
  144.                           float radius, EAICollisionEntities aiCollisionEntities, IPhysicalEntity** pSkipEnts = 0, int nSkipEnts = 0, int geomFlagsAny = geom_colltype0);
  145.  
  146. #ifdef CRYAISYSTEM_DEBUG
  147. /// Count of calls to CheckWalkability - for profiling
  148. extern unsigned g_CheckWalkabilityCalls;
  149. #endif //CRYAISYSTEM_DEBUG
  150.  
  151. // NOTE Mai 24, 2007: <pvl> not really pretty, but this is a way of telling
  152. // the CheckWalkability*() functions if the expensive floor position check needs
  153. // to be performed.  If it doesn't (because m_pos already is a floor position),
  154. // m_isFloor is set to true.
  155. // Default ctor is done so that existing callers (that expect the floor check
  156. // to be done) can remain as they are.
  157. //
  158. // TODO Mai 24, 2007: <pvl> CheckWalkability() interface is starting to show its
  159. // age - most likely it should be changed to a class with state.
  160. struct SWalkPosition
  161. {
  162.         Vec3 m_pos;
  163.         bool m_isFloor;
  164.         SWalkPosition(const Vec3& pos, bool isFloor = false) :
  165.                 m_pos(pos), m_isFloor(isFloor)
  166.         {}
  167. };
  168.  
  169. const size_t GetPhysicalEntitiesInBoxMaxResultCount = 2048;
  170. // multiply the number by two to get it more safe, since it looks like physics can allocate its own list in case more entities are in such a box
  171. typedef StaticDynArray<AABB, GetPhysicalEntitiesInBoxMaxResultCount>             StaticAABBArray;
  172. typedef StaticDynArray<IPhysicalEntity*, GetPhysicalEntitiesInBoxMaxResultCount> StaticPhysEntityArray;
  173.  
  174. /**
  175.  * If a human waypoint link goes over flat ground without any significant
  176.  * bumps or holes, walkability checking of this link can be reduced just to
  177.  * intersecting a box (an approximation of volume created by sweeping
  178.  * simplified human body shape along the link) with non-static world.
  179.  */
  180. bool FindFloor(const Vec3& position, const StaticPhysEntityArray& entities, const StaticAABBArray& aabbs, Vec3& floor);
  181. bool FindFloor(const Vec3& position, Vec3& floor);
  182.  
  183. bool CheckWalkabilitySimple(/*Vec3 from, Vec3 to,*/ SWalkPosition fromPos, SWalkPosition toPos, float radius,
  184.                                                     EAICollisionEntities aiCollisionEntities = AICE_ALL);
  185.  
  186. // Faster and more accurate version of walkability check
  187. // radius is now the agent radius instead of a padding radius
  188. bool CheckWalkability(const Vec3& origin, const Vec3& target, float radius, Vec3* finalFloor = 0, bool* flatFloor = 0);
  189. bool CheckWalkability(const Vec3& origin, const Vec3& target, float radius, const StaticPhysEntityArray& entities,
  190.                       const StaticAABBArray& aabbs, Vec3* finalFloor = 0, bool* flatFloor = 0);
  191. bool CheckWalkability(const Vec3& origin, const Vec3& target, float radius, const ListPositions& boundary,
  192.                       Vec3* finalFloor = 0, bool* flatFloor = 0, const AABB* boundaryAABB = 0);
  193.  
  194. //====================================================================
  195. // GetEntitiesFromAABB
  196. //====================================================================
  197. inline unsigned GetEntitiesFromAABB(PhysicalEntityListAutoPtr& entities, const AABB& aabb, EAICollisionEntities aiCollisionEntities)
  198. {
  199.         IPhysicalEntity** pEntities = NULL;
  200.         unsigned nEntities = gEnv->pPhysicalWorld->GetEntitiesInBox(aabb.min, aabb.max, pEntities, aiCollisionEntities | ent_allocate_list);
  201.  
  202.         // (MATT) At this time, geb() will return pointer to internal memory even if no entities were found  {2009/11/23}
  203.         if (!nEntities)
  204.                 pEntities = NULL;
  205.         entities = pEntities;
  206.         return nEntities;
  207. }
  208.  
  209. ILINE size_t GetPhysicalEntitiesInBox(Vec3 boxMin, Vec3 boxMax, IPhysicalEntity**& entityList, int entityTypes)
  210. {
  211.         extern IPhysicalEntity* g_AIEntitiesInBoxPreAlloc[GetPhysicalEntitiesInBoxMaxResultCount];
  212.  
  213.         entityList = g_AIEntitiesInBoxPreAlloc;
  214.  
  215.         size_t entityCount = (size_t)gEnv->pPhysicalWorld->GetEntitiesInBox(boxMin, boxMax, entityList,
  216.                                                                             entityTypes | ent_allocate_list, GetPhysicalEntitiesInBoxMaxResultCount);
  217.  
  218.         if (entityCount > GetPhysicalEntitiesInBoxMaxResultCount)
  219.         {
  220.                 assert(entityList != &g_AIEntitiesInBoxPreAlloc[0]);
  221.                 memcpy(g_AIEntitiesInBoxPreAlloc, entityList, sizeof(IPhysicalEntity*) * GetPhysicalEntitiesInBoxMaxResultCount);
  222.  
  223.                 gEnv->pPhysicalWorld->GetPhysUtils()->DeletePointer(entityList);
  224.                 entityList = g_AIEntitiesInBoxPreAlloc;
  225.  
  226.                 return GetPhysicalEntitiesInBoxMaxResultCount;
  227.         }
  228.  
  229.         return entityCount;
  230. }
  231.  
  232. ILINE size_t GetPhysicalEntitiesInBox(Vec3 boxMin, Vec3 boxMax, StaticPhysEntityArray& entityList, int entityTypes)
  233. {
  234.         entityList.resize(GetPhysicalEntitiesInBoxMaxResultCount);
  235.         IPhysicalEntity** entityListPtr = &entityList[0];
  236.  
  237.         size_t entityCount = (size_t)gEnv->pPhysicalWorld->GetEntitiesInBox(boxMin, boxMax, entityListPtr,
  238.                                                                             entityTypes | ent_allocate_list, GetPhysicalEntitiesInBoxMaxResultCount);
  239.  
  240.         if (entityCount > GetPhysicalEntitiesInBoxMaxResultCount)
  241.         {
  242.                 assert(entityListPtr != &entityList[0]);
  243.                 memcpy(&entityList[0], entityListPtr, sizeof(IPhysicalEntity*) * GetPhysicalEntitiesInBoxMaxResultCount);
  244.  
  245.                 gEnv->pPhysicalWorld->GetPhysUtils()->DeletePointer(entityListPtr);
  246.  
  247.                 return GetPhysicalEntitiesInBoxMaxResultCount;
  248.         }
  249.  
  250.         entityList.resize(entityCount);
  251.  
  252.         return entityCount;
  253. }
  254.  
  255. //====================================================================
  256. // OverlapSphere
  257. //====================================================================
  258. inline bool OverlapSphere(const Vec3& pos, float radius, EAICollisionEntities aiCollisionEntities)
  259. {
  260.         primitives::sphere spherePrim;
  261.         spherePrim.center = pos;
  262.         spherePrim.r = radius;
  263.  
  264.         intersection_params params;
  265.         params.bNoBorder = true;
  266.         params.bNoAreaContacts = true;
  267.         params.bNoIntersection = true;
  268.         params.bStopAtFirstTri = true;
  269.         params.bThreadSafe = true;
  270.  
  271.         IPhysicalWorld* pPhysics = gEnv->pPhysicalWorld;
  272.         float d = pPhysics->PrimitiveWorldIntersection(spherePrim.type, &spherePrim, Vec3Constants<float>::fVec3_Zero,
  273.                                                        aiCollisionEntities, 0, 0, geom_colltype0, &params);
  274.  
  275.         return (d != 0.0f);
  276. }
  277.  
  278. //====================================================================
  279. // OverlapCapsule
  280. //====================================================================
  281. inline bool OverlapCapsule(const Lineseg& lineseg, float radius, EAICollisionEntities aiCollisionEntities)
  282. {
  283.         primitives::capsule capsulePrim;
  284.         capsulePrim.center = 0.5f * (lineseg.start + lineseg.end);
  285.         capsulePrim.axis = lineseg.end - lineseg.start;
  286.         capsulePrim.hh = 0.5f * capsulePrim.axis.NormalizeSafe(Vec3Constants<float>::fVec3_OneZ);
  287.         capsulePrim.r = radius;
  288.  
  289.         intersection_params ip;
  290.         ip.bNoBorder = true;
  291.         ip.bNoAreaContacts = true;
  292.         ip.bNoIntersection = true;
  293.         ip.bStopAtFirstTri = true;
  294.         ip.bThreadSafe = true;
  295.  
  296.         IPhysicalWorld* pPhysics = gEnv->pPhysicalWorld;
  297.         float d = pPhysics->PrimitiveWorldIntersection(capsulePrim.type, &capsulePrim, Vec3Constants<float>::fVec3_Zero,
  298.                                                        aiCollisionEntities, 0, 0, geom_colltype0, &ip);
  299.  
  300.         return (d != 0.0f);
  301. }
  302.  
  303. //====================================================================
  304. // OverlapCylinder
  305. //====================================================================
  306. inline bool OverlapCylinder(const Lineseg& lineseg, float radius, EAICollisionEntities aiCollisionEntities, IPhysicalEntity* pSkipEntity = NULL, int customFilter = 0, int* pEntityID = 0, int maxIDs = 0)
  307. {
  308.         IPhysicalEntity* pSkipEnts = pSkipEntity;
  309.  
  310.         primitives::cylinder cylinderPrim;
  311.         cylinderPrim.center = 0.5f * (lineseg.start + lineseg.end);
  312.         cylinderPrim.axis = lineseg.end - lineseg.start;
  313.         cylinderPrim.hh = 0.5f * cylinderPrim.axis.NormalizeSafe(Vec3Constants<float>::fVec3_OneZ);
  314.         cylinderPrim.r = radius;
  315.  
  316.         geom_contact* contacts = 0;
  317.         intersection_params params;
  318.         params.bNoBorder = true;
  319.         params.bNoAreaContacts = true;
  320.         params.bNoIntersection = true;
  321.         params.bStopAtFirstTri = true;
  322.  
  323.         IPhysicalWorld* pPhysics = gEnv->pPhysicalWorld;
  324.         WriteLockCond lockContacts;
  325.         float d = pPhysics->PrimitiveWorldIntersection(cylinderPrim.type, &cylinderPrim, Vec3Constants<float>::fVec3_Zero,
  326.                                                        aiCollisionEntities, &contacts, 0, customFilter ? customFilter : geom_colltype0, &params, 0, 0, &pSkipEnts, pSkipEntity ? 1 : 0, &lockContacts);
  327.  
  328.         if (contacts && pEntityID)
  329.         {
  330.                 maxIDs = min(maxIDs, static_cast<int>(d));
  331.                 for (int loop = 0; loop < maxIDs; ++loop)
  332.                 {
  333.                         pEntityID[loop] = contacts[loop].iPrim[0];
  334.                 }
  335.         }
  336.  
  337.         if (d != 0.0f)
  338.         {
  339.                 return true;
  340.         }
  341.  
  342.         return false;
  343. }
  344.  
  345. //====================================================================
  346. // IntersectSegment
  347. //====================================================================
  348. inline bool OverlapSegment(const Lineseg& lineseg, EAICollisionEntities aiCollisionEntities)
  349. {
  350.         Vec3 dir = lineseg.end - lineseg.start;
  351.         ray_hit hit;
  352.         if (gAIEnv.pRayCaster->Cast(RayCastRequest(lineseg.start, dir, aiCollisionEntities,
  353.                                                    rwi_ignore_noncolliding | rwi_stop_at_pierceable)))
  354.                 return true;
  355.         else
  356.                 return false;
  357. }
  358.  
  359. //====================================================================
  360. // IntersectSegment
  361. //====================================================================
  362. inline bool IntersectSegment(Vec3& hitPos, const Lineseg& lineseg, EAICollisionEntities aiCollisionEntities, int filter = rwi_ignore_noncolliding | rwi_stop_at_pierceable)
  363. {
  364.         ray_hit hit;
  365.         const RayCastResult& result = gAIEnv.pRayCaster->Cast(RayCastRequest(lineseg.start, lineseg.end - lineseg.start,
  366.                                                                              aiCollisionEntities, filter));
  367.         if (!result || (result[0].dist < 0.0f))
  368.                 return false;
  369.  
  370.         hitPos = result[0].pt;
  371.         return true;
  372. }
  373.  
  374. //====================================================================
  375. // GetFloorPos
  376. /// returns false if no valid floor found. Checks from upDist above pos to
  377. /// downDist below pos.
  378. //====================================================================
  379. inline bool GetFloorPos(Vec3& floorPos, const Vec3& pos, float upDist, float downDist, float radius, EAICollisionEntities aiCollisionEntities)
  380. {
  381.         // (MATT) This function doesn't have the behaviour you might expect. It only searches up by radius amount, and checks down by
  382.         // upDist + downDist + rad. Might or might not be safe to fix, but this code is being superseded. {2009/07/01}
  383.         FUNCTION_PROFILER(GetISystem(), PROFILE_AI);
  384.         Vec3 delta = Vec3(0, 0, -downDist - upDist);
  385.         Vec3 capStart = pos + Vec3(0, 0, upDist);
  386.         const Vec3 vRad(0, 0, radius);
  387.  
  388.         if (IntersectSegment(floorPos, Lineseg(pos + vRad, pos - Vec3(0.0f, 0.0f, downDist)), aiCollisionEntities, rwi_stop_at_pierceable | (geom_colltype_player << rwi_colltype_bit)))
  389.                 return true;
  390.  
  391.         if (delta.z > 0.0f)
  392.                 return false;
  393.  
  394.         float hitDist;
  395.  
  396.         float numSteps = 1.f + downDist * 0.5f;
  397.         float fStepNumInv = 1.f / numSteps;
  398.         Lineseg seg;
  399.         for (float fStep = 0.f; fStep < numSteps; fStep += 1.f)
  400.         {
  401.                 float frac1 = fStep * fStepNumInv;
  402.                 float frac2 = (fStep + 1.f) * fStepNumInv;
  403.                 seg.start = capStart + frac1 * delta;
  404.                 seg.end = capStart + frac2 * delta;
  405.                 if (IntersectSweptSphere(0, hitDist, seg, radius, aiCollisionEntities, 0, 0, rwi_stop_at_pierceable | (geom_colltype_player << rwi_colltype_bit)))
  406.                 {
  407.                         floorPos.Set(pos.x, pos.y, seg.start.z - (radius + hitDist));
  408.                         return true;
  409.                 }
  410.         }
  411.         return false;
  412. }
  413.  
  414. //===================================================================
  415. // CheckBodyPos
  416. /// Given a floor position (expected to be valid!) checks if a "standard"
  417. /// human body will fit there. Returns true if it will.
  418. //===================================================================
  419. inline bool CheckBodyPos(const Vec3& floorPos, EAICollisionEntities aiCollisionEntities)
  420. {
  421.         Vec3 segStart = floorPos + WalkabilityTorsoBaseOffset + Vec3(0, 0, WalkabilityRadius);
  422.         Vec3 segEnd = floorPos + Vec3(0, 0, WalkabilityTotalHeight - WalkabilityRadius);
  423.         Lineseg torsoSeg(segStart, segEnd);
  424.         return !OverlapCapsule(torsoSeg, WalkabilityRadius, aiCollisionEntities);
  425. }
  426.  
  427. //===================================================================
  428. // CalcPolygonArea
  429. //===================================================================
  430. template<typename VecContainer, typename T>
  431. T CalcPolygonArea(const VecContainer& pts)
  432. {
  433.         if (pts.size() < 2)
  434.                 return T(0);
  435.  
  436.         T totalCross = T(0);
  437.         const typename VecContainer::const_iterator itEnd = pts.end();
  438.         typename VecContainer::const_iterator it = pts.begin();
  439.         const typename VecContainer::const_iterator firstIt = pts.begin();
  440.         typename VecContainer::const_iterator itNext = it;
  441.         for (; it != itEnd; ++it)
  442.         {
  443.                 if (++itNext == itEnd)
  444.                         itNext = pts.begin();
  445.                 totalCross += (it->x - firstIt->x) * (itNext->y - firstIt->y) - (itNext->x - firstIt->x) * (it->y - firstIt->y);
  446.         }
  447.  
  448.         return totalCross / T(2);
  449. }
  450.  
  451. //===================================================================
  452. // IsWoundAnticlockwise
  453. //===================================================================
  454. template<typename VecContainer, typename T>
  455. bool IsWoundAnticlockwise(const VecContainer& pts)
  456. {
  457.         if (pts.size() < 2)
  458.                 return true;
  459.         return CalcPolygonArea<VecContainer, T>(pts) > T(0);
  460. }
  461.  
  462. //===================================================================
  463. // EnsureShapeIsWoundAnticlockwise
  464. //===================================================================
  465. template<typename VecContainer, typename T>
  466. void EnsureShapeIsWoundAnticlockwise(VecContainer& pts)
  467. {
  468.         if (!IsWoundAnticlockwise<VecContainer, T>(pts))
  469.                 std::reverse(pts.begin(), pts.end());
  470. }
  471.  
  472. //===================================================================
  473. // GetFloorRectangleFromOrientedBox
  474. // Returns a rectangle in XY plane from given
  475. //===================================================================
  476. struct SAIRect3
  477. {
  478.         Vec3 center;
  479.         Vec3 axisu, axisv;
  480.         Vec2 min, max;
  481. };
  482.  
  483. void GetFloorRectangleFromOrientedBox(const Matrix34& tm, const AABB& box, SAIRect3& rect);
  484.  
  485. //===================================================================
  486. // OverlapLinesegAABB2D
  487. //===================================================================
  488. inline bool OverlapLinesegAABB2D(const Vec3& p0, const Vec3& p1, const AABB& aabb)
  489. {
  490.         Vec3 c = (aabb.min + aabb.max) * 0.5f;
  491.         Vec3 e = aabb.max - c;
  492.         Vec3 m = (p0 + p1) * 0.5f;
  493.         Vec3 d = p1 - m;
  494.         m = m - c;
  495.  
  496.         // Try world coordinate axes as separating axes
  497.         float adx = fabsf(d.x);
  498.         if (fabsf(m.x) > e.x + adx) return false;
  499.         float ady = fabsf(d.y);
  500.         if (fabsf(m.y) > e.y + ady) return false;
  501.  
  502.         // Add in an epsilon term to counteract arithmetic errors when segment is
  503.         // (near) parallel to a coordinate axis (see text for detail)
  504.         const float EPSILON = 0.000001f;
  505.         adx += EPSILON;
  506.         ady += EPSILON;
  507.  
  508.         if (fabsf(m.x * d.y - m.y * d.x) > e.x * ady + e.y * adx) return false;
  509.  
  510.         // No separating axis found; segment must be overlapping AABB
  511.         return true;
  512. }
  513.  
  514. void CleanupAICollision();
  515.  
  516. #endif
  517.  
downloadAICollision.h 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