Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/dev/inspector-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"watch:dev": "npm run watch"
},
"devDependencies": {
"@dev/addons": "^1.0.0",
"@dev/core": "1.0.0",
"@dev/loaders": "1.0.0",
"@dev/addons": "^1.0.0",
"@dev/materials": "^1.0.0",
"@fluentui/react-components": "^9.62.0",
"@fluentui/react-icons": "^2.0.271",
Expand All @@ -31,5 +31,8 @@
"usehooks-ts": "^3.1.1",
"webpack": "^5.98.0",
"webpack-cli": "^5.1.0"
},
"dependencies": {
"@babylonjs/havok": "^1.3.10"
}
}
43 changes: 16 additions & 27 deletions packages/dev/inspector-v2/src/components/accordionPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,20 @@ export type AccordionSectionContent<ContextT> = Readonly<{
key: string;

/**
* The content that is added to individual sections.
* The section this content belongs to.
*/
content: readonly Readonly<{
/**
* The section this content belongs to.
*/
section: symbol;

/**
* An optional order for the content within the section.
* Defaults to 0.
*/
order?: number;

/**
* The React component that will be rendered for this content.
*/
component: ComponentType<{ context: ContextT }>;
}>[];
section: symbol;

/**
* An optional order for the content within the section.
* Defaults to 0.
*/
order?: number;

/**
* The React component that will be rendered for this content.
*/
component: ComponentType<{ context: ContextT }>;
}>;

// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down Expand Up @@ -118,12 +113,8 @@ export function AccordionPane<ContextT = unknown>(
const childProps = child.props as AccordionSectionProps;
defaultSectionContent.push({
key: child.key ?? childProps.title,
content: [
{
section: defaultSections[index].identity,
component: () => child,
},
],
section: defaultSections[index].identity,
component: () => child,
});
}
});
Expand All @@ -147,9 +138,7 @@ export function AccordionPane<ContextT = unknown>(
return mergedSections
.map((section) => {
// Get a flat list of the section content, preserving the key so it can be used when each component for each section is rendered.
const contentForSection = mergedSectionContent
.flatMap((entry) => entry.content.map((content) => ({ key: entry.key, ...content })))
.filter((content) => content.section === section.identity);
const contentForSection = mergedSectionContent.filter((content) => content.section === section.identity);

// If there is no content for this section, we skip it.
if (contentForSection.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import type { FunctionComponent } from "react";

import type { PhysicsBody } from "core/index";
import type { DropdownOption } from "shared-ui-components/fluent/primitives/dropdown";

import { useCallback } from "react";

import { Vector3 } from "core/Maths/math.vector";
import { PhysicsMotionType, PhysicsPrestepType } from "core/Physics/v2/IPhysicsEnginePlugin";
import { NumberDropdownPropertyLine } from "shared-ui-components/fluent/hoc/dropdownPropertyLine";
import { FloatInputPropertyLine } from "shared-ui-components/fluent/hoc/inputPropertyLine";
import { Vector3PropertyLine } from "shared-ui-components/fluent/hoc/vectorPropertyLine";
import { useVector3Property } from "../../hooks/compoundPropertyHooks";
import { useInterceptObservable } from "../../hooks/instrumentationHooks";
import { useObservableState } from "../../hooks/observableHooks";

import "core/Physics/v2/physicsEngineComponent";

const MotionOptions = [
{ label: "Static", value: PhysicsMotionType.STATIC },
{ label: "Animated", value: PhysicsMotionType.ANIMATED },
{ label: "Dynamic", value: PhysicsMotionType.DYNAMIC },
] as const satisfies readonly DropdownOption[];

const PrestepOptions = [
{ label: "Disabled", value: PhysicsPrestepType.DISABLED },
{ label: "Teleport", value: PhysicsPrestepType.TELEPORT },
{ label: "Action", value: PhysicsPrestepType.ACTION },
] as const satisfies readonly DropdownOption[];

/**
* Physics properties
* @param props transform node
* @returns controls
*/
export const PhysicsBodyProperties: FunctionComponent<{ physicsBody: PhysicsBody }> = (props) => {
const { physicsBody } = props;

const massProperties = useObservableState(
useCallback(() => physicsBody.getMassProperties(), [physicsBody]),
useInterceptObservable("function", physicsBody, "setMassProperties")
);

const centerOfMass = useVector3Property(massProperties, "centerOfMass") ?? Vector3.Zero();
const inertia = useVector3Property(massProperties, "inertia") ?? Vector3.Zero();

// Get current damping values
const linearDamping = useObservableState(() => physicsBody.getLinearDamping(), useInterceptObservable("function", physicsBody, "setLinearDamping"));
const angularDamping = useObservableState(() => physicsBody.getAngularDamping(), useInterceptObservable("function", physicsBody, "setAngularDamping"));

// Get motion and prestep types
const motionType = useObservableState(() => physicsBody.getMotionType(), useInterceptObservable("function", physicsBody, "setMotionType"));
const prestepType = useObservableState(() => physicsBody.getPrestepType(), useInterceptObservable("function", physicsBody, "setPrestepType"));

// Get current velocities
const linearVelocity = useObservableState(
useCallback(() => physicsBody.getLinearVelocity(), [physicsBody]),
useInterceptObservable("function", physicsBody, "setLinearVelocity")
);
const angularVelocity = useObservableState(
useCallback(() => physicsBody.getAngularVelocity(), [physicsBody]),
useInterceptObservable("function", physicsBody, "setAngularVelocity")
);

return (
<>
<NumberDropdownPropertyLine
key="MotionType"
label="Motion Type"
options={MotionOptions}
value={motionType}
onChange={(value) => {
return physicsBody.setMotionType(value as PhysicsMotionType);
}}
/>
<NumberDropdownPropertyLine
label="Prestep Type"
options={PrestepOptions}
value={prestepType}
onChange={(value) => {
return physicsBody.setPrestepType(value as PhysicsPrestepType);
}}
/>
{/* Linear Damping */}
<FloatInputPropertyLine
label="Linear Damping"
min={0}
max={1}
step={0.01}
value={linearDamping}
onChange={(e) => {
physicsBody.setLinearDamping(e);
}}
/>
{/* Angular Damping */}
<FloatInputPropertyLine
label="Angular Damping"
min={0}
max={1}
step={0.01}
value={angularDamping}
onChange={(e) => {
physicsBody.setAngularDamping(e);
}}
/>
<Vector3PropertyLine label="Linear Velocity" value={linearVelocity} onChange={(value) => physicsBody.setLinearVelocity(value)} />
<Vector3PropertyLine label="Angular Velocity" value={angularVelocity} onChange={(value) => physicsBody.setAngularVelocity(value)} />
{/* Physics Mass Properties Controls */}
{massProperties && (
<>
<FloatInputPropertyLine
label="Mass"
value={massProperties.mass ?? 0}
min={0}
step={0.01}
onChange={(value) => {
physicsBody.setMassProperties({ ...massProperties, mass: value });
}}
/>
<Vector3PropertyLine
label="Center of Mass"
value={centerOfMass}
onChange={(value) => {
physicsBody.setMassProperties({ ...massProperties, centerOfMass: value });
}}
/>
<Vector3PropertyLine
label="Inertia"
value={inertia}
onChange={(value) => {
physicsBody.setMassProperties({ ...massProperties, inertia: value });
}}
/>
</>
)}
</>
);
};
14 changes: 7 additions & 7 deletions packages/dev/inspector-v2/src/hooks/compoundPropertyHooks.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import type { Vector3, Color3, Color4, Nullable, Quaternion } from "core/index";
import type { Vector3, Color3, Color4, Quaternion } from "core/index";

import { useCallback } from "react";

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

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

export function useProperty<TargetT extends object, PropertyKeyT extends keyof TargetT>(target: TargetT, propertyKey: PropertyKeyT): TargetT[PropertyKeyT];
export function useProperty<TargetT extends object, PropertyKeyT extends keyof TargetT>(
target: TargetT | null | undefined,
propertyKey: PropertyKeyT
): TargetT[PropertyKeyT] | null;
): TargetT[PropertyKeyT] | undefined;
/**
* Translates a property value to react state, updating the state whenever the property changes.
* @param target The object containing the property to observe.
Expand All @@ -20,7 +20,7 @@ export function useProperty<TargetT extends object, PropertyKeyT extends keyof T
*/
export function useProperty<TargetT extends object, PropertyKeyT extends keyof TargetT>(target: TargetT | null | undefined, propertyKey: PropertyKeyT) {
return useObservableState(
useCallback(() => (target ? target[propertyKey] : null), [target, propertyKey]),
useCallback(() => (target ? target[propertyKey] : undefined), [target, propertyKey]),
useInterceptObservable("property", target, propertyKey)
);
}
Expand All @@ -29,7 +29,7 @@ export function useVector3Property<TargetT extends object, PropertyKeyT extends
export function useVector3Property<TargetT extends object, PropertyKeyT extends PropertyKeys<TargetT, Vector3>>(
target: TargetT | null | undefined,
propertyKey: PropertyKeyT
): TargetT[PropertyKeyT] | null;
): TargetT[PropertyKeyT] | undefined;
/**
* 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.
* @param target The object containing the property to observe.
Expand All @@ -48,7 +48,7 @@ export function useColor3Property<TargetT extends object, PropertyKeyT extends P
export function useColor3Property<TargetT extends object, PropertyKeyT extends PropertyKeys<TargetT, Color3>>(
target: TargetT | null | undefined,
propertyKey: PropertyKeyT
): TargetT[PropertyKeyT] | null;
): TargetT[PropertyKeyT] | undefined;
/**
* 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.
* @param target The object containing the property to observe.
Expand Down Expand Up @@ -90,7 +90,7 @@ export function useQuaternionProperty<TargetT extends object, PropertyKeyT exten
export function useQuaternionProperty<TargetT extends object, PropertyKeyT extends PropertyKeys<TargetT, Quaternion>>(
target: TargetT | null | undefined,
propertyKey: PropertyKeyT
): TargetT[PropertyKeyT] | null;
): TargetT[PropertyKeyT] | undefined;
/**
* 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.
* @param target The object containing the property to observe.
Expand Down
2 changes: 2 additions & 0 deletions packages/dev/inspector-v2/src/inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { PropertiesServiceDefinition } from "./services/panes/properties/propert
import { SkeletonPropertiesServiceDefinition } from "./services/panes/properties/skeletonPropertiesService";
import { SpritePropertiesServiceDefinition } from "./services/panes/properties/spritePropertiesService";
import { TransformNodePropertiesServiceDefinition } from "./services/panes/properties/transformNodePropertiesService";
import { PhysicsPropertiesServiceDefinition } from "./services/panes/properties/physicsPropertiesService";
import { MaterialExplorerServiceDefinition } from "./services/panes/scene/materialExplorerService";
import { NodeHierarchyServiceDefinition } from "./services/panes/scene/nodeExplorerService";
import { SceneExplorerServiceDefinition } from "./services/panes/scene/sceneExplorerService";
Expand Down Expand Up @@ -190,6 +191,7 @@ function _ShowInspector(scene: Nullable<Scene>, options: Partial<IInspectorOptio
NodePropertiesServiceDefinition,
MeshPropertiesServiceDefinition,
TransformNodePropertiesServiceDefinition,
PhysicsPropertiesServiceDefinition,
SkeletonPropertiesServiceDefinition,
BonePropertiesServiceDefinition,
MaterialPropertiesServiceDefinition,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ServiceDefinition } from "../../../modularity/serviceDefinition";
import type { IPropertiesService } from "./propertiesService";

import { TransformNode } from "core/Meshes/transformNode";

import { PropertiesServiceIdentity } from "./propertiesService";
import { PhysicsBodyProperties } from "../../../components/properties/physicsProperties";
import { useProperty } from "../../../hooks/compoundPropertyHooks";
import { PlaceholderPropertyLine } from "shared-ui-components/fluent/hoc/propertyLine";

export const PhysicsPropertiesSectionIdentity = Symbol("Physics");

export const PhysicsPropertiesServiceDefinition: ServiceDefinition<[], [IPropertiesService]> = {
friendlyName: "Physics Properties",
consumes: [PropertiesServiceIdentity],
factory: (propertiesService) => {
const physicsSectionRegistration = propertiesService.addSection({
order: 5,
identity: PhysicsPropertiesSectionIdentity,
});

const contentRegistration = propertiesService.addSectionContent({
key: "Physics Properties",
predicate: (entity: unknown) => entity instanceof TransformNode,
content: [
// "Physics" section.
{
section: PhysicsPropertiesSectionIdentity,
order: 0,
component: ({ context: node }) => {
const physicsBody = useProperty(node, "physicsBody");

if (!physicsBody) {
return <PlaceholderPropertyLine label="No Physics Body" value={undefined} onChange={() => {}} />;
}

return <PhysicsBodyProperties physicsBody={physicsBody} />;
},
},
],
});

return {
dispose: () => {
contentRegistration.dispose();
physicsSectionRegistration.dispose();
},
};
},
};
Loading
Loading