@tomorrowevening/hermes 0.0.36 → 0.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/hermes.cjs.js +16 -16
  2. package/dist/hermes.esm.js +1446 -1498
  3. package/dist/hermes.umd.js +16 -16
  4. package/dist/style.css +1 -1
  5. package/package.json +2 -1
  6. package/src/core/Application.ts +111 -0
  7. package/src/core/RemoteController.ts +60 -0
  8. package/src/core/remote/BaseRemote.ts +16 -0
  9. package/src/core/remote/RemoteComponents.ts +45 -0
  10. package/src/core/remote/RemoteTheatre.ts +300 -0
  11. package/src/core/remote/RemoteThree.ts +143 -0
  12. package/src/core/remote/RemoteTweakpane.ts +194 -0
  13. package/src/core/types.ts +56 -0
  14. package/src/editor/Editor.tsx +20 -0
  15. package/src/editor/components/Draggable.tsx +40 -0
  16. package/src/editor/components/DraggableItem.tsx +22 -0
  17. package/src/editor/components/Dropdown.tsx +38 -0
  18. package/src/editor/components/DropdownItem.tsx +64 -0
  19. package/src/editor/components/NavButton.tsx +11 -0
  20. package/src/editor/components/content.ts +2 -0
  21. package/src/editor/components/icons/CloseIcon.tsx +7 -0
  22. package/src/editor/components/icons/DragIcon.tsx +9 -0
  23. package/src/editor/components/types.ts +41 -0
  24. package/src/editor/global.ts +20 -0
  25. package/src/editor/multiView/CameraWindow.tsx +74 -0
  26. package/src/editor/multiView/InfiniteGridHelper.ts +24 -0
  27. package/src/editor/multiView/InfiniteGridMaterial.ts +127 -0
  28. package/src/editor/multiView/MultiView.scss +101 -0
  29. package/src/editor/multiView/MultiView.tsx +636 -0
  30. package/src/editor/multiView/MultiViewData.ts +59 -0
  31. package/src/editor/multiView/UVMaterial.ts +55 -0
  32. package/src/editor/scss/_debug.scss +58 -0
  33. package/src/editor/scss/_draggable.scss +43 -0
  34. package/src/editor/scss/_dropdown.scss +84 -0
  35. package/src/editor/scss/_sidePanel.scss +278 -0
  36. package/src/editor/scss/_theme.scss +9 -0
  37. package/src/editor/scss/index.scss +67 -0
  38. package/src/editor/sidePanel/Accordion.tsx +41 -0
  39. package/src/editor/sidePanel/ChildObject.tsx +57 -0
  40. package/src/editor/sidePanel/ContainerObject.tsx +11 -0
  41. package/src/editor/sidePanel/SidePanel.tsx +64 -0
  42. package/src/editor/sidePanel/ToggleBtn.tsx +27 -0
  43. package/src/editor/sidePanel/inspector/Inspector.tsx +119 -0
  44. package/src/editor/sidePanel/inspector/InspectorField.tsx +198 -0
  45. package/src/editor/sidePanel/inspector/InspectorGroup.tsx +50 -0
  46. package/src/editor/sidePanel/inspector/SceneInspector.tsx +84 -0
  47. package/src/editor/sidePanel/inspector/inspector.scss +161 -0
  48. package/src/editor/sidePanel/inspector/utils/InspectAnimation.tsx +102 -0
  49. package/src/editor/sidePanel/inspector/utils/InspectCamera.tsx +75 -0
  50. package/src/editor/sidePanel/inspector/utils/InspectLight.tsx +62 -0
  51. package/src/editor/sidePanel/inspector/utils/InspectMaterial.tsx +710 -0
  52. package/src/editor/sidePanel/inspector/utils/InspectTransform.tsx +113 -0
  53. package/src/editor/sidePanel/types.ts +130 -0
  54. package/src/editor/sidePanel/utils.ts +278 -0
  55. package/src/editor/utils.ts +117 -0
  56. package/src/example/CustomEditor.tsx +78 -0
  57. package/src/example/components/App.css +6 -0
  58. package/src/example/components/App.tsx +246 -0
  59. package/src/example/constants.ts +52 -0
  60. package/src/example/index.scss +45 -0
  61. package/src/example/main.tsx +37 -0
  62. package/src/example/three/BaseScene.ts +42 -0
  63. package/src/example/three/CustomMaterial.ts +72 -0
  64. package/src/example/three/FBXAnimation.ts +26 -0
  65. package/src/example/three/Scene1.ts +225 -0
  66. package/src/example/three/Scene2.ts +138 -0
  67. package/src/example/three/loader.ts +110 -0
  68. package/src/index.ts +27 -0
  69. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,40 @@
