@perplexdotgg/bounce 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +30 -0
- package/package.json +32 -0
- package/src/builders/ConvexHullBuilder.ts +437 -0
- package/src/builders/ConvexHullBuilder2d.ts +344 -0
- package/src/builders/ConvexHullBuilder3d.ts +1689 -0
- package/src/builders/HeightMapBuilder.ts +414 -0
- package/src/builders/TriangleMeshBuilder.ts +92 -0
- package/src/collision/CastShapesModule.ts +184 -0
- package/src/collision/CollideShapesModule.ts +152 -0
- package/src/collision/HeightMapCaster.ts +38 -0
- package/src/collision/HeightMapCollider.ts +33 -0
- package/src/collision/TriangleCaster.ts +249 -0
- package/src/collision/TriangleCollider.ts +308 -0
- package/src/collision/TriangleCollider2.ts +379 -0
- package/src/collision/activeEdge.ts +146 -0
- package/src/collision/cast/cast.ts +139 -0
- package/src/collision/cast/castCompoundVsCompound.ts +59 -0
- package/src/collision/cast/castCompoundVsConvex.ts +116 -0
- package/src/collision/cast/castConvexVsCompound.ts +123 -0
- package/src/collision/cast/castConvexVsConvex.ts +213 -0
- package/src/collision/cast/castConvexVsHeightMap.ts +73 -0
- package/src/collision/cast/castConvexVsTriangleMesh.ts +56 -0
- package/src/collision/cast/castRayVsCompound.ts +44 -0
- package/src/collision/cast/castRayVsConvex.ts +45 -0
- package/src/collision/cast/castRayVsHeightMap.ts +58 -0
- package/src/collision/cast/castRayVsTriangleMesh.ts +58 -0
- package/src/collision/closestPoints/closestPoints.ts +23 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates2d.ts +32 -0
- package/src/collision/closestPoints/computeBarycentricCoordinates3d.ts +81 -0
- package/src/collision/closestPoints/computeClosestPointOnLine.ts +30 -0
- package/src/collision/closestPoints/computeClosestPointOnTetrahedron.ts +96 -0
- package/src/collision/closestPoints/computeClosestPointOnTriangle.ts +195 -0
- package/src/collision/closestPoints/isOriginOutsideOfPlane.ts +25 -0
- package/src/collision/closestPoints/isOriginOutsideOfTrianglePlanes.ts +72 -0
- package/src/collision/collide/collide.ts +146 -0
- package/src/collision/collide/collideCompoundVsCompound.ts +60 -0
- package/src/collision/collide/collideCompoundVsConvex.ts +59 -0
- package/src/collision/collide/collideCompoundVsHeightMap.ts +73 -0
- package/src/collision/collide/collideCompoundVsTriangleMesh.ts +56 -0
- package/src/collision/collide/collideConvexVsCompound.ts +57 -0
- package/src/collision/collide/collideConvexVsConvex.ts +225 -0
- package/src/collision/collide/collideConvexVsConvexImp.ts +236 -0
- package/src/collision/collide/collideConvexVsHeightMap.ts +53 -0
- package/src/collision/collide/collideConvexVsTriangleMesh.ts +58 -0
- package/src/collision/collide/collideHeightMapVsCompound.ts +69 -0
- package/src/collision/collide/collideHeightMapVsConvex.ts +53 -0
- package/src/collision/collide/collideSphereVsSphere.ts +81 -0
- package/src/collision/collide/collideTriangleMeshVsCompound.ts +58 -0
- package/src/collision/collide/collideTriangleMeshVsConvex.ts +58 -0
- package/src/collision/epa/EpaConvexHullBuilder.ts +397 -0
- package/src/collision/epa/StaticArray.ts +154 -0
- package/src/collision/epa/TriangleFactory.ts +32 -0
- package/src/collision/epa/arrays.ts +99 -0
- package/src/collision/epa/binaryHeap.ts +82 -0
- package/src/collision/epa/structs.ts +227 -0
- package/src/collision/gjk/GjkModule.ts +864 -0
- package/src/collision/gjk/PenetrationDepthModule.ts +493 -0
- package/src/collision/gjk/SupportPoints.ts +50 -0
- package/src/collision/imp/MinkowskiDifference.ts +36 -0
- package/src/collision/imp/computeExploredDistanceLowerUpperBound.ts +40 -0
- package/src/collision/imp/finalizeImpResult.ts +69 -0
- package/src/collision/imp/findContactImp.ts +196 -0
- package/src/collision/imp/imp.ts +28 -0
- package/src/collision/imp/incrementalMinimumDistanceExploreDirection.ts +207 -0
- package/src/collision/mpr/findPortal.ts +152 -0
- package/src/collision/mpr/mpr.ts +29 -0
- package/src/collision/mpr/updatePortal.ts +52 -0
- package/src/constraints/BaseConstraint.ts +50 -0
- package/src/constraints/ConstraintOptions.ts +22 -0
- package/src/constraints/ConstraintSolver.ts +119 -0
- package/src/constraints/DistanceConstraint.ts +229 -0
- package/src/constraints/FixedConstraint.ts +203 -0
- package/src/constraints/HingeConstraint.ts +460 -0
- package/src/constraints/PointConstraint.ts +108 -0
- package/src/constraints/components/AngleComponent.ts +226 -0
- package/src/constraints/components/AxisComponent.ts +263 -0
- package/src/constraints/components/HingeComponent.ts +215 -0
- package/src/constraints/components/Motor.ts +36 -0
- package/src/constraints/components/PointConstraintComponent.ts +179 -0
- package/src/constraints/components/RotationEulerComponent.ts +139 -0
- package/src/constraints/components/Spring.ts +30 -0
- package/src/constraints/components/SpringComponent.ts +71 -0
- package/src/constraints/types.ts +6 -0
- package/src/helpers.ts +147 -0
- package/src/index.ts +50 -0
- package/src/math/BasicTransform.ts +19 -0
- package/src/math/NumberValue.ts +13 -0
- package/src/math/isometry.ts +64 -0
- package/src/math/mat3.ts +529 -0
- package/src/math/mat4.ts +588 -0
- package/src/math/quat.ts +193 -0
- package/src/math/scalar.ts +81 -0
- package/src/math/tensor.ts +17 -0
- package/src/math/vec3.ts +589 -0
- package/src/math/vec4.ts +10 -0
- package/src/physics/Body.ts +581 -0
- package/src/physics/CollisionFilter.ts +52 -0
- package/src/physics/SleepModule.ts +163 -0
- package/src/physics/broadphase/BodyPairsModule.ts +363 -0
- package/src/physics/broadphase/BvhModule.ts +237 -0
- package/src/physics/broadphase/BvhTree.ts +803 -0
- package/src/physics/broadphase/ConstraintPairsModule.ts +385 -0
- package/src/physics/broadphase/TriangleMeshBvhTree.ts +379 -0
- package/src/physics/manifold/ContactManifold.ts +227 -0
- package/src/physics/manifold/ContactManifoldModule.ts +623 -0
- package/src/physics/manifold/Face.ts +119 -0
- package/src/physics/manifold/ManifoldCache.ts +116 -0
- package/src/physics/manifold/clipping/clipPolyVsEdge.ts +131 -0
- package/src/physics/manifold/clipping/clipPolyVsPlane.ts +73 -0
- package/src/physics/manifold/clipping/clipPolyVsPoly.ts +72 -0
- package/src/physics/narrowphase/CollideBodiesModule.ts +755 -0
- package/src/physics/solver/ContactConstraintModule.ts +659 -0
- package/src/physics/solver/ManifoldConstraint.ts +420 -0
- package/src/physics/solver/estimateCollisionResponse.ts +146 -0
- package/src/shape/Aabb.ts +400 -0
- package/src/shape/Box.ts +231 -0
- package/src/shape/Capsule.ts +332 -0
- package/src/shape/CompoundShape.ts +288 -0
- package/src/shape/Convex.ts +130 -0
- package/src/shape/ConvexHull.ts +423 -0
- package/src/shape/Cylinder.ts +313 -0
- package/src/shape/HeightMap.ts +511 -0
- package/src/shape/Line.ts +14 -0
- package/src/shape/Plane.ts +116 -0
- package/src/shape/Ray.ts +81 -0
- package/src/shape/Segment.ts +25 -0
- package/src/shape/Shape.ts +77 -0
- package/src/shape/Sphere.ts +181 -0
- package/src/shape/TransformedShape.ts +51 -0
- package/src/shape/Triangle.ts +122 -0
- package/src/shape/TriangleMesh.ts +186 -0
- package/src/types.ts +1 -0
- package/src/world.ts +1335 -0
- package/tests/BodyPairsModule.test.ts +71 -0
- package/tests/BvhTree.test.ts +406 -0
- package/tests/test.md +642 -0
- package/tests/vec3.test.ts +12 -0
- package/tsconfig.json +20 -0
- package/vite.config.js +40 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createClass, MonomorphType, NumberType, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
import { Spring, SpringMode } from "./Spring";
|
|
3
|
+
|
|
4
|
+
export const enum MotorMode {
|
|
5
|
+
Off,
|
|
6
|
+
Position,
|
|
7
|
+
Velocity,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const motorProps = {
|
|
11
|
+
spring: MonomorphType(Spring, { mode: SpringMode.UseFrequency, damping: 1.0, frequency: 2.0, stiffness: 2.0 }),
|
|
12
|
+
minForce: NumberType(-Infinity),
|
|
13
|
+
maxForce: NumberType(+Infinity),
|
|
14
|
+
minTorque: NumberType(-Infinity),
|
|
15
|
+
maxTorque: NumberType(+Infinity),
|
|
16
|
+
mode: NumberType(MotorMode.Off),
|
|
17
|
+
} as const satisfies PropertyDefinitionMap;
|
|
18
|
+
|
|
19
|
+
export class Motor extends createClass<Motor, typeof motorProps>(motorProps) {
|
|
20
|
+
reset(): void {
|
|
21
|
+
this.spring.reset();
|
|
22
|
+
this.spring.mode = SpringMode.UseFrequency;
|
|
23
|
+
this.spring.damping = 1.0;
|
|
24
|
+
this.spring.frequency = 2.0;
|
|
25
|
+
this.spring.stiffness = 2.0;
|
|
26
|
+
this.minForce = -Infinity;
|
|
27
|
+
this.maxForce = +Infinity;
|
|
28
|
+
this.minTorque = -Infinity;
|
|
29
|
+
this.maxTorque = +Infinity;
|
|
30
|
+
this.mode = MotorMode.Off;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
isValid(): boolean {
|
|
34
|
+
return this.spring.isValid() && this.minForce <= this.maxForce && this.minTorque <= this.maxTorque;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClass,
|
|
3
|
+
LazyReferenceType,
|
|
4
|
+
MonomorphType,
|
|
5
|
+
PropertyDefinitionMap,
|
|
6
|
+
PropertyDefinitionReference,
|
|
7
|
+
} from "monomorph";
|
|
8
|
+
import { ConstraintOptions } from "../ConstraintOptions";
|
|
9
|
+
import { Vec3 } from "../../math/vec3";
|
|
10
|
+
import { Mat3 } from "../../math/mat3";
|
|
11
|
+
import type { World } from "../../world";
|
|
12
|
+
import { Body, BodyType } from "../../physics/Body";
|
|
13
|
+
|
|
14
|
+
const pointConstraintComponentProps = {
|
|
15
|
+
bodyA: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
|
|
16
|
+
bodyB: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
|
|
17
|
+
options: MonomorphType(ConstraintOptions, {
|
|
18
|
+
positionBaumgarte: 0.8,
|
|
19
|
+
velocityBaumgarte: 1.0,
|
|
20
|
+
strength: 1.0,
|
|
21
|
+
}),
|
|
22
|
+
momentArmA: MonomorphType(Vec3, undefined, true),
|
|
23
|
+
momentArmB: MonomorphType(Vec3, undefined, true),
|
|
24
|
+
effectiveMass: MonomorphType(Mat3, undefined, true),
|
|
25
|
+
effectiveInverseInertiaA: MonomorphType(Mat3, undefined, true),
|
|
26
|
+
effectiveInverseInertiaB: MonomorphType(Mat3, undefined, true),
|
|
27
|
+
totalLambda: MonomorphType(Vec3, undefined, true),
|
|
28
|
+
} as const satisfies PropertyDefinitionMap;
|
|
29
|
+
|
|
30
|
+
export class PointConstraintComponent extends createClass<
|
|
31
|
+
PointConstraintComponent,
|
|
32
|
+
typeof pointConstraintComponentProps
|
|
33
|
+
>(pointConstraintComponentProps) {
|
|
34
|
+
declare world: World;
|
|
35
|
+
|
|
36
|
+
deactivate(): void {
|
|
37
|
+
this.effectiveMass.zero();
|
|
38
|
+
this.totalLambda.zero();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
isActive(): boolean {
|
|
42
|
+
// TODO: should probably just track using an isActive flag
|
|
43
|
+
return !this.effectiveMass.isExactlyZero();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
applyDirectImpulse(impulse: Vec3): void {
|
|
47
|
+
if (impulse.isExactlyZero()) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const bodyA = this.bodyA!;
|
|
52
|
+
const bodyB = this.bodyB!;
|
|
53
|
+
|
|
54
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
55
|
+
bodyA.linearVelocity.subScaledVector(impulse, bodyA.inverseMass);
|
|
56
|
+
bodyA.angularVelocity.subRotatedVector(impulse, this.effectiveInverseInertiaA);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
60
|
+
bodyB.linearVelocity.addScaledVector(impulse, bodyB.inverseMass);
|
|
61
|
+
bodyB.angularVelocity.addRotatedVector(impulse, this.effectiveInverseInertiaB);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setup(localMomentArmA: Vec3, localMomentArmB: Vec3): void {
|
|
66
|
+
const bodyA = this.bodyA!;
|
|
67
|
+
const bodyB = this.bodyB!;
|
|
68
|
+
|
|
69
|
+
rotateLocalToWorldA.fromQuat(bodyA!.orientation);
|
|
70
|
+
rotateLocalToWorldB.fromQuat(bodyB!.orientation);
|
|
71
|
+
|
|
72
|
+
// calculate the moment arms in world space
|
|
73
|
+
this.momentArmA.transformVectorFromMat3(localMomentArmA, rotateLocalToWorldA);
|
|
74
|
+
this.momentArmB.transformVectorFromMat3(localMomentArmB, rotateLocalToWorldB);
|
|
75
|
+
|
|
76
|
+
// calculate the effective mass
|
|
77
|
+
let totalInverseMass = 0;
|
|
78
|
+
|
|
79
|
+
inverseEffectiveMass.zero();
|
|
80
|
+
|
|
81
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
82
|
+
totalInverseMass += bodyA.inverseMass;
|
|
83
|
+
|
|
84
|
+
bodyA.computeInverseInertiaTensor(inverseInertia);
|
|
85
|
+
crossMatrix.asSkewSymmetricMatrix(this.momentArmA);
|
|
86
|
+
this.effectiveInverseInertiaA.multiplyMatrices(inverseInertia, crossMatrix);
|
|
87
|
+
tempMatrix.multiplyMatrices(crossMatrix, inverseInertia);
|
|
88
|
+
tempMatrix.multiplyMat3RightTransposed(crossMatrix);
|
|
89
|
+
inverseEffectiveMass.addMatrices(inverseEffectiveMass, tempMatrix);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
93
|
+
totalInverseMass += bodyB.inverseMass;
|
|
94
|
+
|
|
95
|
+
bodyB.computeInverseInertiaTensor(inverseInertia);
|
|
96
|
+
crossMatrix.asSkewSymmetricMatrix(this.momentArmB);
|
|
97
|
+
this.effectiveInverseInertiaB.multiplyMatrices(inverseInertia, crossMatrix);
|
|
98
|
+
tempMatrix.multiplyMatrices(crossMatrix, inverseInertia);
|
|
99
|
+
tempMatrix.multiplyMat3RightTransposed(crossMatrix);
|
|
100
|
+
inverseEffectiveMass.addMatrices(inverseEffectiveMass, tempMatrix);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// add the inverse mass
|
|
104
|
+
|
|
105
|
+
totalInverseMassMatrix.identity();
|
|
106
|
+
totalInverseMassMatrix.multiplyMatrixByScalar(totalInverseMassMatrix, totalInverseMass);
|
|
107
|
+
inverseEffectiveMass.addMatrices(inverseEffectiveMass, totalInverseMassMatrix);
|
|
108
|
+
|
|
109
|
+
const isValid = this.effectiveMass.invertMat3(inverseEffectiveMass);
|
|
110
|
+
if (isValid === null) {
|
|
111
|
+
this.deactivate();
|
|
112
|
+
}
|
|
113
|
+
this.effectiveMass.multiplyByScalar(this.options.strength);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
warmStart(warmStartImpulseRatio: number): void {
|
|
117
|
+
this.totalLambda.scale(warmStartImpulseRatio);
|
|
118
|
+
this.applyDirectImpulse(this.totalLambda);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
solveVelocity(): void {
|
|
122
|
+
const bodyA = this.bodyA!;
|
|
123
|
+
const bodyB = this.bodyB!;
|
|
124
|
+
|
|
125
|
+
// compute the partial lambda for current velocity
|
|
126
|
+
bodyA.computePointVelocity(pointVelocityA, this.momentArmA);
|
|
127
|
+
bodyB.computePointVelocity(pointVelocityB, this.momentArmB);
|
|
128
|
+
lambda.subtractVectors(pointVelocityA, pointVelocityB);
|
|
129
|
+
lambda.scale(this.options.velocityBaumgarte);
|
|
130
|
+
lambda.transformVectorFromMat3(lambda, this.effectiveMass);
|
|
131
|
+
|
|
132
|
+
// accumulate the lambda
|
|
133
|
+
this.totalLambda.addVector(lambda);
|
|
134
|
+
|
|
135
|
+
// apply the lambda
|
|
136
|
+
this.applyDirectImpulse(lambda);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
solvePosition(): void {
|
|
140
|
+
const bodyA = this.bodyA!;
|
|
141
|
+
const bodyB = this.bodyB!;
|
|
142
|
+
|
|
143
|
+
separation.subtractVectors(bodyB.computedCenterOfMassPosition, bodyA.computedCenterOfMassPosition);
|
|
144
|
+
separation.addVector(this.momentArmB);
|
|
145
|
+
separation.subtractVector(this.momentArmA);
|
|
146
|
+
|
|
147
|
+
// case: no separation
|
|
148
|
+
if (separation.isNearZero(1e-18)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// case: separation
|
|
153
|
+
|
|
154
|
+
lambda.scaleVector(separation, -this.options.positionBaumgarte);
|
|
155
|
+
lambda.transformVectorFromMat3(lambda, this.effectiveMass);
|
|
156
|
+
|
|
157
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
158
|
+
bodyA.computedCenterOfMassPosition.subScaledVector(lambda, bodyA.inverseMass);
|
|
159
|
+
bodyA.subRotationPositionImpulse(lambda, this.effectiveInverseInertiaA);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
163
|
+
bodyB.computedCenterOfMassPosition.addScaledVector(lambda, bodyB.inverseMass);
|
|
164
|
+
bodyB.addRotationPositionImpulse(lambda, this.effectiveInverseInertiaB);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const rotateLocalToWorldA = /*@__PURE__*/ Mat3.create();
|
|
170
|
+
const rotateLocalToWorldB = /*@__PURE__*/ Mat3.create();
|
|
171
|
+
const inverseEffectiveMass = /*@__PURE__*/ Mat3.create();
|
|
172
|
+
const inverseInertia = /*@__PURE__*/ Mat3.create();
|
|
173
|
+
const crossMatrix = /*@__PURE__*/ Mat3.create();
|
|
174
|
+
const tempMatrix = /*@__PURE__*/ Mat3.create();
|
|
175
|
+
const totalInverseMassMatrix = /*@__PURE__*/ Mat3.create();
|
|
176
|
+
const pointVelocityA = /*@__PURE__*/ Vec3.create();
|
|
177
|
+
const pointVelocityB = /*@__PURE__*/ Vec3.create();
|
|
178
|
+
const separation = /*@__PURE__*/ Vec3.create();
|
|
179
|
+
const lambda = /*@__PURE__*/ Vec3.create();
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClass,
|
|
3
|
+
LazyReferenceType,
|
|
4
|
+
MonomorphType,
|
|
5
|
+
PropertyDefinitionMap,
|
|
6
|
+
PropertyDefinitionReference,
|
|
7
|
+
} from "monomorph";
|
|
8
|
+
import { ConstraintOptions } from "../ConstraintOptions";
|
|
9
|
+
import { Mat3 } from "../../math/mat3";
|
|
10
|
+
import { Vec3 } from "../../math/vec3";
|
|
11
|
+
import { Body, BodyType } from "../../physics/Body";
|
|
12
|
+
import type { World } from "../../world";
|
|
13
|
+
import { Quat } from "../../math/quat";
|
|
14
|
+
|
|
15
|
+
const rotationEulerComponentProps = {
|
|
16
|
+
bodyA: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
|
|
17
|
+
bodyB: LazyReferenceType((() => Body) as () => never) as PropertyDefinitionReference<Body | null, true>,
|
|
18
|
+
options: MonomorphType(ConstraintOptions, {
|
|
19
|
+
positionBaumgarte: 0.8,
|
|
20
|
+
velocityBaumgarte: 1.0,
|
|
21
|
+
strength: 1.0,
|
|
22
|
+
}),
|
|
23
|
+
mInvI1: MonomorphType(Mat3, undefined, true),
|
|
24
|
+
mInvI2: MonomorphType(Mat3, undefined, true),
|
|
25
|
+
effectiveMassRotationComponent: MonomorphType(Mat3, undefined, true),
|
|
26
|
+
totalLambdaRotationComponent: MonomorphType(Vec3, undefined, true),
|
|
27
|
+
} as const satisfies PropertyDefinitionMap;
|
|
28
|
+
|
|
29
|
+
export class RotationEulerComponent extends createClass<RotationEulerComponent, typeof rotationEulerComponentProps>(
|
|
30
|
+
rotationEulerComponentProps
|
|
31
|
+
) {
|
|
32
|
+
declare world: World;
|
|
33
|
+
|
|
34
|
+
deactivate(): void {
|
|
35
|
+
this.effectiveMassRotationComponent.zero();
|
|
36
|
+
this.totalLambdaRotationComponent.zero();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
isActive(): boolean {
|
|
40
|
+
return !this.effectiveMassRotationComponent.isExactlyZero();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
applyDirectImpulse(impulse: Vec3): void {
|
|
44
|
+
if (impulse.isExactlyZero()) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bodyA = this.bodyA!;
|
|
49
|
+
const bodyB = this.bodyB!;
|
|
50
|
+
|
|
51
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
52
|
+
bodyA.angularVelocity.subRotatedVector(impulse, this.mInvI1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
56
|
+
bodyB.angularVelocity.addRotatedVector(impulse, this.mInvI2);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setup(): void {
|
|
61
|
+
const bodyA = this.bodyA!;
|
|
62
|
+
const bodyB = this.bodyB!;
|
|
63
|
+
|
|
64
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
65
|
+
bodyA.computeInverseInertiaTensor(this.mInvI1);
|
|
66
|
+
} else {
|
|
67
|
+
this.mInvI1.zero();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
71
|
+
bodyB.computeInverseInertiaTensor(this.mInvI2);
|
|
72
|
+
} else {
|
|
73
|
+
this.mInvI2.zero();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.effectiveMassRotationComponent.addMatrices(this.mInvI1, this.mInvI2);
|
|
77
|
+
const isValid = this.effectiveMassRotationComponent.invertMat3(this.effectiveMassRotationComponent);
|
|
78
|
+
if (isValid === null) {
|
|
79
|
+
this.deactivate();
|
|
80
|
+
}
|
|
81
|
+
this.effectiveMassRotationComponent.multiplyByScalar(this.options.strength);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
warmStart(warmStartImpulseRatio: number): void {
|
|
85
|
+
this.totalLambdaRotationComponent.scale(warmStartImpulseRatio);
|
|
86
|
+
this.applyDirectImpulse(this.totalLambdaRotationComponent);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
solveVelocity(): void {
|
|
90
|
+
const bodyA = this.bodyA!;
|
|
91
|
+
const bodyB = this.bodyB!;
|
|
92
|
+
|
|
93
|
+
// compute the partial lambda for current velocity
|
|
94
|
+
lambda.subtractVectors(bodyA!.angularVelocity, bodyB!.angularVelocity);
|
|
95
|
+
lambda.scale(this.options.velocityBaumgarte);
|
|
96
|
+
lambda.transformVectorFromMat3(lambda, this.effectiveMassRotationComponent);
|
|
97
|
+
|
|
98
|
+
this.totalLambdaRotationComponent.addVector(lambda);
|
|
99
|
+
this.applyDirectImpulse(lambda);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
solvePosition(inverseInitialRotationAToB: Quat): void {
|
|
103
|
+
const bodyA = this.bodyA!;
|
|
104
|
+
const bodyB = this.bodyB!;
|
|
105
|
+
|
|
106
|
+
inverseRotationA.conjugateQuat(bodyA.orientation);
|
|
107
|
+
|
|
108
|
+
quaternionError.multiplyQuats(bodyB.orientation, inverseInitialRotationAToB);
|
|
109
|
+
quaternionError.multiplyQuats(quaternionError, inverseRotationA);
|
|
110
|
+
|
|
111
|
+
// TODO: this only works if error is small, handle large errors
|
|
112
|
+
quaternionError.intoEuler(eulerError);
|
|
113
|
+
|
|
114
|
+
if (eulerError.isNearZero(1e-18)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lambda.scaleVector(eulerError, -this.options.positionBaumgarte);
|
|
119
|
+
lambda.transformVectorFromMat3(lambda, this.effectiveMassRotationComponent);
|
|
120
|
+
|
|
121
|
+
if (bodyA.type === BodyType.dynamic) {
|
|
122
|
+
angularImpulse.transformVectorFromMat3(lambda, this.mInvI1);
|
|
123
|
+
angularImpulse.scaleVector(angularImpulse, 1);
|
|
124
|
+
bodyA.subRotationDelta(angularImpulse);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (bodyB.type === BodyType.dynamic) {
|
|
128
|
+
angularImpulse.transformVectorFromMat3(lambda, this.mInvI2);
|
|
129
|
+
angularImpulse.scaleVector(angularImpulse, 1);
|
|
130
|
+
bodyB.addRotationDelta(angularImpulse);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const lambda = /*@__PURE__*/ Vec3.create();
|
|
136
|
+
const quaternionError = /*@__PURE__*/ Quat.create();
|
|
137
|
+
const inverseRotationA = /*@__PURE__*/ Quat.create();
|
|
138
|
+
const eulerError = /*@__PURE__*/ Vec3.create();
|
|
139
|
+
const angularImpulse = /*@__PURE__*/ Vec3.create();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createClass, NumberType, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
|
|
3
|
+
export const enum SpringMode {
|
|
4
|
+
UseFrequency,
|
|
5
|
+
UseStiffness,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const springProps = {
|
|
9
|
+
mode: NumberType(SpringMode.UseFrequency),
|
|
10
|
+
damping: NumberType(0),
|
|
11
|
+
frequency: NumberType(0),
|
|
12
|
+
stiffness: NumberType(0),
|
|
13
|
+
} as const satisfies PropertyDefinitionMap;
|
|
14
|
+
|
|
15
|
+
export class Spring extends createClass<Spring, typeof springProps>(springProps) {
|
|
16
|
+
reset(): void {
|
|
17
|
+
this.mode = SpringMode.UseFrequency;
|
|
18
|
+
this.frequency = 0;
|
|
19
|
+
this.damping = 0;
|
|
20
|
+
this.stiffness = 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
hasStiffness(): boolean {
|
|
24
|
+
return this.frequency > 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isValid(): boolean {
|
|
28
|
+
return this.frequency >= 0 && this.damping >= 0 && this.stiffness >= 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createClass, NumberType, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
import { squared } from "../../math/scalar";
|
|
3
|
+
|
|
4
|
+
const springComponentProps = {
|
|
5
|
+
bias: NumberType(0),
|
|
6
|
+
softness: NumberType(0),
|
|
7
|
+
} as const satisfies PropertyDefinitionMap;
|
|
8
|
+
|
|
9
|
+
export class SpringComponent extends createClass<SpringComponent, typeof springComponentProps>(springComponentProps) {
|
|
10
|
+
getBias(lambda: number): number {
|
|
11
|
+
return this.softness * lambda + this.bias;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
isActive(): boolean {
|
|
15
|
+
return this.softness !== 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setupSpringProperties(
|
|
19
|
+
deltaTime: number,
|
|
20
|
+
inverseEffectiveMass: number,
|
|
21
|
+
bias: number,
|
|
22
|
+
constraint: number,
|
|
23
|
+
stiffness: number,
|
|
24
|
+
damping: number
|
|
25
|
+
): number {
|
|
26
|
+
this.softness = 1 / (deltaTime * (damping * deltaTime * stiffness));
|
|
27
|
+
this.bias = bias + deltaTime * stiffness * this.softness * constraint;
|
|
28
|
+
return 1 / (inverseEffectiveMass + this.softness);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setupSpringPropertiesWithBias(bias: number): void {
|
|
32
|
+
this.bias = bias;
|
|
33
|
+
this.softness = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setupWithFrequency(
|
|
37
|
+
deltaTime: number,
|
|
38
|
+
inverseEffectiveMass: number,
|
|
39
|
+
bias: number,
|
|
40
|
+
constraint: number,
|
|
41
|
+
frequency: number,
|
|
42
|
+
damping: number
|
|
43
|
+
): number {
|
|
44
|
+
const effectiveMass = 1 / inverseEffectiveMass;
|
|
45
|
+
if (frequency > 0) {
|
|
46
|
+
const omega = 2 * Math.PI * frequency;
|
|
47
|
+
const k = effectiveMass * squared(omega);
|
|
48
|
+
const c = 2 * effectiveMass * damping * omega;
|
|
49
|
+
return this.setupSpringProperties(deltaTime, inverseEffectiveMass, bias, constraint, k, c);
|
|
50
|
+
} else {
|
|
51
|
+
this.setupSpringPropertiesWithBias(bias);
|
|
52
|
+
return effectiveMass;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setupWithStiffness(
|
|
57
|
+
deltaTime: number,
|
|
58
|
+
inverseEffectiveMass: number,
|
|
59
|
+
bias: number,
|
|
60
|
+
constraint: number,
|
|
61
|
+
stiffness: number,
|
|
62
|
+
damping: number
|
|
63
|
+
): number {
|
|
64
|
+
if (stiffness > 0) {
|
|
65
|
+
return this.setupSpringProperties(deltaTime, inverseEffectiveMass, bias, constraint, stiffness, damping);
|
|
66
|
+
} else {
|
|
67
|
+
this.setupSpringPropertiesWithBias(bias);
|
|
68
|
+
return 1 / inverseEffectiveMass;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DistanceConstraint } from "./DistanceConstraint";
|
|
2
|
+
import { FixedConstraint } from "./FixedConstraint";
|
|
3
|
+
import { HingeConstraint } from "./HingeConstraint";
|
|
4
|
+
import { PointConstraint } from "./PointConstraint";
|
|
5
|
+
|
|
6
|
+
export type Constraint = PointConstraint | DistanceConstraint | FixedConstraint | HingeConstraint;
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Monomorph, MonomorphInstance, PoolClass, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
|
|
3
|
+
export function assert(condition: boolean, message: string) {
|
|
4
|
+
if (!condition) {
|
|
5
|
+
throw new Error(message);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function destroyAllInstancesInPool<T extends PoolClass<MonomorphInstance<P, I>>, P extends PropertyDefinitionMap = T extends PoolClass<MonomorphInstance<infer iP, infer iI>> ? iP : never, I extends any = T extends PoolClass<MonomorphInstance<infer iP, infer iI>> ? iI : never>(pool: T) {
|
|
10
|
+
for (let i = 0; i < pool.array.length; i++) {
|
|
11
|
+
const instance = pool.array[i];
|
|
12
|
+
|
|
13
|
+
if (!instance) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (instance.isDestroyed()) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
instance.destroy();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ArrayPool<T extends { index: number }> {
|
|
26
|
+
pool: PoolClass<T>;
|
|
27
|
+
array: T[];
|
|
28
|
+
_count: number;
|
|
29
|
+
_capacity: number;
|
|
30
|
+
|
|
31
|
+
constructor(theClass: any, capacity: number) {
|
|
32
|
+
this._count = 0;
|
|
33
|
+
this._capacity = capacity;
|
|
34
|
+
this.pool = new theClass.Pool(capacity);
|
|
35
|
+
|
|
36
|
+
// preallocate the pool with instances
|
|
37
|
+
for (let i = 0; i < capacity; i++) {
|
|
38
|
+
theClass.create(undefined, this.pool);
|
|
39
|
+
}
|
|
40
|
+
this.array = this.pool.array;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get count() {
|
|
44
|
+
return this._count;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get capacity() {
|
|
48
|
+
return this._capacity;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
create() {
|
|
52
|
+
if (this._count >= this.capacity) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const instance = this.array[this._count];
|
|
56
|
+
instance.index = this._count;
|
|
57
|
+
this._count++;
|
|
58
|
+
return instance;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
remove(instance: T) {
|
|
62
|
+
// get indices of instances to swap
|
|
63
|
+
const index = instance.index;
|
|
64
|
+
const lastIndex = this._count - 1;
|
|
65
|
+
|
|
66
|
+
if (index === lastIndex) {
|
|
67
|
+
// if the instance is the last one, just decrement the count
|
|
68
|
+
this._count--;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// swap instances
|
|
73
|
+
this.array[index] = this.array[lastIndex];
|
|
74
|
+
this.array[lastIndex] = instance;
|
|
75
|
+
|
|
76
|
+
// swap their indices
|
|
77
|
+
this.array[index].index = index;
|
|
78
|
+
this.array[lastIndex].index = lastIndex;
|
|
79
|
+
|
|
80
|
+
// decrement the count
|
|
81
|
+
this._count--;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
clear() {
|
|
85
|
+
this._count = 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function coerceVec3Argument<S extends any>(obj: S, key: keyof S) {
|
|
90
|
+
let value = obj[key] as any;
|
|
91
|
+
if (!value) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
value = { x: value[0], y: value[1], z: value[2] };
|
|
96
|
+
}
|
|
97
|
+
obj[key] = value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function coerceQuatArgument<S extends any>(obj: S, key: keyof S) {
|
|
101
|
+
let value = obj[key] as any;
|
|
102
|
+
if (!value) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(value)) {
|
|
106
|
+
if (Array.length === 3) {
|
|
107
|
+
let x = value[0] * (180 / Math.PI);
|
|
108
|
+
let y = value[1] * (180 / Math.PI);
|
|
109
|
+
let z = value[2] * (180 / Math.PI);
|
|
110
|
+
let halfToRad = Math.PI / 360;
|
|
111
|
+
x *= halfToRad;
|
|
112
|
+
z *= halfToRad;
|
|
113
|
+
y *= halfToRad;
|
|
114
|
+
let sx = Math.sin(x);
|
|
115
|
+
let cx = Math.cos(x);
|
|
116
|
+
let sy = Math.sin(y);
|
|
117
|
+
let cy = Math.cos(y);
|
|
118
|
+
let sz = Math.sin(z);
|
|
119
|
+
let cz = Math.cos(z);
|
|
120
|
+
value = { x: 0, y: 0, z: 0, w: 0 };
|
|
121
|
+
value.x = sx * cy * cz + cx * sy * sz;
|
|
122
|
+
value.y = cx * sy * cz - sx * cy * sz;
|
|
123
|
+
value.z = cx * cy * sz + sx * sy * cz;
|
|
124
|
+
value.w = cx * cy * cz - sx * sy * sz;
|
|
125
|
+
}
|
|
126
|
+
} else if (!("w" in value)) {
|
|
127
|
+
let x = value.x * (180 / Math.PI);
|
|
128
|
+
let y = value.y * (180 / Math.PI);
|
|
129
|
+
let z = value.z * (180 / Math.PI);
|
|
130
|
+
let halfToRad = Math.PI / 360;
|
|
131
|
+
x *= halfToRad;
|
|
132
|
+
z *= halfToRad;
|
|
133
|
+
y *= halfToRad;
|
|
134
|
+
let sx = Math.sin(x);
|
|
135
|
+
let cx = Math.cos(x);
|
|
136
|
+
let sy = Math.sin(y);
|
|
137
|
+
let cy = Math.cos(y);
|
|
138
|
+
let sz = Math.sin(z);
|
|
139
|
+
let cz = Math.cos(z);
|
|
140
|
+
value = { x: 0, y: 0, z: 0, w: 0 };
|
|
141
|
+
value.x = sx * cy * cz + cx * sy * sz;
|
|
142
|
+
value.y = cx * sy * cz - sx * cy * sz;
|
|
143
|
+
value.z = cx * cy * sz + sx * sy * cz;
|
|
144
|
+
value.w = cx * cy * cz - sx * sy * sz;
|
|
145
|
+
}
|
|
146
|
+
obj[key] = value;
|
|
147
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export { Mat3 } from "./math/mat3";
|
|
2
|
+
export { Line } from "./shape/Line";
|
|
3
|
+
export { Mat4 } from "./math/mat4";
|
|
4
|
+
export { Plane } from "./shape/Plane";
|
|
5
|
+
export { type CastRayResult } from "./physics/narrowphase/CollideBodiesModule" ;
|
|
6
|
+
export { QueryPrecision } from "./collision/cast/cast";
|
|
7
|
+
export { Ray } from "./shape/Ray";
|
|
8
|
+
export { baseConstraintProps, ReferenceFrame } from "./constraints/BaseConstraint";
|
|
9
|
+
export { type Constraint } from "./constraints/types";
|
|
10
|
+
export { ContactManifold } from "./physics/manifold/ContactManifold";
|
|
11
|
+
export { FixedConstraint } from "./constraints/FixedConstraint";
|
|
12
|
+
export { PointConstraint } from "./constraints/PointConstraint";
|
|
13
|
+
export { DistanceConstraint } from "./constraints/DistanceConstraint";
|
|
14
|
+
export { HingeConstraint } from "./constraints/HingeConstraint";
|
|
15
|
+
export * as CollisionFilter from "./physics/CollisionFilter";
|
|
16
|
+
export { AllFlag, NoneFlag } from "./physics/CollisionFilter";
|
|
17
|
+
export { Isometry } from "./math/isometry";
|
|
18
|
+
export { clamp, squared, degreesToRadians } from "./math/scalar";
|
|
19
|
+
export { PolygonConvexSupport, SupportMode, ConvexRadiusObject } from "./shape/Convex";
|
|
20
|
+
export { destroyAllInstancesInPool } from "./helpers";
|
|
21
|
+
export { NumberValue } from "./math/NumberValue";
|
|
22
|
+
export { GjkCastShapeResult, GjkModule } from "./collision/gjk/GjkModule";
|
|
23
|
+
export { ColliderType, Body, BodyType } from "./physics/Body";
|
|
24
|
+
export { Face } from "./physics/manifold/Face";
|
|
25
|
+
export { Capsule } from "./shape/Capsule";
|
|
26
|
+
export { default as ConvexHull } from "./shape/ConvexHull";
|
|
27
|
+
export { CastCollector, type CastSettings, CastResult } from "./collision/cast/cast";
|
|
28
|
+
export { Aabb } from "./shape/Aabb";
|
|
29
|
+
export { BvhTree, type BvhTreeOptions, BvhNode } from "./physics/broadphase/BvhTree";
|
|
30
|
+
export { BodyPairsModule, BodyPairNode } from "./physics/broadphase/BodyPairsModule";
|
|
31
|
+
export { CoefficientFunctionType } from "./physics/solver/ContactConstraintModule";
|
|
32
|
+
export {
|
|
33
|
+
ActiveEdgeMode,
|
|
34
|
+
CollectFacesMode,
|
|
35
|
+
BackFaceMode,
|
|
36
|
+
type CollisionSettings,
|
|
37
|
+
CollisionCollector,
|
|
38
|
+
CollisionResult,
|
|
39
|
+
} from "./collision/collide/collide";
|
|
40
|
+
export { Box } from "./shape/Box";
|
|
41
|
+
export { PenetrationDepthModule } from "./collision/gjk/PenetrationDepthModule";
|
|
42
|
+
export { CollideShapesModule } from "./collision/CollideShapesModule";
|
|
43
|
+
export { ShapeType, type Shape, type ConvexShape } from "./shape/Shape";
|
|
44
|
+
export { Quat } from "./math/quat";
|
|
45
|
+
export { Vec3 } from "./math/vec3";
|
|
46
|
+
export { Sphere } from "./shape/Sphere";
|
|
47
|
+
export { World } from "./world";
|
|
48
|
+
export { CompoundShape } from "./shape/CompoundShape";
|
|
49
|
+
export { BasicTransform } from "./math/BasicTransform";
|
|
50
|
+
export { TransformedShape } from "./shape/TransformedShape";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createClass, MonomorphType, NumberType, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
import { Quat } from "./quat";
|
|
3
|
+
import { Vec3 } from "./vec3";
|
|
4
|
+
|
|
5
|
+
const basicTransformProps = {
|
|
6
|
+
position: MonomorphType(Vec3),
|
|
7
|
+
rotation: MonomorphType(Quat),
|
|
8
|
+
scale: NumberType(1.0),
|
|
9
|
+
} as const satisfies PropertyDefinitionMap;
|
|
10
|
+
|
|
11
|
+
export class BasicTransform extends createClass<BasicTransform, typeof basicTransformProps>(basicTransformProps) {
|
|
12
|
+
equals(other: BasicTransform, tolerance: number = 1e-6) {
|
|
13
|
+
return (
|
|
14
|
+
this.position.equals(other.position, tolerance) &&
|
|
15
|
+
this.rotation.equals(other.rotation, tolerance) &&
|
|
16
|
+
Math.abs(this.scale - other.scale) < tolerance
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createClass, NumberType, PropertyDefinitionMap } from "monomorph";
|
|
2
|
+
|
|
3
|
+
const numberValueProps = {
|
|
4
|
+
value: NumberType(0.0),
|
|
5
|
+
} as const satisfies PropertyDefinitionMap;
|
|
6
|
+
|
|
7
|
+
export class NumberValue extends createClass<NumberValue, typeof numberValueProps>(numberValueProps) {
|
|
8
|
+
toObject() {
|
|
9
|
+
return {
|
|
10
|
+
value: this.value,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|