Skip to content

Commit

Permalink
Added snap behavior
Browse files Browse the repository at this point in the history
I'm not happy with the high traffic to the worker, lots of useless snap events
due to the poor event API from `TrackballControls` in three.js, but it is
working fine for the user.
  • Loading branch information
vorth committed Mar 28, 2024
1 parent 4b08670 commit b59bdee
Show file tree
Hide file tree
Showing 19 changed files with 160 additions and 85 deletions.
3 changes: 2 additions & 1 deletion online/src/app/59icosahedra/selectors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const CellSelectorTool = props =>
onDragStart: ( id, position, type, starting, evt ) => {},
onDrag: evt => {},
onDragEnd: evt => {},
onContextMenu: ( id, position, type, selected ) => {}
onContextMenu: ( id, position, type, selected ) => {},
onTrackballEnd: () => {},
};

const [ _, setTool ] = useInteractionTool();
Expand Down
49 changes: 1 addition & 48 deletions online/src/app/classic/classic.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@

import { createSignal, createContext, useContext } from "solid-js";

import { controllerProperty, subController, useEditor } from '../../viewer/context/editor.jsx';
import { subController, useEditor } from '../../viewer/context/editor.jsx';

import { CameraControls } from './components/camera.jsx';
import { StrutBuildPanel } from './components/strutbuilder.jsx';
import { BookmarkBar, ToolBar, ToolFactoryBar } from './components/toolbars.jsx';
import { SceneEditor } from './components/editor.jsx';
import { OrbitsDialog } from "./dialogs/orbits.jsx";
import { ShapesDialog } from "./dialogs/shapes.jsx";
import { PolytopesDialog } from "./dialogs/polytopes.jsx";
import { ErrorAlert } from "./components/alert.jsx";

export const ClassicEditor = () =>
Expand Down Expand Up @@ -51,45 +46,3 @@ export const ClassicEditor = () =>
</div>
)
}

const SymmetryContext = createContext();

export const SymmetryProvider = (props) =>
{
const { rootController, controllerAction } = useEditor();
const symmetry = () => controllerProperty( rootController(), 'symmetry' );
const strutBuilder = () => subController( rootController(), 'strutBuilder' );
const symmController = () => subController( strutBuilder(), `symmetry.${symmetry()}` );

const [ showShapesDialog, setShowShapesDialog ] = createSignal( false );
const [ showOrbitsDialog, setShowOrbitsDialog ] = createSignal( false );
const [ showPolytopesDialog, setShowPolytopesDialog ] = createSignal( false );
const api = {
symmetryDefined: () => !!symmetry(),
symmetryController: () => symmController(),
showShapesDialog: () => setShowShapesDialog( true ),
showOrbitsDialog: () => setShowOrbitsDialog( true ),
showPolytopesDialog: () => {
controllerAction( subController( symmController(), 'polytopes' ), 'setQuaternion' );
setShowPolytopesDialog( true );
},
};

return (
<SymmetryContext.Provider value={api}>

{props.children}

<Show when={!!symmetry()}>
<ShapesDialog controller={symmController()} open={showShapesDialog()} close={ ()=>setShowShapesDialog(false) } />

<OrbitsDialog controller={symmController()} open={showOrbitsDialog()} close={ ()=>setShowOrbitsDialog(false) } />

<PolytopesDialog controller={symmController()} open={showPolytopesDialog()} close={ ()=>setShowPolytopesDialog(false) } />
</Show>

</SymmetryContext.Provider>
);
}

export const useSymmetry = () => useContext( SymmetryContext );
14 changes: 10 additions & 4 deletions online/src/app/classic/components/camera.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ import FormControlLabel from "@suid/material/FormControlLabel";

import { useWorkerClient } from '../../../viewer/context/worker.jsx';
import { useEditor } from '../../../viewer/context/editor.jsx';
import { useSymmetry } from "../context/symmetry.jsx";
import { CameraProvider, useCamera } from '../../../viewer/context/camera.jsx';
import { CameraTool, InteractionToolProvider } from '../../../viewer/context/interaction.jsx';