1
+ // Libs
2
+ import { useState } from 'react';
3
+ import { Reorder } from 'framer-motion';
4
+ // Components
5
+ import NavButton from './NavButton';
6
+ import DraggableItem from './DraggableItem';
7
+ import { DraggableProps } from './types';
8
+
9
+ export default function Draggable(props: DraggableProps) {
10
+ const [expanded, setExpanded] = useState(false);
11
+ const [list, setList] = useState<string[]>(props.options);
12
+
13
+ const updateList = (updated: string[]) => {
14
+ props.onDragComplete(updated);
15
+ setList(updated);
16
+ };
17
+
18
+ const onDelete = (index: number) => {
19
+ const newArray = [...list];
20
+ newArray.splice(index, 1);
21
+ updateList(newArray);
22
+ };
23
+
24
+ const elements: any[] = [];
25
+ list.forEach((value: string, index: number) => {
26
+ elements.push(<DraggableItem key={value} index={index} title={value} onDelete={onDelete} />);
27
+ });
28
+
29
+ let ddClassName = 'dropdown draggable';
30
+ if (props.subdropdown) ddClassName += ' subdropdown';
31
+
32
+ return (
33
+ <div className={ddClassName} onMouseEnter={() => setExpanded(true)} onMouseLeave={() => setExpanded(false)}>
34
+ <NavButton title={props.title} />
35
+ <Reorder.Group axis='y' values={list} onReorder={updateList} style={{ visibility: expanded ? 'visible' : 'hidden' }}>
36
+ {elements}
37
+ </Reorder.Group>
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,22 @@
1
+ // Libs
2
+ import { Reorder } from 'framer-motion';
3
+ // Components
4
+ import CloseIcon from './icons/CloseIcon';
5
+ import DragIcon from './icons/DragIcon';
6
+ import { DraggableItemProps } from './types';
7
+
8
+ export default function DraggableItem(props: DraggableItemProps) {
9
+ return (
10
+ <Reorder.Item key={props.title} value={props.title}>
11
+ <div>
12
+ {DragIcon}
13
+ <span>{props.title}</span>
14
+ <button className='closeIcon' onClick={() => {
15
+ props.onDelete(props.index);
16
+ }}>
17
+ {CloseIcon}
18
+ </button>
19
+ </div>
20
+ </Reorder.Item>
21
+ );
22
+ }
@@ -0,0 +1,38 @@
1
+ // Libs
2
+ import { useState } from 'react';
3
+ // Views
4
+ import NavButton from './NavButton';
5
+ import DropdownItem from './DropdownItem';
6
+ import { DropdownOption, DropdownProps } from './types';
7
+
8
+ export default function Dropdown(props: DropdownProps) {
9
+ const [expanded, setExpanded] = useState(false);
10
+
11
+ const list: Array<any> = [];
12
+ {
13
+ props.options.map((option: DropdownOption, index: number) => {
14
+ if (props.onSelect !== undefined) {
15
+ option.onSelect = props.onSelect;
16
+ }
17
+ list.push(<DropdownItem option={option} key={index} />);
18
+ });
19
+ }
20
+
21
+ let ddClassName = 'dropdown';
22
+ if (props.subdropdown) ddClassName += ' subdropdown';
23
+
24
+ return (
25
+ <div
26
+ className={ddClassName}
27
+ onMouseEnter={() => setExpanded(true)}
28
+ onMouseLeave={() => setExpanded(false)}
29
+ >
30
+ <NavButton title={props.title} />
31
+ <ul
32
+ style={{ visibility: expanded ? 'visible' : 'hidden' }}
33
+ >
34
+ {list}
35
+ </ul>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,64 @@
1
+ // Libs
2
+ import { useState } from 'react';
3
+ // Components
4
+ import Draggable from './Draggable';
5
+ import Dropdown from './Dropdown';
6
+ import { DropdownItemProps, DropdownOption } from './types';
7
+ // Utils
8
+ import { randomID } from '../utils';
9
+
10
+ export default function DropdownItem(props: DropdownItemProps) {
11
+ const { option } = props;
12
+ const [selected, setSelected] = useState('');
13
+
14
+ let element;
15
+ switch (option.type) {
16
+ case 'draggable':
17
+ element = (
18
+ <Draggable
19
+ title={option.title}
20
+ options={option.value as Array<string>}
21
+ onDragComplete={(options: string[]) => {
22
+ if (option.onDragComplete !== undefined) option.onDragComplete(options);
23
+ }}
24
+ subdropdown={true}
25
+ />
26
+ );
27
+ break;
28
+ case 'dropdown':
29
+ element = (
30
+ <Dropdown
31
+ title={option.title}
32
+ options={option.value as Array<DropdownOption>}
33
+ onSelect={option.onSelect}
34
+ subdropdown={true}
35
+ />
36
+ );
37
+ break;
38
+ case 'option':
39
+ element = (
40
+ <button
41
+ onClick={() => {
42
+ if (option.onSelect !== undefined) option.onSelect(option.value);
43
+ // Toggle selectable
44
+ if (option.selectable) {
45
+ if (selected !== option.title) {
46
+ setSelected(option.title);
47
+ } else {
48
+ setSelected('');
49
+ }
50
+ }
51
+ }}
52
+ >
53
+ {option.title}
54
+ </button>
55
+ );
56
+ break;
57
+ }
58
+
59
+ return (
60
+ <li className={selected === option.title ? 'selected' : ''} key={randomID()}>
61
+ {element}
62
+ </li>
63
+ );
64
+ }
@@ -0,0 +1,11 @@
1
+ type NavButtonProps = {
2
+ title: string
3
+ }
4
+
5
+ export default function NavButton(props: NavButtonProps) {
6
+ return props.title.search('<') > -1 ? (
7
+ <button className='svg' dangerouslySetInnerHTML={{ __html: props.title }}></button>
8
+ ) : (
9
+ <button>{props.title}</button>
10
+ );
11
+ }
@@ -0,0 +1,2 @@
1
+ export const gridImage = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABkCAYAAAA8AQ3AAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEoklEQVR4nO3aQc4aMRAFYR+AOQv3Px3ZRYoSoh8YxvX61YJdSdPuD++81lqPZ7/b7fa43++P4zieNnbPu+M40PO1dXqwujc9WIeY0nk5WJ0erO4DD84hpnReDlanB6v70INxiCmdl4PV6cHqTvDYf4gpnZeD1enB6k7yyDgsvfNysDo9WN2JHvzD0jsvB6vTg9Wd6kE/LL3zcrA6PVjd2R6LfFh65+VgdXqwum94LOph6Z2Xg9Xpweq+5bGIh6V3Xg5Wpwer+7IH67D0zsvB6vRgdRd4cA5L77wcrE4PVneRB+Ow9M7Lwer0YHUXeuw/LL3zcrA6PVjdxR4ZSynBsNMjqtvgwV9KEYadHjHdFg/6Uqow7PQI6XZ5+NIdhGGnR0K308OX7iAMOz3o3W6PRVxKK4adHuQO4sFaSjmGnR7IDuTBWYoYdnrwOpgHYyli2OnB64Ae+5cihp0evA7qkbG8EozaTg9WB/bgL68Io7LTg9WhPejLq8Io7PRgdXSPqpfudIy2Tg9Wl+BR89J9F0bCn6BpL3qw9vLqdxdxebuXkv5deqcHqwvzYC0PspTY79I7PVhdoAdneaClRH6X3unB6kI9GMuDLSXuu/ROD1YX7LF/ecClRH2X3unB6sI9MpZcgjGu04PVDfDgL7kIY1SnB6sb4UFfchXGoE4PVjfFY8RL9ykYUzo9WN0kj/iX7pMwJnR6sLppHou45N1LoX6X3unB6oZ6sJYMWQruu/ROD1Y32IOzZNBSUN+ld3qwuuEejCXTOy8Hq9OD1V3osf+w9M7Lwer0YHUXe2QspQTDTo+oboMHfylFGHZ6xHRbPOhLqcKw0yOk2+Ux4qX7FAw7PRK6nR7xL90nYdjpQe92eyziUlox7PQgdxAP1lLKMez0QHYgD85SxLDTg9fBPBhLEcNOD14H9Ni/FDHs9OB1UI+M5ZVg1HZ6sDqwB395RRiVnR6sDu1BX14VRmGnB6uje1S9dKdjtHV6sLoEj5qX7gkYTZ0erC7FY5GHa8No6fRgdWEe6OHaMMZ3erC6QA/0cG0Yozs9WF2oB3q4NoyxnR6sLtgDPVwbxshOD1YX7oEerg1jXKcHqxvggR6uDWNUpwerG+GBHq4NY1CnB6ub4jHipfsUjCmdHqxukkf8S/dJGBM6PVjdNI9FHq4NI73Tg9UN9UAP14YR2+nB6gZ7oIdrw4js9GB1wz3Qw7VhxHV6sLoCD/RwbRhRnR6srsQDPVwbRkynB6sr8kAP14YR0enB6qo80MO1YQR0erC6Ng/0S/c2DHqnB6tr9MC+dG/EIHd6sLpWj0UcrhWD2unB6so9WMOVY+A6PVidHqDhxGB1erA6PdZjUYYTg9Xpwer0+P3bP5wYrE4PVqfHHz8x7PSgdnr89RPDTg9ip8c/fmLY6cHr9HjyE8NOD1anx/Pu8pfuYrA6PVidHv/vlhi9nR6sTo8fdWI0dnqwOj1+3InR1unB6vR4qROjqdOD1enxcidGS6cHq9PjrU6Mhk4PVqfH250Y0zs9WJ0eH3RizO70YHV6fNad+tJdDFanB6vT4/PutJfuYrA6PVidHud0vwA7LKl1AiiR5gAAAABJRU5ErkJggg==`;
2
+ export const noImage = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA5klEQVRoge2Y0Q6EIAwE6cX//+X6cCFpSMEKVTdk501OpRNKiyelFC0b8Ps6gCwoggZF0KAIGhRBgyJoUAQNiqCxjciR9SLV//eZiAyvK3U8i/QVaQO2YyLSFVvlkdTKDjJCukh2ykR5ZEW+kHmlatl90RaBtDkK/w7CYhuRUEO0ee3l+J3m55Vm+17vtwjTnV1V3QA8qfbeUXCzRWDpiLLS+OyzvRW7IzW9R+okvclsqR09743bo0yUpc1+lSJvNsa002+Euk9GKzV7SmZDRIMiaFAEDYqgQRE0KIIGRdCgCBoUQeMEMERadX7YUz8AAAAASUVORK5CYII=`;
@@ -0,0 +1,7 @@
1
+ export default (
2
+ <svg className='closeIcon' width='14' height='14' fill='none' stroke='#666666' strokeMiterlimit='10'>
3
+ <circle cx='7' cy='7' r='6' />
4
+ <line x1='4' y1='4' x2='10' y2='10' />
5
+ <line x1='4' y1='10' x2='10' y2='4' />
6
+ </svg>
7
+ );
@@ -0,0 +1,9 @@
1
+ export default (
2
+ <svg className='dragIcon' width='14' height='14' fill='#666666' stroke='none'>
3
+ <path
4
+ d='M10.43,4H3.57C3.26,4,3,4.22,3,4.5v1C3,5.78,3.26,6,3.57,6h6.86C10.74,6,11,5.78,11,5.5v-1
5
+ C11,4.22,10.74,4,10.43,4z M10.43,8H3.57C3.26,8,3,8.22,3,8.5v1C3,9.78,3.26,10,3.57,10h6.86C10.74,10,11,9.78,11,9.5v-1
6
+ C11,8.22,10.74,8,10.43,8z'
7
+ />
8
+ </svg>
9
+ );
@@ -0,0 +1,41 @@
1
+ export type DropdownType = 'option' | 'dropdown' | 'draggable'
2
+
3
+ export interface DropdownOption {
4
+ title: string
5
+ value: any | Array<DropdownOption>
6
+ type: DropdownType
7
+ // Option
8
+ onSelect?: (value: any) => void
9
+ selectable?: boolean
10
+ // Draggable
11
+ onDragComplete?: (options: Array<string>) => void
12
+ }
13
+
14
+ export interface DropdownProps {
15
+ title: string
16
+ options: Array<DropdownOption>
17
+ onSelect?: (value: any) => void
18
+ subdropdown?: boolean
19
+ }
20
+
21
+ export interface DropdownItemProps {
22
+ option: DropdownOption
23
+ onSelect?: (value: any) => void
24
+ // Draggable
25
+ onDragComplete?: (options: Array<string>) => void
26
+ }
27
+
28
+ // Draggable
29
+
30
+ export interface DraggableItemProps {
31
+ index: number
32
+ title: string
33
+ onDelete: (index: number) => void
34
+ }
35
+
36
+ export interface DraggableProps {
37
+ title: string
38
+ options: Array<string>
39
+ onDragComplete: (options: Array<string>) => void
40
+ subdropdown?: boolean
41
+ }
@@ -0,0 +1,20 @@
1
+ import { EventDispatcher } from 'three';
2
+
3
+ export const debugDispatcher = new EventDispatcher();
4
+
5
+ export const ToolEvents = {
6
+ CUSTOM: 'ToolEvents::custom',
7
+ // Components
8
+ SELECT_DROPDOWN: 'ToolEvents::selectDropdown',
9
+ DRAG_UPDATE: 'ToolEvents::dragUpdate',
10
+ // SceneHierarchy
11
+ SET_SCENE: 'ToolEvents::setScene',
12
+ GET_OBJECT: 'ToolEvents::getObject',
13
+ SET_OBJECT: 'ToolEvents::setObject',
14
+ UPDATE_OBJECT: 'ToolEvents::updateObject',
15
+ CREATE_TEXTURE: 'ToolEvents::createTexture',
16
+ REQUEST_METHOD: 'ToolEvents::requestMethod',
17
+ // MultiView
18
+ ADD_CAMERA: 'ToolEvents::addCamera',
19
+ REMOVE_CAMERA: 'ToolEvents::removeCamera',
20
+ };
@@ -0,0 +1,74 @@
1
+ import { ForwardedRef, forwardRef, useState } from 'react';
2
+ import { Camera } from 'three';
3
+
4
+ interface DropdownProps {
5
+ index: number;
6
+ open: boolean;
7
+ onToggle: (value: boolean) => void;
8
+ onSelect: (value: string) => void;
9
+ options: string[];
10
+ up?: boolean;
11
+ }
12
+
13
+ export const Dropdown = (props: DropdownProps) => {
14
+ const [selectedOption, setSelectedOption] = useState(props.options[props.index]);
15
+
16
+ const handleToggle = () => {
17
+ props.onToggle(!props.open);
18
+ };
19
+
20
+ const handleSelect = (option: any) => {
21
+ if (option !== selectedOption) {
22
+ props.onSelect(option);
23
+ setSelectedOption(option);
24
+ }
25
+ props.onToggle(false);
26
+ };
27
+
28
+ return (
29
+ <div className={`dropdown ${props.up === true ? 'up' : ''}`}>
30
+ <div className='dropdown-toggle' onClick={handleToggle}>
31
+ {selectedOption}
32
+ </div>
33
+ {props.open && (
34
+ <ul className='dropdown-menu'>
35
+ {props.options.map((option) => (
36
+ <li key={option} onClick={() => handleSelect(option)}>
37
+ {option}
38
+ </li>
39
+ ))}
40
+ </ul>
41
+ )}
42
+ </div>
43
+ );
44
+ };
45
+
46
+ interface CameraWindowProps {
47
+ camera: Camera
48
+ onSelect: (value: string) => void;
49
+ options: string[];
50
+ }
51
+
52
+ const CameraWindow = forwardRef(function CameraWindow(props: CameraWindowProps, ref: ForwardedRef<HTMLDivElement>) {
53
+ const [open, setOpen] = useState(false);
54
+ const index = props.options.indexOf(props.camera.name);
55
+ return (
56
+ <div className='CameraWindow'>
57
+ <div ref={ref} className='clickable' onClick={() => {
58
+ if (open) setOpen(false);
59
+ }} />
60
+ <Dropdown
61
+ index={index}
62
+ open={open}
63
+ options={props.options}
64
+ onSelect={props.onSelect}
65
+ onToggle={(value: boolean) => {
66
+ setOpen(value);
67
+ }}
68
+ up={true}
69
+ />
70
+ </div>
71
+ );
72
+ });
73
+
74
+ export default CameraWindow;
@@ -0,0 +1,24 @@
1
+ import { Mesh, PlaneGeometry } from 'three';
2
+ import InfiniteGridMaterial from './InfiniteGridMaterial';
3
+
4
+ /**
5
+ * Copied from:
6
+ * https://github.com/theatre-js/theatre/blob/main/packages/r3f/src/extension/InfiniteGridHelper/index.ts
7
+ */
8
+
9
+ export default class InfiniteGridHelper extends Mesh {
10
+ gridMaterial: InfiniteGridMaterial;
11
+
12
+ constructor() {
13
+ const material = new InfiniteGridMaterial();
14
+ super(new PlaneGeometry(2, 2), material);
15
+ this.gridMaterial = material;
16
+ this.frustumCulled = false;
17
+ this.name = 'InfiniteGridHelper';
18
+ this.position.y = 0.1;
19
+ }
20
+
21
+ update() {
22
+ this.gridMaterial.needsUpdate = true;
23
+ }
24
+ }
@@ -0,0 +1,127 @@
1
+ import { Color, DoubleSide, GLSL3, ShaderMaterial } from 'three';
2
+
3
+ type InfiniteGridProps = {
4
+ divisions?: number
5
+ scale?: number
6
+ color?: Color
7
+ distance?: number
8
+ subgridOpacity?: number
9
+ gridOpacity?: number
10
+ }
11
+
12
+ const vertex = `out vec3 worldPosition;
13
+ uniform float uDistance;
14
+
15
+ void main() {
16
+ // Scale the plane by the drawing distance
17
+ worldPosition = position.xzy * uDistance;
18
+ worldPosition.xz += cameraPosition.xz;
19
+
20
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(worldPosition, 1.0);
21
+ }`;
22
+
23
+ const fragment = `out vec4 fragColor;
24
+ in vec3 worldPosition;
25
+
26
+ uniform float uDivisions;
27
+ uniform float uScale;
28
+ uniform vec3 uColor;
29
+ uniform float uDistance;
30
+ uniform float uSubgridOpacity;
31
+ uniform float uGridOpacity;
32
+
33
+ float getGrid(float gapSize) {
34
+ vec2 worldPositionByDivision = worldPosition.xz / gapSize;
35
+
36
+ // Inverted, 0 where line, >1 where there's no line
37
+ // We use the worldPosition (which in this case we use similarly to UVs) differential to control the anti-aliasing
38
+ // We need to do the -0.5)-0.5 trick because the result fades out from 0 to 1, and we want both
39
+ // worldPositionByDivision == 0.3 and worldPositionByDivision == 0.7 to result in the same fade, i.e. 0.3,
40
+ // otherwise only one side of the line will be anti-aliased
41
+ vec2 grid = abs(fract(worldPositionByDivision-0.5)-0.5) / fwidth(worldPositionByDivision) / 2.0;
42
+ float gridLine = min(grid.x, grid.y);
43
+
44
+ // Uninvert and clamp
45
+ return 1.0 - min(gridLine, 1.0);
46
+ }
47
+
48
+ void main() {
49
+ float cameraDistanceToGridPlane = distance(cameraPosition.y, worldPosition.y);
50
+ float cameraDistanceToFragmentOnGridPlane = distance(cameraPosition.xz, worldPosition.xz);
51
+
52
+ // The size of the grid and subgrid are powers of each other and they are determined based on camera distance.
53
+ // The current grid will become the next subgrid when it becomes too small, and its next power becomes the new grid.
54
+ float subGridPower = pow(uDivisions, floor(log(cameraDistanceToGridPlane) / log(uDivisions)));
55
+ float gridPower = subGridPower * uDivisions;
56
+
57
+ // If we want to fade both the grid and its subgrid, we need to displays 3 different opacities, with the next grid being the third
58
+ float nextGridPower = gridPower * uDivisions;
59
+
60
+ // 1 where grid, 0 where no grid
61
+ float subgrid = getGrid(subGridPower * uScale);
62
+ float grid = getGrid(gridPower * uScale);
63
+ float nextGrid = getGrid(nextGridPower * uScale);
64
+
65
+ // Where we are between the introduction of the current grid power and when we switch to the next grid power
66
+ float stepPercentage = (cameraDistanceToGridPlane - subGridPower)/(gridPower - subGridPower);
67
+
68
+ // The last x percentage of the current step over which we want to fade
69
+ float fadeRange = 0.3;
70
+
71
+ // We calculate the fade percentage from the step percentage and the fade range
72
+ float fadePercentage = max(stepPercentage - 1.0 + fadeRange, 0.0) / fadeRange;
73
+
74
+ // Set base opacity based on how close we are to the drawing distance, with a cubic falloff
75
+ float baseOpacity = subgrid * pow(1.0 - min(cameraDistanceToFragmentOnGridPlane / uDistance, 1.0), 3.0);
76
+
77
+ // Shade the subgrid
78
+ fragColor = vec4(uColor.rgb, (baseOpacity - fadePercentage) * uSubgridOpacity);
79
+
80
+ // Somewhat arbitrary additional fade coefficient to counter anti-aliasing popping when switching between grid powers
81
+ float fadeCoefficient = 0.5;
82
+
83
+ // Shade the grid
84
+ fragColor.a = mix(fragColor.a, baseOpacity * uGridOpacity - fadePercentage * (uGridOpacity - uSubgridOpacity) * fadeCoefficient, grid);
85
+
86
+ // Shade the next grid
87
+ fragColor.a = mix(fragColor.a, baseOpacity * uGridOpacity, nextGrid);
88
+
89
+ if (fragColor.a <= 0.0) discard;
90
+ }`;
91
+
92
+ export default class InfiniteGridMaterial extends ShaderMaterial {
93
+ constructor(props?: InfiniteGridProps) {
94
+ super({
95
+ extensions: {
96
+ derivatives: true,
97
+ },
98
+ glslVersion: GLSL3,
99
+ side: DoubleSide,
100
+ transparent: true,
101
+ uniforms: {
102
+ uScale: {
103
+ value: props?.scale !== undefined ? props?.scale : 0.1,
104
+ },
105
+ uDivisions: {
106
+ value: props?.divisions !== undefined ? props?.divisions : 10,
107
+ },
108
+ uColor: {
109
+ value: props?.color !== undefined ? props?.color : new Color(0xffffff),
110
+ },
111
+ uDistance: {
112
+ value: props?.distance !== undefined ? props?.distance : 10000,
113
+ },
114
+ uSubgridOpacity: {
115
+ value: props?.subgridOpacity !== undefined ? props?.subgridOpacity : 0.15,
116
+ },
117
+ uGridOpacity: {
118
+ value: props?.gridOpacity !== undefined ? props?.gridOpacity : 0.25,
119
+ },
120
+ },
121
+ vertexShader: vertex,
122
+ fragmentShader: fragment,
123
+ name: 'InfiniteGrid',
124
+ depthWrite: false,
125
+ });
126
+ }
127
+ }
@@ -0,0 +1,101 @@
1
+ $padding: 2px;
2
+
3
+ .multiview {
4
+ display: grid;
5
+ font-family: Roboto Mono, Source Code Pro, Menlo, Courier, monospace;
6
+ font-size: 10px;
7
+ grid-template-columns: repeat(2, 1fr);
8
+ position: absolute;
9
+ overflow: hidden;
10
+ left: 0;
11
+ top: 0;
12
+ right: 300px;
13
+ bottom: 0;
14
+ z-index: 1;
15
+
16
+ canvas {
17
+ pointer-events: none;
18
+ }
19
+
20
+ .dropdown {
21
+ background-color: #222;
22
+ display: inline-block;
23
+ font-size: 10px;
24
+ padding: $padding;
25
+ text-align: center;
26
+ width: 100px;
27
+
28
+ .dropdown-toggle {
29
+ cursor: pointer;
30
+ }
31
+
32
+ .dropdown-menu {
33
+ position: absolute;
34
+ top: 100%;
35
+ left: 0;
36
+ z-index: 1;
37
+ list-style: none;
38
+ padding: 0;
39
+ margin: 0;
40
+ width: 100%;
41
+
42
+ li {
43
+ background-color: #222;
44
+ cursor: pointer;
45
+ padding: $padding;
46
+ transition: 0.2s linear background-color;
47
+ &:hover {
48
+ background-color: #333;
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ .cameras {
55
+ display: grid;
56
+ grid-template-columns: repeat(2, 1fr);
57
+ pointer-events: visible;
58
+ position: absolute;
59
+ width: 100%;
60
+ height: 100%;
61
+
62
+ &.single {
63
+ grid-template-columns: repeat(1, 1fr);
64
+ }
65
+
66
+ .dropdown {
67
+ position: absolute;
68
+ top: 0;
69
+ left: 50%;
70
+ transform: translateX(-50%);
71
+
72
+ &.up {
73
+ bottom: 0;
74
+ top: initial;
75
+ .dropdown-menu {
76
+ top: initial;
77
+ bottom: 100%;
78
+ }
79
+ }
80
+ }
81
+
82
+ .CameraWindow {
83
+ border: 1px dotted #333;
84
+ pointer-events: visible;
85
+ position: relative;
86
+
87
+ .clickable {
88
+ display: inline-block;
89
+ width: 100%;
90
+ height: 100%;
91
+ }
92
+ }
93
+ }
94
+
95
+ .settings {
96
+ pointer-events: visible;
97
+ position: absolute;
98
+ left: 50%;
99
+ transform: translateX(-50%);
100
+ }
101
+ }