Skip to content

Commit

Permalink
Merge pull request #842 from vorth/snap
Browse files Browse the repository at this point in the history
Added snap behavior
  • Loading branch information
vorth authored Mar 28, 2024
2 parents 4b08670 + b59bdee commit 750165e
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 750165e

Please sign in to comment.