Skip to content

Commit c1d42d8

Browse files
CedricGuillemetryantrem
authored andcommitted
Inspector V2 Physics properties (BabylonJS#16800)
- [x] Motion type - [x] Prestep type - [x] Mass - [x] Linear/angular damping edit - [x] Linear/angular velocity value This PR contains basic support for physics body properties (from a TransformNode.physicsBody). If there is no physicsBody, right now it just uses a placeholder line component. Later, we are thinking we can provide some info and maybe a link to docs. I (@ryantrem) also included some additional changes/fixes in this PR: - "Flatten" AccordionPane. There is really no reason at this layer to have an array of content, since the original purpose of that in the context of PropertiesPane was to make it so the delegate doesn't have to be duplicated, but at this layer there is no delegate. - Update `useProperty` to return undefined if `target` is undefined, which is more consistent with getting undefined when the property does not exist. - Update `DropdownProps.options` to be a readonly array (otherwise there is a compile error if you try to pass in a readonly array because it thinks the Dropdown needs to be able to mutate the array). - Update `Dropdown` to use `useMemo` instead of `useState` so it updates with props changes. --------- Co-authored-by: Ryan Tremblay <[email protected]>
1 parent 4189f91 commit c1d42d8

File tree

11 files changed

+306
-63
lines changed

11 files changed

+306
-63
lines changed

package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/dev/inspector-v2/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"watch:dev": "npm run watch"
1717
},
1818
"devDependencies": {
19+
"@dev/addons": "^1.0.0",
1920
"@dev/core": "1.0.0",
2021
"@dev/loaders": "1.0.0",
21-
"@dev/addons": "^1.0.0",
2222
"@dev/materials": "^1.0.0",
2323
"@fluentui/react-components": "^9.62.0",
2424
"@fluentui/react-icons": "^2.0.271",
@@ -31,5 +31,8 @@
3131
"usehooks-ts": "^3.1.1",
3232
"webpack": "^5.98.0",
3333
"webpack-cli": "^5.1.0"
34+
},
35+
"dependencies": {
36+
"@babylonjs/havok": "^1.3.10"
3437
}
3538
}

packages/dev/inspector-v2/src/components/accordionPane.tsx

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,20 @@ export type AccordionSectionContent<ContextT> = Readonly<{
3333
key: string;
3434

3535
/**
36-
* The content that is added to individual sections.
36+
* The section this content belongs to.
3737
*/
38-
content: readonly Readonly<{
39-
/**
40-
* The section this content belongs to.
41-
*/
42-
section: symbol;
43-
44-
/**
45-
* An optional order for the content within the section.
46-
* Defaults to 0.
47-
*/
48-
order?: number;
49-
50-
/**
51-
* The React component that will be rendered for this content.
52-
*/
53-
component: ComponentType<{ context: ContextT }>;
54-
}>[];
38+
section: symbol;
39+
40+
/**
41+
* An optional order for the content within the section.
42+
* Defaults to 0.
43+
*/
44+
order?: number;
45+
46+
/**
47+
* The React component that will be rendered for this content.
48+
*/
49+
component: ComponentType<{ context: ContextT }>;
5550
}>;
5651

