Skip to content
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

Add basic physics joint component include FixedJoint, SpringJoint, HingeJoint #853

Merged
merged 16 commits into from
Jul 6, 2022
2 changes: 1 addition & 1 deletion packages/core/src/physics/PhysicsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { HitResult } from "./HitResult";
import { ColliderShape } from "./shape";

/**
* A physics manager is a collection of bodies and constraints which can interact.
* A physics manager is a collection of colliders and constraints which can interact.
*/
export class PhysicsManager {
/** @internal */
Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/physics/enums/HingeJointFlag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Flags specific to the Hinge Joint.
* @internal
*/
export enum HingeJointFlag {
yangfengzzz marked this conversation as resolved.
Show resolved Hide resolved
/** enable the limit */
LimitEnabled = 1,
/** enable the drive */
DriveEnabled = 2,
/** if the existing velocity is beyond the drive velocity, do not add force */
DriveFreeSpin = 4
}
4 changes: 4 additions & 0 deletions packages/core/src/physics/enums/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./PhysicsMaterialCombineMode";
export * from "./ColliderShapeUpAxis";
export * from "./ControllerCollisionFlag";
export * from "./ControllerNonWalkableMode";
7 changes: 2 additions & 5 deletions packages/core/src/physics/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
export { HitResult } from "./HitResult";
export { PhysicsMaterialCombineMode } from "./enums/PhysicsMaterialCombineMode";
export { ColliderShapeUpAxis } from "./enums/ColliderShapeUpAxis";
export { ControllerCollisionFlag } from "./enums/ControllerCollisionFlag";
export { ControllerNonWalkableMode } from "./enums/ControllerNonWalkableMode";

export { PhysicsManager } from "./PhysicsManager";
export { PhysicsMaterial } from "./PhysicsMaterial";
export { CharacterController } from "./CharacterController";
export * from "./shape";
export * from "./joint";
export * from "./enums";

export { Collider } from "./Collider";
export { StaticCollider } from "./StaticCollider";
Expand Down
38 changes: 38 additions & 0 deletions packages/core/src/physics/joint/FixedJoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Joint } from "./Joint";
import { PhysicsManager } from "../PhysicsManager";
import { Collider } from "../Collider";
import { Vector3 } from "@oasis-engine/math";
import { IFixedJoint } from "@oasis-engine/design";

/*
* A fixed joint permits no relative movement between two colliders. ie the colliders are glued together.
*/
export class FixedJoint extends Joint {
/**
* The connected anchor position.
* @remarks If connectedCollider is set, this anchor is relative offset.
* Or the anchor is world anchor position.
*/
get connectedAnchor(): Vector3 {
return this._connectedCollider.localPosition;
}

set connectedAnchor(value: Vector3) {
const connectedAnchor = this._connectedCollider.localPosition;
if (value !== connectedAnchor) {
connectedAnchor.copyFrom(value);
}
(<IFixedJoint>this._nativeJoint).setConnectedAnchor(value);
}

/**
* @override
* @internal
*/
_onAwake() {
const { _connectedCollider: connectedCollider, _collider: collider } = this;
connectedCollider.collider = null;
collider.collider = this.entity.getComponent(Collider);
this._nativeJoint = PhysicsManager._nativePhysics.createFixedJoint(collider.collider._nativeCollider);
}
}
164 changes: 164 additions & 0 deletions packages/core/src/physics/joint/HingeJoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { Joint } from "./Joint";
import { IHingeJoint } from "@oasis-engine/design";
import { PhysicsManager } from "../PhysicsManager";
import { HingeJointFlag } from "../enums/HingeJointFlag";
import { Collider } from "../Collider";
import { Vector3 } from "@oasis-engine/math";
import { JointMotor } from "./JointMotor";
import { JointLimits } from "./JointLimits";

