Skip to content

Commit

Permalink
(feat) Architecture on top of Reveal (#4403)
Browse files Browse the repository at this point in the history
* Initial commit

* Update .prettierrc

* Fixing lint errors

* Update RevealRenderTarget.ts

* More fixes

* Update RevealRenderTarget.ts

* Initial commit

* Finishing

* Fixing

* Moving around

* Moving

* Revert "Moving around"

This reverts commit f09ef09.

* Update RevealRenderTarget.ts

* Removing constructors

* Update SurfaceRenderStyle.ts

* Fixing smaller issues

* SurfaceView

* Lights

* Moving code around

* More

* More fixes

* Remove name()

* Fixing boundingBox

* Dispose materials

* Fix matrial

* Fix materials

* Fixing

* Moving files

* Edit tool

* Move of box

* More

* Fix events

* Update BaseTool.ts

* Fix interface

* Update BaseTool.ts

* Add rotation

* Update BoxEditTool.ts

* Update BaseTool.ts

* Moving things around

* Move files

* Fix tool controller

* Before refacoring box edit

* Fixing edit of box

* Add box edit

* More edit of points

* Comments

* Fixes

* Update BoxThreeView.ts

* Update BoxThreeView.ts

* Fixing

* Fixes

* Fixing editing

* More edit fixes

* Fix lint

* Updating lock files

* Update BoxThreeView.ts

* Tuning

* Add axis

* Renaming

* Renaming and small fixes

* Optimizing

* Update README.md

* Update vectorExtensions.ts

* Update README.md

* Update AxisThreeView.ts

* Fix crash in stories

* Update AxisThreeView.ts

* Update AxisThreeView.ts

* Update AxisThreeView.ts

* Fixes

* Update GroupThreeView.ts

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Some reorginizing

* Update README.md

* Rewrite hover point in box

* removed typescript-eslint/class-literal-property-style lint rule

* Update tsconfig.json

* Add some getters

* Add area, strings and fix linter errors

* Update BoxThreeView.ts

* Add measure line and generalized a lot

* Some renaming

* fix: typos (#4504)

Co-authored-by: Nils Petter Fremming <[email protected]>

* More fixes

* add support of depthTest

* Update reveal-react-components.json

* feat: add added/deleted events

* remove one option

* Fix smaller bugs in picking

* feat: React panel reacting to domain object updates

* Fixing linting + base class for all measure domain objects

* Generalizing the card (Part 1)

* fix: don't recreate Reveal on every RevealStoryBook rerender

* Fix area

* Fixes

* Refacor the updating of the panel

* Fixing the panel

* Add proper Icons

* Fixing the panel

* Update DomainObjectPanel.tsx

* Fixed strings

* Simple Unit support

* Add ShowMeasurmentsOnTopCommand

* Update MeasureRenderStyle.ts

* Add extra toolbar

* Optimizing

* Active tool updater

* Move some files

* Implement popup style for placement

* Smaller fixes

* Smaller fixes

* Fix some selection issue

* Fix focus

* Fixing selection and focus

* Some refacturing

* chore: update visual test image

* Fix react stuff

* Renaming

* chore: export relevant React components and fix typo

* chore: don't export CommandButton

* Fix text alignment and size

---------

Co-authored-by: Pramod S <[email protected]>
Co-authored-by: Christopher J. Tannum <[email protected]>
Co-authored-by: Håkon Flatval <[email protected]>
  • Loading branch information
4 people authored May 24, 2024
1 parent f4bf3fa commit 244a535
Show file tree
Hide file tree
Showing 106 changed files with 10,655 additions and 100 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ examples/public/
documentation/.docusaurus
documentation/generated
documentation/docs/api
/react-components/stories/public/cadModel
3 changes: 3 additions & 0 deletions react-components/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ module.exports = {
],
'@typescript-eslint/consistent-type-definitions': ['error', 'type'],
'@typescript-eslint/no-misused-promises': 'off',
'@typescript-eslint/class-literal-property-style': 'off',
'@typescript-eslint/no-extraneous-class': 'off',
'@typescript-eslint/prefer-optional-chain': 'off',
'no-console': [2, { allow: ['warn', 'error'] }],
eqeqeq: ['error', 'always']
},
Expand Down

This file was deleted.

186 changes: 186 additions & 0 deletions react-components/src/architecture/base/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Building and testing

Do the following commands:

yarn
yarn build
yarn storybook`

This will automatically goto: https://localhost:3000/?modelUrl=primitivesOpen.

Instead of yarn build, use can build with types by:

yarn tsc --noEmit

When the StoryBook is open, goto `Architecture/Main`. The toolbar on the right hand side is the one I have made. Here some commands and tools are added.

# Motivation

The objectives with this framework it that is should be easy to use and extend in the future. I hope all new related functionality will be developed using this framework. More is described in this document: https://docs.google.com/presentation/d/1Y50PeqoCS5BdWyDqRNNazeISy1SYTcx4QDcjeMKIm78/edit?usp=sharing

# Coding

You will probably find the coding style somewhat different than usually done in Reveal. Some difference:

- **Headers:** Headers are used to mark sections in a file. This is important for fast navigation and to keep the code in correct order. The order in the file is always:

- Public constants (exported)
- Instance fields.
- Instance properties
- Implementation of interface (if any)
- Overrides from the must basic base class (if any)
- :
- Overrides from the actually base class (if any)
- Instance methods (If many, break it up based on what they do)
- Static methods (if any)
- Local functions, types or classes used in this file only

- **Virtual methods:**: Since TypeScript doesn't allow the virtual keyword, all functions are virtual. This is a weakness for the application developer. I have tried to mark function as virtual by comments. You should not override other function than this. A virtual function normally brings in unnecessary complexity and should general be used by care.

- Function with prefix `Core` are always virtual and protected. They can be overridden, but must call the same function for the base class. A function without the `Core` prefix is the one you should call from outside and is public. Experience shows that this pattern will make the code easier to maintain and extend in the future. For instance `remove/removeCore` and `initialize/initializeCore` in the `DomainObject`.
- Overridden methods should alway be marked by the override keyword. You will unfortunate not get any linting error when you forget this.
-

- **Static methods and classes:** The linker don't like static method, especially when the entire class have static method only. I wanted to use this more, but have used functions instead. I don't like this, because it does not group similar function together. It will also be less readable on the caller side, since you don't know what part of the code the function is coming from. In typescript we have for instance Math with act like a static class, but they use som magic to pretend it to be using interface.

- **Reuse:** I try to reuse code whenever it is possible. Therefore you will find functions that are very small,
often one liners. This ensures the complexity of the code to be low and increase readability on the caller side. For instance:

export function clear<T>(array: T[]): void {
array.splice(0, array.length);
}

- **Single responsibility:** I try to give each class one single responsibility. Therefore I use several base classes, each class adds some complexity.
Examples of this is `BaseView/ThreeView/GroupThreeView` which are all base classes. If they are merge to one single class, it will be too complex.

- **Interdependence:** Try to keep must classes independent of other classes. For instance the tool should not know about the views. Utility function and classes can used whenever you like.

- **Utility function and classes:** I have added som utility function/classes in `architecture/base/utilities`.
- Some utility functions may be a duplicate of some function in a package we use. Please tell me.
- Some utility classes you will see is like a duplicate, but is made by purpose. For instance `Range3` class which is almost the same as `THREE.Box`, but is far easier to work with.
- Some utility classes or function is already implemented in Reveal, and are similar. The reason for not reusing Reveal here is due to what we should expose out of Reveal and into Reveal-components. For the moment this is rather strick. Examples is `Vector3Pool` which is implemented both places. Also some of the color classes may seem to be similar. But all this is low level stuff, and is not related to any Reveal functionality.
- I will remove unused utility function or classes when I feel ready for it. Most of them are imported file by file from the node-visualizer project.

# Architecture Overview

## architecture/base

Here is the architecture with all base classes and some utility functionality.

### architecture/base/domainObject

- **DomainObject:** This is the base class for the domain object. A domain object is some sort of data, that is a collection of other domainObject or the data itself.

- An important property of the domainObject is that is has a list of views that will be updated.
- It has a parent and a collection of children. To ensure easy use, I have added several methods to access children, descendants and ancestors in several ways.

- It has some properties like selected, expanded and active. This is not used yet, but I wanted it to be here as this is part of the pattern. They are connected to how they are visualized and behave in a tree view, but this is not used for the moment.

- Methods with postfix `Interactive` are doing all necessary updates automatically. For instance: `addChild/addChildInteractive` or `setVisible/setVisibleInteractive`

- **VisualDomainObject**: This subclass adds functionality to a domain object so it can be shown in 3D. The most important function here is `createThreeView()` which must be overridden.

- **FolderDomainObject**: Concrete class for multi purpose folder.

- **RootDomainObject**: Concrete class for the root of the hierarchy

### architecture/base/renderTarget

Normally, when I follow this pattern, the renderTarget is a domainObject itself, and you can have as many of them as you like. But this makes it a little bit more complicated. Since we always have one viewer only, I don't use any base class for this.

- **RevealRenderTarget** Wrap around the Cognite3DViewer. It contains:

- The `Cognite3DViewer` and assume this is constructed somewhere else.
- The `RootDomainObject`, where the data can be added.
- The `ToolController`, where the active tool is kept and where the events are handled.
- In addition, it has several utility functions for getting information from the viewer.
- It set up listeners on `cameraChange` and `beforeSceneRendered` events from the viewer.

- **ToolController**
Holds the tools and all commands, the active tool and the previous tool.
- It inherits from `PointerEvents`, which is a base class on the Reveal side. All virtual functions are implemented in the `ToolControllers`.
- It creates a `PointerEventsTarget`, also on the Reveal side, that set up all the event listeners for the pointer (mouse or touch) handlers. The `PointerEventsTarget` will automatically call the functions `onHover`, `onClick`, `onDoubleClick`, `onPointerDown`, `onPointerUp` and `onPointerDrag` correctly.
- In addition it sets up some other events like `onKey` and `onFocus`.
- All event are routed to the active tool

### architecture/base/commands

- **BaseCommand**: Base class for all tools and commands. There are basically user actions. It contains all necessary information to create and update a button in the user interface, but it doesn't need or use React. The must important method to override is `invokeCore`, which is called when the user presses the button.A base command can be checkable.

- **RenderTargetCommand** I wanted `BaseCommand` should be independent of any type of connection to the rest of the system. This class only brings in the connection to the `RevealRenderTarget`.

- **BaseTool** This is the base class for all the tools, which is used when doing user interaction in the viewer itself. It defined a lot of virtual methods for user interactions. This should be overridden for creating the logic specific for a tool.

### architecture/base/concreteCommands

This is a collection of most commonly used tools and commands:

- **FitViewCommand**: Fit view to the model bounding box
- **NavigationTool**: Reroute the events to the camera manager
- **SetFlexibleControlsTypeCommand**: This is another version than we had before. It is made for testing the architecture where the users are changing the state of the command from outside. Use the "1" or "2" key to change between Fly or Orbit mode.

### architecture/base/views

- **BaseView**: Represents a abstract base view class that provides common functionality for all types of views. This does not have any dependency to `three.js` and can be used in other types of views as well.

- **ThreeView**: Represents an abstract base class for a `Three.js` view in the application. It adds basically 2 things: Concept of bounding box and a pointer to the renderTarget (viewer). The bounding box is a lazy calculation. The reason for this object is that we sometimes can have a view without any `Object3D`, for instance if a view manipulates another view, for instance a texture on a surface.

- **GroupThreeView**: Represents an abstract base class for a `Three.js` view where it holds a pointer to a `THREE.Group` object. This object is the root of the `Object3D`'s that can be added to the view. The most important method is a`addChildren()` to be overridden. Here is where the children of the group should be added. The class will administrate the group and the children, and perform a lazy creation of these automatically. `GroupThreeView` implements the `CustomObject` which is injected into Reveal by `viewer.addCustomObject(this)` when the view is set to visible. It is removed from Reveal when view is hidden by `viewer.removeCustomObject(this)`. This magic should be hidden from the application developer. All concrete views I have made inherit from `GroupThreeView`.

### architecture/base/domainObjectHelpers

- **RenderStyle**: Is the base class for all render styles.

- **Changes**: All changes that can be applied on a domainObject. it uses symbols as the type, because it can easily be extended. It looks like an enum when unit it, since I have used a static class, and this is done by purpose.

- **DomainObjectChange** Here you can add several changes before you call domainObject.notify(....)

### architecture/base/utilities

Smaller files with utility classes and functions:

- **architecture/base/utilities/box:**

Geometry algorithms and enums for box manipulation.

- **architecture/base/utilities/colors:**

Color manipulation. Making various color maps and 1d textures from color maps. Used mostly by the terrain visualization.

- **architecture/base/utilities/extensions:**

Some simple math and THREE.Vector2/3 extension

- **architecture/base/utilities/geometry:**

Some useful geometry

- **architecture/base/utilities/sprites:**

Easy Creation of sprite with text

# Some concrete examples

These are made to test the architecture but should when ready be used by any of Cognite's applications:

## architecture/concrete

### architecture/concrete/axes

- AxisDomainObject, AxisRenderStyle and AxisTreeView
- SetAxisVisibleCommand to set the axis visible

### architecture/concrete/boxDomainObject

- BoxDomainObject, BoxRenderStyle and BoxTreeView
- BotEditTool for manipulation

### architecture/concrete/terrainDomainObject

- TerrainDomainObject, TerrainRenderStyle and TerrainTreeView
- UpdateTerrainCommand and SetTerrainVisibleCommand for demo/example
- geometry: Folder with math and grid structures

```
```
99 changes: 99 additions & 0 deletions react-components/src/architecture/base/commands/BaseCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*!
* Copyright 2024 Cognite AS
*/

import { clear, remove } from '../utilities/extensions/arrayExtensions';

type UpdateDelegate = (command: BaseCommand) => void;

export type Tooltip = {
key: string;
fallback?: string;
};

export abstract class BaseCommand {
// ==================================================
// INSTANCE FIELDS
// ==================================================

private readonly _listeners: UpdateDelegate[] = [];

// ==================================================
// VIRTUAL METHODS (To be override)
// =================================================

public get name(): string {
return this.tooltip.fallback ?? this.tooltip.key;
}

public get shortCutKey(): string | undefined {
return undefined;
}

public get tooltip(): Tooltip {
return { key: '' };
}

public get icon(): string {
return 'Unknown';
}

public get isEnabled(): boolean {
return true;
}

public get isVisible(): boolean {
return this.isEnabled;
}

public get isCheckable(): boolean {
return false;
}

public get isChecked(): boolean {
return false;
}

/*
* Called when the command is invoked
* Return true if successful, false otherwise
* Override this method to implement the command logic
*/
protected invokeCore(): boolean {
return false;
}

public equals(other: BaseCommand): boolean {
return this.constructor === other.constructor;
}

public invoke(): boolean {
return this.invokeCore();
}

public dispose(): void {
this.removeEventListeners();
}

// ==================================================
// INSTANCE METHODS: Event listeners
// ==================================================

public addEventListener(listener: UpdateDelegate): void {
this._listeners.push(listener);
}

public removeEventListener(listener: UpdateDelegate): void {
remove(this._listeners, listener);
}

public removeEventListeners(): void {
clear(this._listeners);
}

public update(): void {
for (const listener of this._listeners) {
listener(this);
}
}
}
Loading

0 comments on commit 244a535

Please sign in to comment.