5752
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -113,12 +108,8 @@ export function AccordionPane<ContextT = unknown>(
113108
const childProps = child.props as AccordionSectionProps;
114109
defaultSectionContent.push({
115110
key: child.key ?? childProps.title,
116-
content: [
117-
{
118-
section: defaultSections[index].identity,
119-
component: () => child,
120-
},
121-
],
111+
section: defaultSections[index].identity,
112+
component: () => child,
122113
});
123114
}
124115
});
@@ -142,9 +133,7 @@ export function AccordionPane<ContextT = unknown>(
142133
return mergedSections
143134
.map((section) => {
144135
// Get a flat list of the section content, preserving the key so it can be used when each component for each section is rendered.
145-
const contentForSection = mergedSectionContent
146-
.flatMap((entry) => entry.content.map((content) => ({ key: entry.key, ...content })))
147-
.filter((content) => content.section === section.identity);
136+
const contentForSection = mergedSectionContent.filter((content) => content.section === section.identity);
148137

149138
// If there is no content for this section, we skip it.
150139
if (contentForSection.length === 0) {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type { FunctionComponent } from "react";
2+
3+
import type { PhysicsBody } from "core/index";
4+
import type { DropdownOption } from "shared-ui-components/fluent/primitives/dropdown";
5+
6+
import { useCallback } from "react";
7+
8+
import { Vector3 } from "core/Maths/math.vector";
9+
import { PhysicsMotionType, PhysicsPrestepType } from "core/Physics/v2/IPhysicsEnginePlugin";
10+
import { NumberDropdownPropertyLine } from "shared-ui-components/fluent/hoc/dropdownPropertyLine";
11+
import { FloatInputPropertyLine } from "shared-ui-components/fluent/hoc/inputPropertyLine";
12+
import { Vector3PropertyLine } from "shared-ui-components/fluent/hoc/vectorPropertyLine";
13+
import { useVector3Property } from "../../hooks/compoundPropertyHooks";
14+
import { useInterceptObservable } from "../../hooks/instrumentationHooks";
15+
import { useObservableState } from "../../hooks/observableHooks";
16+
17+
import "core/Physics/v2/physicsEngineComponent";
18+
19+
const MotionOptions = [
20+
{ label: "Static", value: PhysicsMotionType.STATIC },
21+
{ label: "Animated", value: PhysicsMotionType.ANIMATED },
22+
{ label: "Dynamic", value: PhysicsMotionType.DYNAMIC },
23+
] as const satisfies readonly DropdownOption[];
24+
25+
const PrestepOptions = [
26+
{ label: "Disabled", value: PhysicsPrestepType.DISABLED },
27+
{ label: "Teleport", value: PhysicsPrestepType.TELEPORT },
28+
{ label: "Action", value: PhysicsPrestepType.ACTION },
29+
] as const satisfies readonly DropdownOption[];
30+
31+
/**
32+
* Physics properties
33+
* @param props transform node
34+
* @returns controls
35+
*/
36+
export const PhysicsBodyProperties: FunctionComponent<{ physicsBody: PhysicsBody }> = (props) => {
37+
const { physicsBody } = props;
38+
39+
const massProperties = useObservableState(
40+
useCallback(() => physicsBody.getMassProperties(), [physicsBody]),
41+
useInterceptObservable("function", physicsBody, "setMassProperties")
42+
);
43+
44+
const centerOfMass = useVector3Property(massProperties, "centerOfMass") ?? Vector3.Zero();
45+
const inertia = useVector3Property(massProperties, "inertia") ?? Vector3.Zero();
46+
47+
// Get current damping values
48+
const linearDamping = useObservableState(() => physicsBody.getLinearDamping(), useInterceptObservable("function", physicsBody, "setLinearDamping"));
49+
const angularDamping = useObservableState(() => physicsBody.getAngularDamping(), useInterceptObservable("function", physicsBody, "setAngularDamping"));
50+
51+
// Get motion and prestep types
52+
const motionType = useObservableState(() => physicsBody.getMotionType(), useInterceptObservable("function", physicsBody, "setMotionType"));
53+
const prestepType = useObservableState(() => physicsBody.getPrestepType(), useInterceptObservable("function", physicsBody, "setPrestepType"));
54+
55+
// Get current velocities
56+
const linearVelocity = useObservableState(
57+
useCallback(() => physicsBody.getLinearVelocity(), [physicsBody]),
58+
useInterceptObservable("function", physicsBody, "setLinearVelocity")
59+
);
60+
const angularVelocity = useObservableState(
61+
useCallback(() => physicsBody.getAngularVelocity(), [physicsBody]),
62+
useInterceptObservable("function", physicsBody, "setAngularVelocity")
63+
);
64+
65+
return (
66+
<>
67+
<NumberDropdownPropertyLine
68+
key="MotionType"
69+
label="Motion Type"
70+
options={MotionOptions}
71+
value={motionType}
72+
onChange={(value) => {
73+
return physicsBody.setMotionType(value as PhysicsMotionType);
74+
}}
75+
/>
76+
<NumberDropdownPropertyLine
77+
label="Prestep Type"
78+
options={PrestepOptions}
79+
value={prestepType}
80+
onChange={(value) => {
81+
return physicsBody.setPrestepType(value as PhysicsPrestepType);
82+
}}
83+
/>
84+
{/* Linear Damping */}
85+
<FloatInputPropertyLine
86+
label="Linear Damping"
87+
min={0}
88+
max={1}
89+
step={0.01}
90+
value={linearDamping}
91+
onChange={(e) => {
92+
physicsBody.setLinearDamping(e);
93+
}}
94+
/>
95+
{/* Angular Damping */}
96+
<FloatInputPropertyLine
97+
label="Angular Damping"
98+
min={0}
99+
max={1}
100+
step={0.01}
101+
value={angularDamping}
102+
onChange={(e) => {
103+
physicsBody.setAngularDamping(e);
104+
}}
105+
/>
106+
<Vector3PropertyLine label="Linear Velocity" value={linearVelocity} onChange={(value) => physicsBody.setLinearVelocity(value)} />
107+
<Vector3PropertyLine label="Angular Velocity" value={angularVelocity} onChange={(value) => physicsBody.setAngularVelocity(value)} />
108+
{/* Physics Mass Properties Controls */}
109+
{massProperties && (
110+
<>
111+
<FloatInputPropertyLine
112+
label="Mass"
113+
value={massProperties.mass ?? 0}
114+
min={0}
115+
step={0.01}
116+
onChange={(value) => {
117+
physicsBody.setMassProperties({ ...massProperties, mass: value });
118+
}}
119+
/>
120+
<Vector3PropertyLine
121+
label="Center of Mass"
122+
value={centerOfMass}
123+
onChange={(value) => {
124+
physicsBody.setMassProperties({ ...massProperties, centerOfMass: value });
125+
}}
126+
/>
127+
<Vector3PropertyLine
128+
label="Inertia"
129+
value={inertia}
130+
onChange={(value) => {
131+
physicsBody.setMassProperties({ ...massProperties, inertia: value });
132+
}}
133+
/>
134+
</>
135+
)}
136+
</>
137+
);
138+
};

packages/dev/inspector-v2/src/hooks/compoundPropertyHooks.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import type { Vector3, Color3, Color4, Nullable, Quaternion } from "core/index";
1+
import type { Vector3, Color3, Color4, Quaternion } from "core/index";
22

33
import { useCallback } from "react";
44

55
import { useInterceptObservable } from "./instrumentationHooks";
66
import { useObservableState } from "./observableHooks";
77

8-
type PropertyKeys<TargetT, PropertyT> = { [PropertyKeyT in keyof TargetT]: TargetT[PropertyKeyT] extends Nullable<PropertyT> ? PropertyKeyT : never }[keyof TargetT];
8+
type PropertyKeys<TargetT, PropertyT> = { [PropertyKeyT in keyof TargetT]: TargetT[PropertyKeyT] extends PropertyT | null | undefined ? PropertyKeyT : never }[keyof TargetT];
99

1010
export function useProperty<TargetT extends object, PropertyKeyT extends keyof TargetT>(target: TargetT, propertyKey: PropertyKeyT): TargetT[PropertyKeyT];
1111
export function useProperty<TargetT extends object, PropertyKeyT extends keyof TargetT>(
1212
target: TargetT | null | undefined,
1313
propertyKey: PropertyKeyT
14-
): TargetT[PropertyKeyT] | null;
14+
): TargetT[PropertyKeyT] | undefined;
1515
/**
1616
* Translates a property value to react state, updating the state whenever the property changes.
1717
* @param target The object containing the property to observe.
@@ -20,7 +20,7 @@ export function useProperty<TargetT extends object, PropertyKeyT extends keyof T
2020
*/
2121
export function useProperty<TargetT extends object, PropertyKeyT extends keyof TargetT>(target: TargetT | null | undefined, propertyKey: PropertyKeyT) {
2222
return useObservableState(
23-
useCallback(() => (target ? target[propertyKey] : null), [target, propertyKey]),
23+
useCallback(() => (target ? target[propertyKey] : undefined), [target, propertyKey]),
2424
useInterceptObservable("property", target, propertyKey)
2525
);
2626
}
@@ -29,7 +29,7 @@ export function useVector3Property<TargetT extends object, PropertyKeyT extends
2929
export function useVector3Property<TargetT extends object, PropertyKeyT extends PropertyKeys<TargetT, Vector3>>(
3030
target: TargetT | null | undefined,
3131
propertyKey: PropertyKeyT
32-
): TargetT[PropertyKeyT] | null;
32+
): TargetT[PropertyKeyT] | undefined;
3333
/**
3434
* Translates a Vector3 property value to react state, updating the state whenever the property changes or when the x/y/z components of the Vector3 change.
3535
* @param target The object containing the property to observe.
@@ -48,7 +48,7 @@ export function useColor3Property<TargetT extends object, PropertyKeyT extends P
4848
export function useColor3Property<TargetT extends object, PropertyKeyT extends PropertyKeys<TargetT, Color3>>(
4949
target: TargetT | null | undefined,
5050
propertyKey: PropertyKeyT
51-
): TargetT[PropertyKeyT] | null;
51+
): TargetT[PropertyKeyT] | undefined;
5252
/**
5353
* Translates a Color3 property value to react state, updating the state whenever the property changes or when the r/g/b components of the Color3 change.
5454
* @param target The object containing the property to observe.
@@ -90,7 +90,7 @@ export function useQuaternionProperty<TargetT extends object, PropertyKeyT exten
9090
export function useQuaternionProperty<TargetT extends object, PropertyKeyT extends PropertyKeys<TargetT, Quaternion>>(
9191
target: TargetT | null | undefined,
9292
propertyKey: PropertyKeyT
93-
): TargetT[PropertyKeyT] | null;
93+
): TargetT[PropertyKeyT] | undefined;
9494
/**
9595
* Translates a Quaternion property value to react state, updating the state whenever the property changes or when the x/y/z/w components of the Quaternion change.
9696
* @param target The object containing the property to observe.

packages/dev/inspector-v2/src/inspector.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { PropertiesServiceDefinition } from "./services/panes/properties/propert
2626
import { SkeletonPropertiesServiceDefinition } from "./services/panes/properties/skeletonPropertiesService";
2727
import { SpritePropertiesServiceDefinition } from "./services/panes/properties/spritePropertiesService";
2828
import { TransformNodePropertiesServiceDefinition } from "./services/panes/properties/transformNodePropertiesService";
29+
import { PhysicsPropertiesServiceDefinition } from "./services/panes/properties/physicsPropertiesService";
2930
import { MaterialExplorerServiceDefinition } from "./services/panes/scene/materialExplorerService";
3031
import { NodeHierarchyServiceDefinition } from "./services/panes/scene/nodeExplorerService";
3132
import { SceneExplorerServiceDefinition } from "./services/panes/scene/sceneExplorerService";
@@ -190,6 +191,7 @@ function _ShowInspector(scene: Nullable<Scene>, options: Partial<IInspectorOptio
190191
NodePropertiesServiceDefinition,
191192
MeshPropertiesServiceDefinition,
192193
TransformNodePropertiesServiceDefinition,
194+
PhysicsPropertiesServiceDefinition,
193195
SkeletonPropertiesServiceDefinition,
194196
BonePropertiesServiceDefinition,
195197
MaterialPropertiesServiceDefinition,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { ServiceDefinition } from "../../../modularity/serviceDefinition";
2+
import type { IPropertiesService } from "./propertiesService";
3+
4+
import { TransformNode } from "core/Meshes/transformNode";
5+
6+
import { PropertiesServiceIdentity } from "./propertiesService";
7+
import { PhysicsBodyProperties } from "../../../components/properties/physicsProperties";
8+
import { useProperty } from "../../../hooks/compoundPropertyHooks";
9+
import { PlaceholderPropertyLine } from "shared-ui-components/fluent/hoc/propertyLine";
10+
11+
export const PhysicsPropertiesSectionIdentity = Symbol("Physics");
12+
13+
export const PhysicsPropertiesServiceDefinition: ServiceDefinition<[], [IPropertiesService]> = {
14+
friendlyName: "Physics Properties",
15+
consumes: [PropertiesServiceIdentity],
16+
factory: (propertiesService) => {
17+
const physicsSectionRegistration = propertiesService.addSection({
18+
order: 5,
19+
identity: PhysicsPropertiesSectionIdentity,
20+
});
21+
22+
const contentRegistration = propertiesService.addSectionContent({
23+
key: "Physics Properties",
24+
predicate: (entity: unknown) => entity instanceof TransformNode,
25+
content: [
26+
// "Physics" section.
27+
{
28+
section: PhysicsPropertiesSectionIdentity,
29+
order: 0,
30+
component: ({ context: node }) => {
31+
const physicsBody = useProperty(node, "physicsBody");
32+
33+
if (!physicsBody) {
34+
return <PlaceholderPropertyLine label="No Physics Body" value={undefined} onChange={() => {}} />;
35+
}
36+
37+
return <PhysicsBodyProperties physicsBody={physicsBody} />;
38+
},
39+
},
40+
],
41+
});
42+
43+
return {
44+
dispose: () => {
45+
contentRegistration.dispose();
46+
physicsSectionRegistration.dispose();
47+
},
48+
};
49+
},
50+
};

0 commit comments

Comments
 (0)