Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions fission/src/mirabuf/ProtectedZoneSceneObject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Jolt from "@azaleacolburn/jolt-physics"
import * as THREE from "three"
import type Jolt from "@azaleacolburn/jolt-physics"
import type * as THREE from "three"
import EventSystem, { type SynthesisEventListener } from "@/systems/EventSystem.ts"
import MatchMode from "@/systems/match_mode/MatchMode"
import { MatchModeType } from "@/systems/match_mode/MatchModeTypes"
Expand All @@ -11,7 +11,7 @@ import { MiraType } from "./MirabufLoader"
import type MirabufSceneObject from "./MirabufSceneObject"
import type { RigidNodeAssociate } from "./MirabufSceneObject"
import { ContactType } from "./ZoneTypes"
import { ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes"
import type { ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes"
Comment thread
AlexD717 marked this conversation as resolved.

class ProtectedZoneSceneObject extends ZoneSceneObject<ProtectedZonePreferences> {
private _robotsInside: Map<MirabufSceneObject, number> = new Map()
Expand Down
4 changes: 2 additions & 2 deletions fission/src/mirabuf/ScoringZoneSceneObject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Jolt from "@azaleacolburn/jolt-physics"
import * as THREE from "three"
import type Jolt from "@azaleacolburn/jolt-physics"
import type * as THREE from "three"
Comment thread
AlexD717 marked this conversation as resolved.
import ScoreTracker from "@/systems/match_mode/ScoreTracker"
import EventSystem from "@/systems/EventSystem.ts"
import PreferencesSystem from "@/systems/preferences/PreferencesSystem"
Expand Down
2 changes: 1 addition & 1 deletion fission/src/mirabuf/ZoneSceneObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
convertThreeQuaternionToJoltQuat,
convertThreeVector3ToJoltRVec3,
} from "@/util/TypeConversions"
import { deltaFieldTransformsPhysicalProp, VisualProperties } from "@/util/threejs/MeshCreation"
import { deltaFieldTransformsPhysicalProp, type VisualProperties } from "@/util/threejs/MeshCreation"
Comment thread
AlexD717 marked this conversation as resolved.
import type MirabufSceneObject from "./MirabufSceneObject"

export default abstract class ZoneSceneObject<P extends object> extends SceneObject {
Expand Down
99 changes: 98 additions & 1 deletion fission/src/systems/physics/PhysicsSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,32 @@ const SIGNIFICANT_FRICTION_THRESHOLD = 0.05
const MAX_ROBOT_MASS = 250.0
const MAX_GP_MASS = 10.0

// Minimum threshold for Wadell sphericity to consider a convex hull a sphere.
// 2025 & 2026 spheres have a sphericity of 0.9999
// 2023 cube has a value of 0.9532
const MIN_SPHERICITY = 0.98
Comment thread
AlexD717 marked this conversation as resolved.
Outdated

const SPHERE_GP_ANGULAR_DAMPING = 0.5
const SPHERE_GP_LINEAR_DAMPING = 0.1

// Threshold needed to overcome to start moving spheres
const SPHERE_GP_STICTION_LINEAR_SPEED = 0.04 // meters / second
const SPHERE_GP_STICTION_ANGULAR_SPEED = 0.1 // radians / second

/**
* Wadell sphericity of a solid from its volume and surface area. Returns a value in (0, 1],
* approaching 1 as the solid approaches a perfect sphere, or 0 when the area is non-positive.
* https://en.wikipedia.org/wiki/Sphericity
*
* @param volume Solid volume (any unit).
* @param area Solid surface area (consistent unit. The measure is dimensionless).
*/
function computeSphericity(volume: number, area: number): number {
if (volume <= 0 || area <= 0) return 0
const volumeEquivalentSphereArea = Math.cbrt(Math.PI) * Math.pow(6 * volume, 2 / 3)
return volumeEquivalentSphereArea / area
}

let lastDeltaT = STANDARD_SIMULATION_PERIOD
export function getLastDeltaT(): number {
return lastDeltaT
Expand Down Expand Up @@ -89,6 +115,8 @@ class PhysicsSystem extends WorldSystem {
private _joltBodyInterface: Jolt.BodyInterface
private _bodies: Array<Jolt.BodyID>
private _constraints: Array<Jolt.Constraint>
// Sphere game-piece bodies that get the resting-stiction pass each step (see update()).
private _sphereGamePieceBodies: Array<Jolt.BodyID> = []

private _physicsEventQueue: SynthesisEvent<
"OnContactAddedEvent" | "OnContactPersistedEvent" | "OnContactValidateEvent"
Expand Down Expand Up @@ -839,9 +867,13 @@ class PhysicsSystem extends WorldSystem {

nonPhysicsNodes.forEach(rn => {
const compoundShapeSettings = new JOLT.StaticCompoundShapeSettings()

let shapesAdded = 0

let totalMass = 0
// Accumulated geometry used to decide whether a game piece is sphere-like (see below).
let totalVolume = 0
let totalArea = 0

type FrictionPairing = {
dynamic: number
Expand Down Expand Up @@ -914,6 +946,9 @@ class PhysicsSystem extends WorldSystem {
const [partDefinition, partInstance] = constructPartDefinition(partId)
if (!partDefinition) return

totalVolume += partDefinition.physicalData?.volume ?? 0
totalArea += partDefinition.physicalData?.area ?? 0

const physicalMaterial =
parser.assembly.data!.materials!.physicalMaterials![
partInstance.physicalMaterial ?? DEFAULT_PHYSICAL_MATERIAL_KEY
Expand Down Expand Up @@ -974,10 +1009,28 @@ class PhysicsSystem extends WorldSystem {
return
}

const shape = shapeResult.Get()
let shape = shapeResult.Get()
let appliedSphereCollider = false

if (rn.isDynamic) {
if (rn.isGamePiece) {
if (computeSphericity(totalVolume, totalArea) >= MIN_SPHERICITY) {
const center = shape.GetCenterOfMass()
const volumeMeters3 = totalVolume * 1e-6 // Convert cm^3 to m^3
const radius = Math.max(Math.cbrt((3 * volumeMeters3) / (4 * Math.PI)), 0.01)

const sphereSettings = new JOLT.SphereShapeSettings(radius)
const identityRotation = new JOLT.Quat(0, 0, 0, 1)
const offsetSettings = new JOLT.RotatedTranslatedShapeSettings(
center,
identityRotation,
sphereSettings
)
shape = offsetSettings.Create().Get()
JOLT.destroy(identityRotation)
Comment thread
AlexD717 marked this conversation as resolved.
appliedSphereCollider = true
}

const mass = totalMass == 0.0 ? 1 : Math.min(totalMass, MAX_GP_MASS)
shape.GetMassProperties().mMass = mass
} else {
Expand Down Expand Up @@ -1021,6 +1074,12 @@ class PhysicsSystem extends WorldSystem {
this._bodies.push(body.GetID())
body.SetRestitution(0.4)

if (appliedSphereCollider) {
body.GetMotionProperties().SetAngularDamping(SPHERE_GP_ANGULAR_DAMPING)
body.GetMotionProperties().SetLinearDamping(SPHERE_GP_LINEAR_DAMPING)
this._sphereGamePieceBodies.push(body.GetID())
}

JOLT.destroy(bodySettings)
JOLT.destroy(p)
JOLT.destroy(r)
Expand Down Expand Up @@ -1238,13 +1297,15 @@ class PhysicsSystem extends WorldSystem {
* @param bodies Bodies to destroy.
*/
public destroyBodies(...bodies: Jolt.Body[]) {
this.unregisterSphereGamePieceBodies(bodies.map(x => x.GetID()))
bodies.forEach(x => {
this._joltBodyInterface.RemoveBody(x.GetID())
this._joltBodyInterface.DestroyBody(x.GetID())
})
}

public destroyBodyIds(...bodies: Jolt.BodyID[]) {
this.unregisterSphereGamePieceBodies(bodies)
bodies.forEach(x => {
if (this.isBodyAdded(x)) {
this._joltBodyInterface.RemoveBody(x)
Expand All @@ -1260,6 +1321,7 @@ class PhysicsSystem extends WorldSystem {
mech.constraints.forEach(x => {
this._joltPhysSystem.RemoveConstraint(x.primaryConstraint)
})
this.unregisterSphereGamePieceBodies([...mech.nodeToBody.values()])
mech.nodeToBody.forEach(x => {
this._joltBodyInterface.RemoveBody(x)
this._joltBodyInterface.DestroyBody(x)
Expand All @@ -1270,6 +1332,14 @@ class PhysicsSystem extends WorldSystem {
})
}

private unregisterSphereGamePieceBodies(bodies: Jolt.BodyID[]) {
if (this._sphereGamePieceBodies.length === 0) return
const removed = new Set(bodies.map(b => b.GetIndexAndSequenceNumber()))
this._sphereGamePieceBodies = this._sphereGamePieceBodies.filter(
b => !removed.has(b.GetIndexAndSequenceNumber())
)
}

public getBody(bodyId: Jolt.BodyID): Jolt.Body | undefined {
const hasBody = this.hasBody(bodyId)
if (!hasBody) return
Expand All @@ -1281,6 +1351,30 @@ class PhysicsSystem extends WorldSystem {
return this._joltPhysSystem.GetBodyInterface().IsAdded(bodyId)
}

/**
* Snaps near-stationary sphere game pieces back to rest.
* This is necessary on some fields (ex. 2025, 2026) to prevent them from rolling off the starting positions.
*/
private applySphereGamePieceStiction(): void {
if (this._sphereGamePieceBodies.length === 0) return

const zero = new JOLT.Vec3(0, 0, 0)
this._sphereGamePieceBodies.forEach(bodyId => {
const body = this.getBody(bodyId)
if (!body) return

const atRest =
body.GetLinearVelocity().Length() < SPHERE_GP_STICTION_LINEAR_SPEED &&
body.GetAngularVelocity().Length() < SPHERE_GP_STICTION_ANGULAR_SPEED

if (atRest) {
body.SetLinearVelocity(zero)
body.SetAngularVelocity(zero)
}
})
JOLT.destroy(zero)
}

public update(deltaT: number): void {
if (this._pauseSet.size > 0) {
return
Expand All @@ -1296,6 +1390,8 @@ class PhysicsSystem extends WorldSystem {

this._joltInterface.Step(lastDeltaT, substeps)

this.applySphereGamePieceStiction()

if (World.multiplayerSystem != null) {
const interObjectCollisions = this._physicsEventQueue
.filter((x): x is SynthesisEvent<"OnContactAddedEvent"> => x.type === "OnContactAddedEvent")
Expand Down Expand Up @@ -1354,6 +1450,7 @@ class PhysicsSystem extends WorldSystem {
// Destroy Jolt Bodies.
this.destroyBodyIds(...this._bodies)
this._bodies = []
this._sphereGamePieceBodies = []

JOLT.destroy(this._joltBodyInterface)
JOLT.destroy(this._joltInterface)
Expand Down
Loading