/**
* A joint which behaves in a similar way to a hinge or axle.
*/
export class HingeJoint extends Joint {
private _axis: Vector3 = new Vector3(1, 0, 0);
private _hingeFlags: number = 0;
private _useSpring: boolean = false;
private _jointMonitor: JointMotor;
private _limits: JointLimits;

/**
* The anchor rotation.
*/
get axis(): Vector3 {
return this._axis;
}

set axis(value: Vector3) {
const axis = this._axis;
if (value !== axis) {
axis.copyFrom(value);
}
(<IHingeJoint>this._nativeJoint).setAxis(axis);
}

/**
* The swing offset.
*/
get swingOffset(): Vector3 {
return this._collider.localPosition;
}

set swingOffset(value: Vector3) {
const swingOffset = this._collider.localPosition;
if (value !== swingOffset) {
swingOffset.copyFrom(value);
}
(<IHingeJoint>this._nativeJoint).setSwingOffset(swingOffset);
}

/**
* The connected anchor position.
* @remarks If connectedCollider is set, this anchor is relative offset.
* Or the anchor is world anchor position.
*/
get connectedAnchor(): Vector3 {
return this._connectedCollider.localPosition;
}

set connectedAnchor(value: Vector3) {
const connectedAnchor = this._connectedCollider.localPosition;
if (value !== connectedAnchor) {
connectedAnchor.copyFrom(value);
}
(<IHingeJoint>this._nativeJoint).setConnectedAnchor(value);
}

/**
* The current angle in degrees of the joint relative to its rest position.
*/
get angle(): number {
return (<IHingeJoint>this._nativeJoint).getAngle();
}

/**
* The angular velocity of the joint in degrees per second.
*/
get velocity(): Readonly<Vector3> {
return (<IHingeJoint>this._nativeJoint).getVelocity();
}

/**
* Enables the joint's limits. Disabled by default.
*/
get useLimits(): boolean {
return (this._hingeFlags & HingeJointFlag.LimitEnabled) == HingeJointFlag.LimitEnabled;
}

set useLimits(value: boolean) {
if (value !== this.useLimits) {
this._hingeFlags |= HingeJointFlag.LimitEnabled;
}
(<IHingeJoint>this._nativeJoint).setHingeJointFlag(HingeJointFlag.LimitEnabled, value);
}

/**
* Enables the joint's motor. Disabled by default.
*/
get useMotor(): boolean {
return (this._hingeFlags & HingeJointFlag.DriveEnabled) == HingeJointFlag.DriveEnabled;
}

set useMotor(value: boolean) {
if (value !== this.useMotor) {
this._hingeFlags |= HingeJointFlag.DriveEnabled;
}
(<IHingeJoint>this._nativeJoint).setHingeJointFlag(HingeJointFlag.DriveEnabled, value);
}

/**
* Enables the joint's spring. Disabled by default.
*/
get useSpring(): boolean {
return this._useSpring;
}

set useSpring(value: boolean) {
this._useSpring = value;
this.limits = this._limits;
}

/**
* The motor will apply a force up to a maximum force to achieve the target velocity in degrees per second.
*/
get motor(): JointMotor {
return this._jointMonitor;
}

set motor(value: JointMotor) {
this._jointMonitor = value;
(<IHingeJoint>this._nativeJoint).setDriveVelocity(value.targetVelocity);
(<IHingeJoint>this._nativeJoint).setDriveForceLimit(value.forceLimit);
(<IHingeJoint>this._nativeJoint).setDriveGearRatio(value.gearRation);
(<IHingeJoint>this._nativeJoint).setHingeJointFlag(HingeJointFlag.DriveFreeSpin, value.freeSpin);
}

/**
* Limit of angular rotation (in degrees) on the hinge joint.
*/
get limits(): JointLimits {
return this._limits;
}

set limits(value: JointLimits) {
this._limits = value;
if (this.useSpring) {
(<IHingeJoint>this._nativeJoint).setSoftLimit(value.min, value.max, value.stiffness, value.damping);
} else {
(<IHingeJoint>this._nativeJoint).setHardLimit(value.min, value.max, value.contactDistance);
}
yangfengzzz marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @override
* @internal
*/
_onAwake() {
const { _connectedCollider: connectedCollider, _collider: collider } = this;
connectedCollider.localPosition = new Vector3();
connectedCollider.collider = null;
GuoLei1990 marked this conversation as resolved.
Show resolved Hide resolved
collider.localPosition = new Vector3();
collider.collider = this.entity.getComponent(Collider);
this._nativeJoint = PhysicsManager._nativePhysics.createHingeJoint(collider.collider._nativeCollider);
}
}
117 changes: 117 additions & 0 deletions packages/core/src/physics/joint/Joint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { IJoint } from "@oasis-engine/design";
import { Vector3, Quaternion } from "@oasis-engine/math";
import { Component } from "../../Component";
import { Collider } from "../Collider";
import { dependentComponents } from "../../ComponentsDependencies";

/**
* A base class providing common functionality for joints.
* @decorator `@dependentComponents(Collider)`
*/
@dependentComponents(Collider)
export class Joint extends Component {
protected _connectedCollider = new JointCollider();
protected _collider = new JointCollider();
protected _nativeJoint: IJoint;
private _force: number = 0;
private _torque: number = 0;
private _flags: number = 0;

/**
* The connected collider.
*/
get connectedCollider(): Collider {
return this._connectedCollider.collider;
}

set connectedCollider(value: Collider) {
const collider = this._connectedCollider.collider;
if (collider !== value) {
this._connectedCollider.collider = value;
this._nativeJoint.setConnectedCollider(value._nativeCollider);
}
}

/**
* The scale to apply to the inverse mass of collider 0 for resolving this constraint.
*/
get connectedMassScale(): number {
return this._connectedCollider.massScale;
}

set connectedMassScale(value: number) {
this._connectedCollider.massScale = value;
this._nativeJoint.setConnectedMassScale(value);
}

/**
* The scale to apply to the inverse inertia of collider0 for resolving this constraint.
*/
get connectedInertiaScale(): number {
return this._connectedCollider.inertiaScale;
}

set connectedInertiaScale(value: number) {
this._connectedCollider.inertiaScale = value;
this._nativeJoint.setConnectedInertiaScale(value);
}

/**
* The scale to apply to the inverse mass of collider 1 for resolving this constraint.
*/
get massScale(): number {
return this._collider.massScale;
}

set massScale(value: number) {
this._collider.massScale = value;
this._nativeJoint.setMassScale(value);
}

/**
* The scale to apply to the inverse inertia of collider1 for resolving this constraint.
*/
get inertiaScale(): number {
return this._collider.inertiaScale;
}

set inertiaScale(value: number) {
this._collider.inertiaScale = value;
this._nativeJoint.setInertiaScale(value);
}

/**
* The maximum force the joint can apply before breaking.
*/
get breakForce(): number {
return this._force;
}

set breakForce(value: number) {
this._force = value;
this._nativeJoint.setBreakForce(value);
}

/**
* The maximum torque the joint can apply before breaking.
*/
get breakTorque(): number {
return this._torque;
}

set breakTorque(value: number) {
this._torque = value;
this._nativeJoint.setBreakTorque(value);
}
}

/**
* @internal
*/
class JointCollider {
collider: Collider = null;
localPosition: Vector3;
localRotation: Quaternion;
massScale: number = 0;
inertiaScale: number = 0;
}
16 changes: 16 additions & 0 deletions packages/core/src/physics/joint/JointLimits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* JointLimits is used to limit the joints angle.
*/
export class JointLimits {
/** The upper angular limit (in degrees) of the joint. */
max: number = 0;
/** The lower angular limit (in degrees) of the joint. */
min: number = 0;
/** Distance inside the limit value at which the limit will be considered to be active by the solver. */
contactDistance: number = -1;

/** The spring forces used to reach the target position. */
stiffness: number = 0;
/** The damper force uses to dampen the spring. */
damping: number = 0;
}
13 changes: 13 additions & 0 deletions packages/core/src/physics/joint/JointMotor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* The JointMotor is used to motorize a joint.
*/
export class JointMotor {
/** The motor will apply a force up to force to achieve targetVelocity. */
targetVelocity: number = 0;
/** The force limit.*/
forceLimit: number = Number.MAX_VALUE;
/** Gear ration for the motor */
gearRation: number = 1.0;
/** If freeSpin is enabled the motor will only accelerate but never slow down. */
freeSpin: boolean = false;
}
Loading