import { InteractionToolProvider } from '../../../viewer/context/interaction.jsx';
import { SceneCanvas } from '../../../viewer/scenecanvas.jsx';

import { SnapCameraTool } from '../tools/snapcamera.jsx';


export const CameraControls = (props) =>
{
const context = useCamera();
const { isWorkerReady, subscribeFor } = useWorkerClient();
const { rootController, controllerAction } = useEditor();
const { state, setCamera, togglePerspective } = useCamera();
const { snapping, toggleSnapping } = useSymmetry();
const [ scene, setScene ] = createStore( null );

const isPerspective = () => state.camera.perspective;
Expand Down Expand Up @@ -78,14 +81,17 @@ export const CameraControls = (props) =>
<CameraProvider name='trackball' context={context}>
<InteractionToolProvider>
{/* provider and CameraTool just to get the desired cursor */}
<CameraTool/>
<SnapCameraTool/>
<div id='camera-controls' style={{ display: 'grid', 'grid-template-rows': 'min-content min-content' }}>
<Stack spacing={2} direction="row" style={{ padding: '8px' }}>
<FormControlLabel label="perspective"
control={
<Switch checked={isPerspective()} onChange={togglePerspective} size='small' inputProps={{ "aria-label": "controlled" }} />
}/>
<div id='snap-switch' class='placeholder' >snap</div>
<FormControlLabel label="snap"
control={
<Switch checked={snapping()} onChange={toggleSnapping} size='small' inputProps={{ "aria-label": "controlled" }} />
}/>
<div id='outlines-switch' class='placeholder' >outlines</div>
</Stack>

Expand Down
7 changes: 4 additions & 3 deletions online/src/app/classic/components/editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { LabelDialog } from '../dialogs/label.jsx';

import { useCamera } from '../../../viewer/context/camera.jsx';
import { useViewer } from '../../../viewer/context/viewer.jsx';
import { CameraTool, InteractionToolProvider } from '../../../viewer/context/interaction.jsx';
import { InteractionToolProvider } from '../../../viewer/context/interaction.jsx';
import { useEditor } from '../../../viewer/context/editor.jsx';

import { SceneCanvas } from '../../../viewer/index.jsx';

import { SnapCameraTool } from '../tools/snapcamera.jsx';
import { SelectionTool } from '../tools/selection.jsx';
import { StrutDragTool } from '../tools/strutdrag.jsx';
import { ContextualMenuArea, resumeMenuKeyEvents, suspendMenuKeyEvents } from '../../framework/menus.jsx';
Expand Down Expand Up @@ -109,7 +110,7 @@ export const SceneEditor = ( props ) =>
<SelectionTool/>
}>
<Match when={ viewing() }>
<CameraTool/>
<SnapCameraTool/>
</Match>
<Match when={ strutting() }>
<StrutDragTool/>
Expand Down
2 changes: 1 addition & 1 deletion online/src/app/classic/components/strutbuilder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { controllerProperty, subController, useEditor } from '../../../viewer/co

import { StrutLengthPanel } from './length.jsx';
import { OrbitPanel } from './orbitpanel.jsx';
import { useSymmetry } from "../classic.jsx";
import { useSymmetry } from "../context/symmetry.jsx";

