-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Crowd): improve 'update' method, add support for fixed time step…
…ping with interpolation
- Loading branch information
1 parent
25f585a
commit a128ee1
Showing
12 changed files
with
412 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- | ||
'@recast-navigation/core': minor | ||
'recast-navigation': minor | ||
--- | ||
|
||
feat(Crowd): improve 'update' method, add support for fixed time stepping with interpolation | ||
|
||
The previous `update` method did a form of variable time stepping with a target time step. | ||
|
||
This has been replaced with a method that supports fixed time stepping, variable time stepping, and fixed time stepping with interpolation. | ||
|
||
The new method signature is: | ||
|
||
```ts | ||
update(dt: number, timeSinceLastCalled?: number, maxSubSteps?: number): void; | ||
``` | ||
|
||
To perform a variable sized time step update, call `update` with only the `dt` parameter. | ||
|
||
```ts | ||
crowd.update(1 / 60); | ||
|
||
// or | ||
|
||
crowd.update(deltaTime); | ||
``` | ||
|
||
To perform fixed time stepping with interpolation, call `update` with the `dt`, `timeSinceLastCalled`, and `maxSubSteps` parameters. | ||
|
||
```ts | ||
const dt = 1 / 60; | ||
const timeSinceLastCalled = /* get this from your game loop */; | ||
const maxSubSteps = 10; // optional, default is 10 | ||
|
||
crowd.update(dt, timeSinceLastCalled, maxSubSteps); | ||
``` | ||
|
||
The interpolated position of the agents can be retrieved from `agent.interpolatedPosition`. | ||
|
||
If the old behavior is desired, the following can be done: | ||
|
||
```ts | ||
const crowd = new Crowd(navMesh); | ||
|
||
const targetStepSize = 1 / 60; | ||
const maxSubSteps = 10; | ||
const episilon = 0.001; | ||
|
||
const update = (deltaTime: number) => { | ||
if (deltaTime <= Epsilon) { | ||
return; | ||
} | ||
|
||
if (targetStepSize <= episilon) { | ||
crowd.update(deltaTime); | ||
} else { | ||
let iterationCount = Math.floor(deltaTime / targetStepSize); | ||
|
||
if (iterationCount > maxSubSteps) { | ||
iterationCount = maxSubSteps; | ||
} | ||
if (iterationCount < 1) { | ||
iterationCount = 1; | ||
} | ||
|
||
const step = deltaTime / iterationCount; | ||
for (let i = 0; i < iterationCount; i++) { | ||
crowd.update(step); | ||
} | ||
} | ||
}; | ||
``` | ||
|
||
As part of this change, the `maximumSubStepCount` and `timeFactor` Crowd properties have been removed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
packages/recast-navigation/.storybook/stories/crowd/crowd-with-multiple-agents.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { OrbitControls } from '@react-three/drei'; | ||
import { ThreeEvent, useFrame } from '@react-three/fiber'; | ||
import { Crowd, NavMesh, NavMeshQuery } from '@recast-navigation/core'; | ||
import React, { useEffect, useState } from 'react'; | ||
import { threeToSoloNavMesh } from 'recast-navigation/three'; | ||
import { Group, Mesh, MeshStandardMaterial } from 'three'; | ||
import { Debug } from '../../common/debug'; | ||
import { NavTestEnvironment } from '../../common/nav-test-environment'; | ||
import { decorators } from '../../decorators'; | ||
import { parameters } from '../../parameters'; | ||
|
||
export default { | ||
title: 'Crowd / Crowd With Multiple Agents', | ||
decorators, | ||
parameters, | ||
}; | ||
|
||
const agentMaterial = new MeshStandardMaterial({ | ||
color: 'red', | ||
}); | ||
|
||
export const CrowdWithMultipleAgents = () => { | ||
const [group, setGroup] = useState<Group | null>(null); | ||
|
||
const [navMesh, setNavMesh] = useState<NavMesh | undefined>(); | ||
const [navMeshQuery, setNavMeshQuery] = useState<NavMeshQuery | undefined>(); | ||
const [crowd, setCrowd] = useState<Crowd | undefined>(); | ||
|
||
useEffect(() => { | ||
if (!group) return; | ||
|
||
const meshes: Mesh[] = []; | ||
|
||
group.traverse((child) => { | ||
if (child instanceof Mesh) { | ||
meshes.push(child); | ||
} | ||
}); | ||
|
||
const maxAgentRadius = 0.15; | ||
const cellSize = 0.05; | ||
|
||
const { success, navMesh } = threeToSoloNavMesh(meshes, { | ||
cs: cellSize, | ||
ch: 0.2, | ||
walkableRadius: Math.ceil(maxAgentRadius / cellSize), | ||
}); | ||
|
||
if (!success) return; | ||
|
||
const navMeshQuery = new NavMeshQuery(navMesh); | ||
|
||
const crowd = new Crowd(navMesh, { | ||
maxAgents: 10, | ||
maxAgentRadius, | ||
}); | ||
|
||
for (let i = 0; i < 10; i++) { | ||
const { randomPoint: position } = | ||
navMeshQuery.findRandomPointAroundCircle({ x: -2, y: 0, z: 3 }, 1); | ||
|
||
crowd.addAgent(position, { | ||
radius: 0.1 + Math.random() * 0.05, | ||
height: 0.5, | ||
maxAcceleration: 4.0, | ||
maxSpeed: 1.0, | ||
separationWeight: 1.0, | ||
}); | ||
} | ||
|
||
setNavMesh(navMesh); | ||
setNavMeshQuery(navMeshQuery); | ||
setCrowd(crowd); | ||
|
||
return () => { | ||
crowd.destroy(); | ||
navMesh.destroy(); | ||
|
||
setNavMesh(undefined); | ||
setNavMeshQuery(undefined); | ||
setCrowd(undefined); | ||
}; | ||
}, [group]); | ||
|
||
useFrame((_, delta) => { | ||
if (!crowd) return; | ||
|
||
crowd.update(delta); | ||
}); | ||
|
||
const onClick = (e: ThreeEvent<MouseEvent>) => { | ||
if (!navMesh || !navMeshQuery || !crowd) return; | ||
|
||
const { point: target } = navMeshQuery.findClosestPoint(e.point); | ||
|
||
for (const agent of crowd.getAgents()) { | ||
agent.requestMoveTarget(target); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<group ref={setGroup} onClick={onClick}> | ||
<NavTestEnvironment /> | ||
</group> | ||
|
||
<Debug navMesh={navMesh} crowd={crowd} agentMaterial={agentMaterial} /> | ||
|
||
<OrbitControls /> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.