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

feat(EnvironmentModel): Adding support for environment overlay #262

Merged
merged 2 commits into from
Oct 5, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ it('should not update session URL when fields are the same for on demand mode',
expect(getKvsStreamSrcFn).toBeCalledWith(PLAYBACKMODE_ON_DEMAND, startTime, endTime);
});

it('should not update session URL when fields are the same for live mode', async () => {
// TODO: Fix the flaky test
it.skip('should not update session URL when fields are the same for live mode', async () => {
const getKvsStreamSrcFn = jest.spyOn(mockVideoData, 'getKvsStreamSrc').mockResolvedValue(mockLiveURL);
const { rerender } = render(<VideoPlayer viewport={{ duration: '0' }} videoData={mockVideoData} />);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { HTML5Backend } from 'react-dnd-html5-backend';
import useLogger from '../../../logger/react-logger/hooks/useLogger';
import { useSceneComposerId } from '../../../common/sceneComposerIdContext';
import { ISceneNodeInternal, useStore } from '../../../store';
import { isEnvironmentNode } from '../../../utils/nodeUtils';

import ISceneHierarchyNode from './model/ISceneHierarchyNode';

Expand Down Expand Up @@ -106,12 +107,13 @@ const SceneHierarchyDataProvider: FC<SceneHierarchyDataProviderProps> = ({ selec
getObject3DBySceneNodeRef,
setCameraTarget,
removeSceneNode,
isEditing,
} = useStore(sceneComposerId)((state) => state);

const { nodeMap } = document;

const rootNodeRefs = Object.values(nodeMap)
.filter((item) => !item.parentRef)
.filter((item) => !item.parentRef && (!isEnvironmentNode(item) || isEditing()))
.map((item) => item.ref);

const [searchTerms, setSearchTerms] = useState('');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import ISceneHierarchyNode from '../../model/ISceneHierarchyNode';
import { useChildNodes, useSceneHierarchyData } from '../../SceneHierarchyDataProvider';
import { DropHandler } from '../../../../../hooks/useDropMonitor';
import SubModelTree from '../SubModelTree';
import { KnownComponentType } from '../../../../../interfaces';
import { useNodeErrorState, useStore } from '../../../../../store';
import { sceneComposerIdContext, useSceneComposerId } from '../../../../../common/sceneComposerIdContext';
import { isEnvironmentNode } from '../../../../../utils/nodeUtils';

import SceneNodeLabel from './SceneNodeLabel';
import { AcceptableDropTypes, EnhancedTree, EnhancedTreeItem } from './constants';
Expand Down Expand Up @@ -35,9 +35,9 @@ const SceneHierarchyTreeItem: FC<SceneHierarchyTreeItemProps> = ({
const model = getObject3DBySceneNodeRef(key) as Object3D | undefined;
const sceneComposerId = useSceneComposerId();
const isViewing = useStore(sceneComposerId)((state) => state.isViewing);
const node = useStore(sceneComposerId)((state) => state.getSceneNodeByRef(key));

const isModelRef = componentTypes?.find((type) => type === KnownComponentType.ModelRef);
const showSubModel = isModelRef && !!model && !isViewing();
const showSubModel = !isEnvironmentNode(node) && !!model && !isViewing();

const onExpandNode = useCallback((expanded) => {
setExpanded(expanded);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useSnapObjectToFloor } from '../../three/transformUtils';
import { toNumber } from '../../utils/stringUtils';
import { isLinearPlaneMotionIndicator } from '../../utils/sceneComponentUtils';
import LogProvider from '../../logger/react-logger/log-provider';
import { findComponentByType } from '../../utils/nodeUtils';
import { findComponentByType, isEnvironmentNode } from '../../utils/nodeUtils';

import { ComponentEditor } from './ComponentEditor';
import { Matrix3XInputGrid, ExpandableInfoSection, Triplet } from './CommonPanelComponents';
Expand Down Expand Up @@ -127,64 +127,66 @@ export const SceneNodeInspectorPanel: React.FC = () => {
<Input value={selectedSceneNode.name} onChange={(e) => handleInputChanges({ name: e.detail.value })} />
</FormField>
</ExpandableInfoSection>
<ExpandableInfoSection
title={intl.formatMessage({ defaultMessage: 'Transform', description: 'Expandable section title' })}
defaultExpanded
>
<Matrix3XInputGrid
name={intl.formatMessage({ defaultMessage: 'Position', description: 'Input Grid title name' })}
labels={['X', 'Y', 'Z']}
values={selectedSceneNode.transform.position}
disabled={[false, selectedSceneNode.transformConstraint.snapToFloor === true, false]}
readonly={readonly}
toStr={(a) => a.toFixed(3)}
fromStr={toNumber}
onChange={debounce((items) => {
handleInputChanges({ transform: { position: items } });
applySnapToFloorConstraint();
}, 100)}
/>
<Matrix3XInputGrid
name={intl.formatMessage({ defaultMessage: 'Rotation', description: 'Input Grid title name' })}
labels={['X', 'Y', 'Z']}
values={selectedSceneNode.transform.rotation}
toStr={(a) => THREE.MathUtils.radToDeg(a).toFixed(3)}
fromStr={(s) => THREE.MathUtils.degToRad(toNumber(s))}
readonly={readonly}
onChange={debounce((items) => {
handleInputChanges({ transform: { rotation: items } });
applySnapToFloorConstraint();
}, 100)}
/>
{!isCameraComponent && (
{!isEnvironmentNode(selectedSceneNode) && (
<ExpandableInfoSection
title={intl.formatMessage({ defaultMessage: 'Transform', description: 'Expandable section title' })}
defaultExpanded
>
<Matrix3XInputGrid
name={intl.formatMessage({ defaultMessage: 'Scale', description: 'Input Grid title name' })}
name={intl.formatMessage({ defaultMessage: 'Position', description: 'Input Grid title name' })}
labels={['X', 'Y', 'Z']}
disabled={[false, isLinearPlaneMotionIndicator(selectedSceneNode), false]}
values={selectedSceneNode.transform.position}
disabled={[false, selectedSceneNode.transformConstraint.snapToFloor === true, false]}
readonly={readonly}
values={selectedSceneNode.transform.scale}
toStr={(a) => a.toFixed(3)}
fromStr={toNumber}
onChange={debounce((items) => {
handleInputChanges({ transform: { scale: items } });
handleInputChanges({ transform: { position: items } });
applySnapToFloorConstraint();
}, 100)}
/>
)}
{isModelComponent && (
<FormField label={intl.formatMessage({ defaultMessage: 'Constraints', description: 'Form field label' })}>
<Checkbox
checked={selectedSceneNode.transformConstraint.snapToFloor === true}
onChange={({ detail: { checked } }) => {
handleInputChanges({ transformConstraint: { snapToFloor: checked } });
<Matrix3XInputGrid
name={intl.formatMessage({ defaultMessage: 'Rotation', description: 'Input Grid title name' })}
labels={['X', 'Y', 'Z']}
values={selectedSceneNode.transform.rotation}
toStr={(a) => THREE.MathUtils.radToDeg(a).toFixed(3)}
fromStr={(s) => THREE.MathUtils.degToRad(toNumber(s))}
readonly={readonly}
onChange={debounce((items) => {
handleInputChanges({ transform: { rotation: items } });
applySnapToFloorConstraint();
}, 100)}
/>
{!isCameraComponent && (
<Matrix3XInputGrid
name={intl.formatMessage({ defaultMessage: 'Scale', description: 'Input Grid title name' })}
labels={['X', 'Y', 'Z']}
disabled={[false, isLinearPlaneMotionIndicator(selectedSceneNode), false]}
readonly={readonly}
values={selectedSceneNode.transform.scale}
toStr={(a) => a.toFixed(3)}
fromStr={toNumber}
onChange={debounce((items) => {
handleInputChanges({ transform: { scale: items } });
applySnapToFloorConstraint();
}}
>
{intl.formatMessage({ defaultMessage: 'Snap to floor', description: 'checkbox option' })}
</Checkbox>
</FormField>
)}
</ExpandableInfoSection>
}, 100)}
/>
)}
{isModelComponent && (
<FormField label={intl.formatMessage({ defaultMessage: 'Constraints', description: 'Form field label' })}>
<Checkbox
checked={selectedSceneNode.transformConstraint.snapToFloor === true}
onChange={({ detail: { checked } }) => {
handleInputChanges({ transformConstraint: { snapToFloor: checked } });
applySnapToFloorConstraint();
}}
>
{intl.formatMessage({ defaultMessage: 'Snap to floor', description: 'checkbox option' })}
</Checkbox>
</FormField>
)}
</ExpandableInfoSection>
)}

{componentViews}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useStore } from '../../store';
import { TransformControls as TransformControlsImpl } from '../../three/TransformControls';
import { snapObjectToFloor } from '../../three/transformUtils';
import { isLinearPlaneMotionIndicator } from '../../utils/sceneComponentUtils';
import { isEnvironmentNode } from '../../utils/nodeUtils';

export function EditorTransformControls() {
const { domElement } = useThree(({ gl }) => gl);
Expand Down Expand Up @@ -54,7 +55,7 @@ export function EditorTransformControls() {

// Update transform controls' attached object
useEffect(() => {
if (selectedSceneNodeRef) {
if (selectedSceneNodeRef && !isEnvironmentNode(selectedSceneNode)) {
const object3d = getObject3DBySceneNodeRef(selectedSceneNodeRef);
if (object3d) {
log?.verbose('attach transform controls to', object3d);
Expand All @@ -70,7 +71,7 @@ export function EditorTransformControls() {
if (addingWidget) {
transformControls.detach();
}
}, [selectedSceneNodeRef, document, log, addingWidget]);
}, [selectedSceneNodeRef, selectedSceneNode, document, log, addingWidget]);

// Transform control callbacks
useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { render } from '@testing-library/react';
import EntityGroup from '..';
import { useSceneDocument } from '../../../../store';
import { fakeSceneNode } from '../fakers';
import { IModelRefComponent, KnownComponentType } from '../../../../interfaces';
import { ModelType } from '../../../../models/SceneModels';

jest.mock('../../../../store', () => ({
...jest.requireActual('../../../../store'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { sceneComposerIdContext, useSceneComposerId } from '../../../common/scen
import { getChildrenGroupName, getEntityGroupName } from '../../../utils/objectThreeUtils';
import { KnownComponentType } from '../../../interfaces';
import LogProvider from '../../../logger/react-logger/log-provider';
import { isEnvironmentNode } from '../../../utils/nodeUtils';

import useCallbackWhenNotPanning from './useCallbackWhenNotPanning';
import ComponentGroup from './ComponentGroup';
Expand Down Expand Up @@ -110,7 +111,7 @@ const EntityGroup = ({ node }: IEntityGroupProps) => {
dispose={null}
onPointerDown={onPointerDown}
onPointerUp={onPointerUp}
userData={{ nodeRef, componentTypes }}
userData={{ nodeRef: !isEnvironmentNode(node) ? nodeRef : undefined, componentTypes }} // Do not add ref for environment nodes
>
<ComponentGroup node={node} components={node.components} />
<ChildGroup node={node} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';

import { IModelRefComponentInternal, ISceneNodeInternal, useEditorState } from '../../../store';
import { useSceneComposerId } from '../../../common/sceneComposerIdContext';

import { GLTFModelComponent } from './GLTFModelComponent';

interface EnvironmentModelComponentProps {
component: IModelRefComponentInternal;
node: ISceneNodeInternal;
}

export const EnvironmentModelComponent: React.FC<EnvironmentModelComponentProps> = ({ node, component }) => {
const sceneComposerId = useSceneComposerId();
const { isEditing, cameraControlsType } = useEditorState(sceneComposerId);

if (isEditing()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is cleaner if you invert it:

if (isViewing() {
    return null;
}

return (
   <GLTFModelComponent />
)

Also I'm not sure I agree that the isEditing or cameracControls type properties should be the responsibility of the EnvironmentModelComponent.

I would say it's up the orchestrator component to understand logic around whether this should be visible or not, and the hidden while immersive flag also shouldn't need to be propagated down tot he GLTF model compoennt, since it's just another flag we use to determine if we render this at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I didn't remove when we removed the immersive view feature. If we retackle that again this will be rectified. I can also make a note to perform this extra clean up and get to it when things calm down

return (
<GLTFModelComponent
key={component.ref}
node={node}
component={component}
hiddenWhileImmersive={cameraControlsType === 'immersive'}
/>
);
}

return <></>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import { render } from '@testing-library/react';

import { EnvironmentModelComponent } from '../EnvironmentModelComponent';
import { IModelRefComponentInternal, ISceneNodeInternal, useStore } from '../../../../store';
import { ModelType } from '../../../../models/SceneModels';
import { KnownComponentType } from '../../../../interfaces';

// @ts-ignore
jest.mock('scheduler', () => require('scheduler/unstable_mock'));

jest.mock('../GLTFModelComponent', () => ({
GLTFModelComponent: (props) => <div id={'GLTFModelComponent'} {...props} />,
}));

/* eslint-enable */

describe('EnvironmentModelComponent', () => {
const component: IModelRefComponentInternal = {
uri: 'uri',
modelType: ModelType.GLB,
ref: 'test-ref',
type: KnownComponentType.ModelRef,
};

const node: ISceneNodeInternal = {
ref: 'test-ref',
name: 'test-name',
transform: { position: [0, 0, 0], rotation: [0, 0, 0], scale: [0, 0, 0] },
transformConstraint: {},
components: [component],
childRefs: [],
properties: {},
};

beforeEach(() => {
jest.clearAllMocks();
});

it('should render as expected when editing', () => {
useStore('default').setState({
isEditing: jest.fn().mockReturnValue(true),
});
const { container } = render(<EnvironmentModelComponent component={component} node={node} />);

expect(container).toMatchSnapshot();
});

it('should render as empty when viewing', () => {
useStore('default').setState({
isEditing: jest.fn().mockReturnValue(false),
});
const { container } = render(<EnvironmentModelComponent component={component} node={node} />);

expect(container).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EnvironmentModelComponent should render as empty when viewing 1`] = `<div />`;

exports[`EnvironmentModelComponent should render as expected when editing 1`] = `
<div>
<div
component="[object Object]"
id="GLTFModelComponent"
node="[object Object]"
/>
</div>
`;
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ModelRefComponent should render correctly 1`] = `null`;
exports[`ModelRefComponent should render correctly 1`] = `
<div>
<div
component="[object Object]"
id="GLTFModelComponent"
node="[object Object]"
/>
</div>
`;

exports[`ModelRefComponent should render correctly with Environment Model Type 1`] = `
<div>
<div
component="[object Object]"
id="EnvironmentModelComponent"
node="[object Object]"
/>
</div>
`;

exports[`ModelRefComponent should render correctly with Tiles Model Type 1`] = `
<div>
<div
component="[object Object]"
id="TilesModelComponent"
node="[object Object]"
/>
</div>
`;
Loading