export const StrutBuildPanel = () =>
{
Expand Down
2 changes: 1 addition & 1 deletion online/src/app/classic/components/toolbars.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { createSignal, createEffect } from "solid-js";

import { controllerProperty, subController, useEditor } from '../../../viewer/context/editor.jsx';
import { useSymmetry } from "../classic.jsx";
import { useSymmetry } from "../context/symmetry.jsx";
import { ToolConfig } from "../dialogs/toolconfig.jsx";

const ToolbarSpacer = () => ( <div style={{ 'min-width': '10px', 'min-height': '10px' }}></div> )
Expand Down
74 changes: 74 additions & 0 deletions online/src/app/classic/context/symmetry.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

import { createContext, createSignal, useContext } from "solid-js";

import { controllerProperty, subController, useEditor } from "../../../viewer/context/editor.jsx";
import { useCamera } from "../../../viewer/context/camera.jsx";
import { useWorkerClient } from "../../../viewer/context/worker.jsx";

import { ShapesDialog } from "../dialogs/shapes.jsx";
import { OrbitsDialog } from "../dialogs/orbits.jsx";
import { PolytopesDialog } from "../dialogs/polytopes.jsx";
import { unwrap } from "solid-js/store";


const SymmetryContext = createContext();

export const SymmetryProvider = (props) =>
{
const { subscribeFor } = useWorkerClient();
const { state, setCamera } = useCamera();
const { rootController, controllerAction } = useEditor();

const symmetry = () => controllerProperty( rootController(), 'symmetry' );
const strutBuilder = () => subController( rootController(), 'strutBuilder' );
const symmController = () => subController( strutBuilder(), `symmetry.${symmetry()}` );

const [ showShapesDialog, setShowShapesDialog ] = createSignal( false );
const [ showOrbitsDialog, setShowOrbitsDialog ] = createSignal( false );
const [ showPolytopesDialog, setShowPolytopesDialog ] = createSignal( false );

const [ snapping, setSnapping ] = createSignal( false );
const snapCamera = () =>
{
if ( snapping() ) {
const { up, lookDir } = state.camera;
controllerAction( symmController(), 'snapCamera', { up: unwrap(up), lookDir: unwrap(lookDir) } );
}
}

const api = {
snapping, snapCamera,
toggleSnapping: () => setSnapping( v => !v ),
symmetryDefined: () => !!symmetry(),
symmetryController: () => symmController(),
showShapesDialog: () => setShowShapesDialog( true ),
showOrbitsDialog: () => setShowOrbitsDialog( true ),
showPolytopesDialog: () => {
controllerAction( subController( symmController(), 'polytopes' ), 'setQuaternion' );
setShowPolytopesDialog( true );
},
};

subscribeFor( 'CAMERA_SNAPPED', ( data ) => {
const { up, lookDir } = data;
setCamera( data );
})

return (
<SymmetryContext.Provider value={api}>

{props.children}

<Show when={!!symmetry()}>
<ShapesDialog controller={symmController()} open={showShapesDialog()} close={ ()=>setShowShapesDialog(false) } />

<OrbitsDialog controller={symmController()} open={showOrbitsDialog()} close={ ()=>setShowOrbitsDialog(false) } />

<PolytopesDialog controller={symmController()} open={showPolytopesDialog()} close={ ()=>setShowPolytopesDialog(false) } />
</Show>

</SymmetryContext.Provider>
);
}

export const useSymmetry = () => useContext( SymmetryContext );
3 changes: 2 additions & 1 deletion online/src/app/classic/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { ViewerProvider } from '../../viewer/context/viewer.jsx';
import { CameraProvider } from '../../viewer/context/camera.jsx';

import { VZomeAppBar } from './components/appbar.jsx';
import { ClassicEditor, SymmetryProvider } from './classic.jsx';
import { ClassicEditor } from './classic.jsx';
import { SymmetryProvider } from './context/symmetry.jsx';

const Persistence = () =>
{
Expand Down
2 changes: 1 addition & 1 deletion online/src/app/classic/menus/systemmenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Choices, Divider, Menu, MenuAction, createCheckboxItem } from "../../framework/menus.jsx";

import { controllerProperty, useEditor } from "../../../viewer/context/editor.jsx";
import { useSymmetry } from "../classic.jsx";
import { useSymmetry } from "../context/symmetry.jsx";

export const SystemMenu = () =>
{
Expand Down
2 changes: 1 addition & 1 deletion online/src/app/classic/menus/toolsmenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { mergeProps } from "solid-js";
import { Divider, Menu, MenuAction, createMenuAction } from "../../framework/menus.jsx";

import { useSymmetry } from "../classic.jsx";
import { useSymmetry } from "../context/symmetry.jsx";
import { useEditor } from "../../../viewer/context/editor.jsx";

export const SymmetryAction = ( props ) =>
Expand Down
17 changes: 17 additions & 0 deletions online/src/app/classic/tools/snapcamera.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

import { onMount } from "solid-js";

import { useInteractionTool, grabTool } from "../../../viewer/context/interaction.jsx";
import { useSymmetry } from "../context/symmetry.jsx";


export const SnapCameraTool = props =>
{
const { snapCamera } = useSymmetry();

const [ _, setTool ] = useInteractionTool();
const cursor = 'url(rotate.svg) 9 9, url(rotate.png) 9 9, grab';
onMount( () => setTool( { ...grabTool, cursor, onTrackballEnd: snapCamera } ) );

return null;
}
4 changes: 2 additions & 2 deletions online/src/app/classic/tools/trackball.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import { Show, createEffect, createSignal, onCleanup } from "solid-js";
import { Matrix4, Quaternion, Vector3 } from 'three';
import { useFrame, useThree } from "solid-three";
import { DragHandler } from "./drag";
import { VectorArrow } from "./arrow";
import { DragHandler } from "./drag.ts";
import { VectorArrow } from "./arrow.jsx";

const defaultDrag = {
direction: [ 1, 0 ],
Expand Down
1 change: 1 addition & 0 deletions online/src/viewer/context/editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const EditorProvider = props =>
case 'FETCH_STARTED':
case 'TRACKBALL_SCENE_LOADED':
case 'SCENES_DISCOVERED':
case 'CAMERA_SNAPPED':
// TODO: do these require any state changes?
break;

Expand Down
18 changes: 5 additions & 13 deletions online/src/viewer/context/interaction.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

import { createContext, createEffect, createSignal, useContext } from "solid-js";
import { createContext, createSignal, useContext } from "solid-js";

const InteractionToolContext = createContext( [] );

const grabTool = {
export const grabTool = {

allowTrackball: true, // THIS is the reason this component exists

Expand All @@ -13,7 +13,8 @@ const grabTool = {
bkgdClick: () => {},
onDragStart: () => {},
onDrag: evt => {},
onDragEnd: evt => {}
onDragEnd: evt => {},
onTrackballEnd: () => {},
};

const InteractionToolProvider = (props) =>
Expand All @@ -29,13 +30,4 @@ const InteractionToolProvider = (props) =>

const useInteractionTool = () => { return useContext( InteractionToolContext ); };

const CameraTool = props =>
{
const [ _, setTool ] = useInteractionTool();
const cursor = 'url(rotate.svg) 9 9, url(rotate.png) 9 9, grab';
createEffect( () => setTool( { ...grabTool, cursor } ) );

return null;
}

export { InteractionToolProvider, useInteractionTool, CameraTool };
export { InteractionToolProvider, useInteractionTool };
4 changes: 3 additions & 1 deletion online/src/viewer/ltcanvas.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const LightedCameraControls = (props) =>
props = mergeProps( { rotateSpeed: 4.5, zoomSpeed: 3, panSpeed: 1 }, props );
const halfWidth = () => perspectiveProps.width / 2;

const onDragEnd = () => ( tool !== undefined ) && tool() .onTrackballEnd();

return (
<>
<Show when={state.camera.perspective} fallback={
Expand All @@ -55,7 +57,7 @@ const LightedCameraControls = (props) =>
</PerspectiveCamera>
</Show>
<TrackballControls enabled={enableTrackball()} rotationOnly={props.rotationOnly} name={name}
camera={trackballProps.camera} target={perspectiveProps.target} sync={trackballProps.sync}
camera={trackballProps.camera} target={perspectiveProps.target} sync={trackballProps.sync} trackballEnd={onDragEnd}
rotateSpeed={props.rotateSpeed} zoomSpeed={props.zoomSpeed} panSpeed={props.panSpeed} />
</>
);
Expand Down
4 changes: 4 additions & 0 deletions online/src/viewer/trackballcontrols.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export const TrackballControls = (props) =>

const onChange = () => props.sync( controls .target, props.name );
controls .addEventListener( "change", onChange );
const onEnd = () => {
props.trackballEnd && props.trackballEnd( controls .target, props.name );
}
controls .addEventListener( "end", onEnd );

onCleanup(() => {
controls .removeEventListener( "change", onChange );
Expand Down
Loading

0 comments on commit b59bdee

Please sign in to comment.