-
Notifications
You must be signed in to change notification settings - Fork 30
Feat: physics force and impulse #1338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 28 commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
e5b09b1
pointed to the branch and made tests
popuz 464090c
add main helper for application of several impulses
popuz 0e57d5d
add first physics force helpers and separated into files
popuz 3c9e55c
small clean-up
popuz 1af3fd0
adjusted to new renamings and removed Space
popuz c6a5bf4
apply renaming to parameter of the main helpers
popuz afd5481
more clean-up
popuz 3581dd8
removed unneeded advanceFrame
popuz 947bef4
removed unneeded advanceFrame
popuz 9e3b256
changed overload to be specifically direction instead of vector
popuz 810fb57
updated comments
popuz 00fe871
made helpers for vector operations
popuz ca73eb8
update namings
popuz b6395b2
updated protocol
popuz 07cba38
add localToWorld helper
popuz fa4aa30
add knockback
popuz 31572ea
add force for duration
popuz ca5f044
add repulsion force
popuz 127a652
make build
popuz 3e2d5dc
pointed to @experimental
popuz 2fdbc5c
pointed to @next
popuz 5becb68
make build
popuz 116edd0
fixed linter issues
popuz 4a59913
Merge branch 'main' into feat/physics-force-and-impulse
popuz 6c5d66c
updated snapshots
popuz 75639bd
Merge remote-tracking branch 'origin/feat/physics-force-and-impulse' …
popuz f8dcd2d
Merge branch 'main' into feat/physics-force-and-impulse
popuz 281c5db
change getOrNull to Has
popuz fba7691
removed math.ts and used Vector3 helpers instead. Moved map up
popuz 4eee635
don't add force for zero Vector
popuz 8dc05f4
add a comment for fource sources
popuz 318fddc
mirrored vector3 operations in separate file (to fix @dcl/ecs-math pa…
popuz 994e83b
make build
popuz 21f2ac6
updated snapshots
popuz 1824cc5
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/phy…
popuz 5941e53
removed deps on ecs-math
popuz 6d32683
fix for external forces in multiplayer
popuz 42b972e
add forces to not sync
popuz c72d0d3
force clean-up
popuz bb6c6bc
make snapshot
popuz 1659021
add trigger area result to non-sync component
popuz fdac09b
swap exception to error log
popuz 99717c7
removed TriggerAreaResults from sync components
popuz e8e2906
Merge remote-tracking branch 'refs/remotes/origin/main' into feat/phy…
popuz 9f87c97
another make snapshot
popuz 52bf3bc
fixed test
popuz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| export * from './coordinates' | ||
| export * from './math' | ||
| export * from './tree' | ||
| export { createTimers, Timers, TimerId, TimerCallback } from './timers' |
This file contains hidden or 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,38 @@ | ||
| import { Vector3Type } from '../../schemas' | ||
|
|
||
| /** @public */ | ||
| export function isZeroVector(v: Vector3Type): boolean { | ||
| return v.x === 0 && v.y === 0 && v.z === 0 | ||
| } | ||
|
|
||
| /** @public */ | ||
| export function normalizeVector(v: Vector3Type): Vector3Type { | ||
| const len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) | ||
| if (len === 0) return { x: 0, y: 0, z: 0 } | ||
| return { x: v.x / len, y: v.y / len, z: v.z / len } | ||
| } | ||
|
|
||
| /** @public */ | ||
| export function scaleVector(v: Vector3Type, s: number): Vector3Type { | ||
| return { x: v.x * s, y: v.y * s, z: v.z * s } | ||
| } | ||
|
|
||
| /** @public */ | ||
| export function addVectors(a: Vector3Type, b: Vector3Type): Vector3Type { | ||
| return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z } | ||
| } | ||
|
|
||
| /** @public */ | ||
| export function subtractVectors(a: Vector3Type, b: Vector3Type): Vector3Type { | ||
| return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z } | ||
| } | ||
|
|
||
| /** @public */ | ||
| export function vectorLength(v: Vector3Type): number { | ||
| return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z) | ||
| } | ||
|
|
||
| /** @public */ | ||
| export function vectorsEqual(a: Vector3Type, b: Vector3Type): boolean { | ||
| return a.x === b.x && a.y === b.y && a.z === b.z | ||
| } | ||
This file contains hidden or 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 hidden or 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 hidden or 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,198 @@ | ||
| import * as components from '../components' | ||
| import { Entity } from '../engine' | ||
| import { IEngine } from '../engine' | ||
| import { Vector3Type } from '../schemas' | ||
| import { | ||
| isZeroVector, | ||
| normalizeVector, | ||
| scaleVector, | ||
| addVectors, | ||
| subtractVectors, | ||
| vectorLength, | ||
| vectorsEqual, | ||
| createTimers | ||
| } from '../runtime/helpers' | ||
| import { KnockbackFalloff } from './physics-impulse' | ||
|
|
||
| /** | ||
| * @internal | ||
| */ | ||
| export interface PhysicsForceHelper { | ||
| applyForceToPlayer(source: Entity, vector: Vector3Type, magnitude?: number): void | ||
| removeForceFromPlayer(source: Entity): void | ||
| applyForceToPlayerForDuration(source: Entity, duration: number, vector: Vector3Type, magnitude?: number): void | ||
| applyRepulsionForceToPlayer( | ||
| source: Entity, | ||
| fromPosition: Vector3Type, | ||
| magnitude: number, | ||
| radius?: number, | ||
| falloff?: KnockbackFalloff | ||
| ): void | ||
| } | ||
|
|
||
| /** @internal */ | ||
| export function createPhysicsForceHelper(engine: IEngine): PhysicsForceHelper { | ||
| const PhysicsCombinedForce = components.PhysicsCombinedForce(engine) | ||
| const Transform = components.Transform(engine) | ||
|
|
||
| const forceSources = new Map<Entity, Vector3Type>() | ||
| const repulsionSources = new Map< | ||
| Entity, | ||
| { fromPosition: Vector3Type; magnitude: number; radius: number; falloff: KnockbackFalloff } | ||
| >() | ||
| let lastWrittenForceVector: Vector3Type | null = null | ||
|
|
||
| function recalcForce(): void { | ||
| if (forceSources.size === 0) { | ||
| if (PhysicsCombinedForce.has(engine.PlayerEntity)) { | ||
| PhysicsCombinedForce.deleteFrom(engine.PlayerEntity) | ||
| } | ||
| lastWrittenForceVector = null | ||
| return | ||
| } | ||
|
|
||
| const current = PhysicsCombinedForce.getOrNull(engine.PlayerEntity) | ||
| if (current && lastWrittenForceVector && current.vector) { | ||
| if (!vectorsEqual(current.vector, lastWrittenForceVector)) { | ||
| throw new Error( | ||
| 'PBPhysicsCombinedForce was modified outside Physics helper. ' + | ||
| 'Do not mix direct component access with Physics.applyForceToPlayer().' | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| let sum: Vector3Type = { x: 0, y: 0, z: 0 } | ||
| for (const v of forceSources.values()) { | ||
|
popuz marked this conversation as resolved.
|
||
| sum = addVectors(sum, v) | ||
| } | ||
|
|
||
| PhysicsCombinedForce.createOrReplace(engine.PlayerEntity, { vector: sum }) | ||
| lastWrittenForceVector = sum | ||
| } | ||
|
|
||
| function applyForceToPlayer(source: Entity, vector: Vector3Type, magnitude?: number): void { | ||
| let finalVector: Vector3Type | ||
|
|
||
| if (typeof magnitude === 'number') { | ||
| if (isZeroVector(vector)) return | ||
| finalVector = scaleVector(normalizeVector(vector), magnitude) | ||
| } else { | ||
| finalVector = vector | ||
|
popuz marked this conversation as resolved.
|
||
| } | ||
|
|
||
| forceSources.set(source, finalVector) | ||
| recalcForce() | ||
| } | ||
|
|
||
| function removeForceFromPlayer(source: Entity): void { | ||
| repulsionSources.delete(source) | ||
| const timerId = durationTimers.get(source) | ||
| if (timerId !== undefined) { | ||
| timers.clearTimeout(timerId) | ||
| durationTimers.delete(source) | ||
| } | ||
| if (!forceSources.has(source)) return | ||
| forceSources.delete(source) | ||
| recalcForce() | ||
| } | ||
|
|
||
| const timers = createTimers(engine) | ||
| const durationTimers = new Map<Entity, number>() | ||
|
popuz marked this conversation as resolved.
Outdated
|
||
|
|
||
| function scheduleForceDuration(source: Entity, seconds: number): void { | ||
| const existing = durationTimers.get(source) | ||
| if (existing !== undefined) { | ||
| timers.clearTimeout(existing) | ||
| } | ||
|
|
||
| const timerId = timers.setTimeout(() => { | ||
| durationTimers.delete(source) | ||
| removeForceFromPlayer(source) | ||
| }, seconds * 1000) | ||
| durationTimers.set(source, timerId) | ||
| } | ||
|
|
||
| function applyForceToPlayerForDuration( | ||
| source: Entity, | ||
| duration: number, | ||
| vector: Vector3Type, | ||
| magnitude?: number | ||
| ): void { | ||
| applyForceToPlayer(source, vector, magnitude) | ||
| scheduleForceDuration(source, duration) | ||
| } | ||
|
|
||
| function computeRepulsionVector( | ||
| fromPosition: Vector3Type, | ||
| magnitude: number, | ||
| radius: number, | ||
| falloff: KnockbackFalloff | ||
| ): Vector3Type | null { | ||
| const diff = subtractVectors(Transform.get(engine.PlayerEntity).position, fromPosition) | ||
|
|
||
| if (isZeroVector(diff)) return { x: 0, y: magnitude, z: 0 } | ||
|
popuz marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Fast path: default params — no need to compute distance | ||
| if (radius === Infinity && falloff === KnockbackFalloff.CONSTANT) { | ||
| return scaleVector(normalizeVector(diff), magnitude) | ||
| } | ||
|
|
||
| const distance = vectorLength(diff) | ||
| if (distance > radius) return null | ||
|
|
||
| let effectiveMagnitude: number | ||
| switch (falloff) { | ||
| case KnockbackFalloff.LINEAR: | ||
| effectiveMagnitude = magnitude * (1 - distance / radius) | ||
| break | ||
| case KnockbackFalloff.INVERSE_SQUARE: | ||
| effectiveMagnitude = magnitude / (distance * distance + 1) | ||
| break | ||
| case KnockbackFalloff.CONSTANT: | ||
| default: | ||
| effectiveMagnitude = magnitude | ||
| break | ||
| } | ||
|
|
||
| if (effectiveMagnitude === 0) return null | ||
|
|
||
| // normalize(diff) * effectiveMagnitude in one step | ||
| return scaleVector(diff, effectiveMagnitude / distance) | ||
| } | ||
|
|
||
| function applyRepulsionForceToPlayer( | ||
| source: Entity, | ||
| fromPosition: Vector3Type, | ||
| magnitude: number, | ||
| radius: number = Infinity, | ||
| falloff: KnockbackFalloff = KnockbackFalloff.CONSTANT | ||
| ): void { | ||
| repulsionSources.set(source, { fromPosition, magnitude, radius, falloff }) | ||
|
|
||
| const vector = computeRepulsionVector(fromPosition, magnitude, radius, falloff) | ||
| if (vector) { | ||
| forceSources.set(source, vector) | ||
| } else { | ||
| forceSources.delete(source) | ||
| } | ||
| recalcForce() | ||
| } | ||
|
|
||
| // Background system: recalculate repulsion vectors every tick | ||
| engine.addSystem(() => { | ||
| if (repulsionSources.size === 0) return | ||
|
|
||
| for (const [source, { fromPosition, magnitude, radius, falloff }] of repulsionSources) { | ||
| const vector = computeRepulsionVector(fromPosition, magnitude, radius, falloff) | ||
| if (vector) { | ||
| forceSources.set(source, vector) | ||
| } else { | ||
| forceSources.delete(source) | ||
| } | ||
| } | ||
|
|
||
| recalcForce() | ||
| }) | ||
|
|
||
| return { applyForceToPlayer, removeForceFromPlayer, applyForceToPlayerForDuration, applyRepulsionForceToPlayer } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.