From d21937d56fb42195d21999a7b832e314e23ce283 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sat, 25 May 2024 11:33:06 +0200 Subject: [PATCH 01/27] Initial commit --- .../architecture/base/commands/BaseCommand.ts | 5 + .../base/commands/BaseEditTool.ts | 7 + .../architecture/base/commands/BaseTool.ts | 4 + .../base/commands/NavigationTool.ts | 4 + .../base/commands/RenderTargetCommand.ts | 9 + .../base/domainObjects/DomainObject.ts | 184 +++++++++++------- .../base/domainObjects/VisualDomainObject.ts | 45 +++-- .../base/domainObjectsHelpers/BaseCreator.ts | 45 ++++- .../base/domainObjectsHelpers/Changes.ts | 18 +- .../base/domainObjectsHelpers/ColorType.ts | 8 +- .../DomainObjectChange.ts | 71 +++---- .../base/domainObjectsHelpers/VisibleState.ts | 2 +- .../utilities/extensions/mathExtensions.ts | 19 +- .../utilities/extensions/rayExtensions.ts | 8 + .../utilities/extensions/stringExtensions.ts | 31 ++- .../base/utilities/geometry/Range1.ts | 7 +- .../boxDomainObject/MeasureBoxCreator.ts | 6 +- .../boxDomainObject/MeasureLineCreator.ts | 6 +- .../boxDomainObject/MeasureRenderStyle.ts | 2 +- .../boxDomainObject/MeasurementFunctions.ts | 30 --- .../boxDomainObject/MeasurementTool.ts | 55 +++--- .../SetMeasurmentTypeCommand.ts | 14 +- .../ShowMeasurmentsOnTopCommand.ts | 8 +- .../TerrainDomainObject.ts | 4 +- .../geometry/ContouringService.ts | 4 +- 25 files changed, 355 insertions(+), 241 deletions(-) delete mode 100644 react-components/src/architecture/concrete/boxDomainObject/MeasurementFunctions.ts diff --git a/react-components/src/architecture/base/commands/BaseCommand.ts b/react-components/src/architecture/base/commands/BaseCommand.ts index 3f8ad3f7e05..04ee863126d 100644 --- a/react-components/src/architecture/base/commands/BaseCommand.ts +++ b/react-components/src/architecture/base/commands/BaseCommand.ts @@ -11,6 +11,11 @@ export type Tooltip = { fallback?: string; }; +/** + * Base class for all command and tools. Thses are object that can do a + * user interaction with the system. It also have enough information to + * generate the UI for the command. + */ export abstract class BaseCommand { // ================================================== // INSTANCE FIELDS diff --git a/react-components/src/architecture/base/commands/BaseEditTool.ts b/react-components/src/architecture/base/commands/BaseEditTool.ts index ebd552b168f..91bb1dd4d98 100644 --- a/react-components/src/architecture/base/commands/BaseEditTool.ts +++ b/react-components/src/architecture/base/commands/BaseEditTool.ts @@ -7,6 +7,13 @@ import { type DomainObject } from '../domainObjects/DomainObject'; import { isDomainObjectIntersection } from '../domainObjectsHelpers/DomainObjectIntersection'; import { type BaseDragger } from '../domainObjectsHelpers/BaseDragger'; +/** + * The `BaseEditTool` class is an abstract class that extends the `NavigationTool` class. + * It provides a base implementation for editing tools in a specific architecture. + * Custom editing tools can be created by extending this class and overriding its methods. + * This class will also proivide the dragging functionality if the picked domain object has + * createDragger() overridden. + */ export abstract class BaseEditTool extends NavigationTool { // ================================================== // INSTANCE FIELDS diff --git a/react-components/src/architecture/base/commands/BaseTool.ts b/react-components/src/architecture/base/commands/BaseTool.ts index 03c814a2342..8e31fe6de94 100644 --- a/react-components/src/architecture/base/commands/BaseTool.ts +++ b/react-components/src/architecture/base/commands/BaseTool.ts @@ -21,6 +21,10 @@ import { type BaseCommand } from './BaseCommand'; import { ActiveToolUpdater } from '../reactUpdaters/ActiveToolUpdater'; import { PopupStyle } from '../domainObjectsHelpers/PopupStyle'; +/** + * Base class for intraction in the 3D viewer + * Provides common functionality and virtual methods to be overridden by derived classes. + */ export abstract class BaseTool extends RenderTargetCommand { // ================================================== // OVERRIDES diff --git a/react-components/src/architecture/base/commands/NavigationTool.ts b/react-components/src/architecture/base/commands/NavigationTool.ts index 44826126fc1..fcd2b0266c3 100644 --- a/react-components/src/architecture/base/commands/NavigationTool.ts +++ b/react-components/src/architecture/base/commands/NavigationTool.ts @@ -7,6 +7,10 @@ import { BaseTool } from './BaseTool'; import { type Tooltip } from './BaseCommand'; import { type IFlexibleCameraManager } from '@cognite/reveal'; +/** + * Represents a tool navigation tool used for camera manipulation. + * Inherit from this class if you like to have some camera manipulation in your tool. + */ export class NavigationTool extends BaseTool { // ================================================== // INSTANVE PROPERTIES diff --git a/react-components/src/architecture/base/commands/RenderTargetCommand.ts b/react-components/src/architecture/base/commands/RenderTargetCommand.ts index 47bf3fc9b48..d297277be1a 100644 --- a/react-components/src/architecture/base/commands/RenderTargetCommand.ts +++ b/react-components/src/architecture/base/commands/RenderTargetCommand.ts @@ -4,7 +4,12 @@ import { BaseCommand } from './BaseCommand'; import { type RevealRenderTarget } from '../renderTarget/RevealRenderTarget'; +import { type RootDomainObject } from '../domainObjects/RootDomainObject'; +/** + * Represents a base class where the render target is known. + * Subclasses of this class is used to interact with the render target + */ export abstract class RenderTargetCommand extends BaseCommand { public _renderTarget: RevealRenderTarget | undefined = undefined; @@ -15,6 +20,10 @@ export abstract class RenderTargetCommand extends BaseCommand { return this._renderTarget; } + public get rootDomainObject(): RootDomainObject { + return this.renderTarget.rootDomainObject; + } + public override invoke(): boolean { const success = this.invokeCore(); if (success) { diff --git a/react-components/src/architecture/base/domainObjects/DomainObject.ts b/react-components/src/architecture/base/domainObjects/DomainObject.ts index 54dacfd389b..de31761cc66 100644 --- a/react-components/src/architecture/base/domainObjects/DomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/DomainObject.ts @@ -23,7 +23,6 @@ import { PopupStyle } from '../domainObjectsHelpers/PopupStyle'; /** * Represents an abstract base class for domain objects. * @abstract - * @extends BaseSubject */ export abstract class DomainObject { // ================================================== @@ -33,8 +32,16 @@ export abstract class DomainObject { // Some basic states private _name: string | undefined = undefined; private _color: Color | undefined = undefined; + + // Selection. This is used for selection in 3D viewer. Selection in a tree view is + // not implemented yet, but should be added in the future. private _isSelected: boolean = false; + + // Maximum one active object for each type of domain object. Works as a long term selection + // For instance you can have many crop boxes, but only one can be used at the time. private _isActive: boolean = false; + + // Expaned when it is shown in a tree view private _isExpanded = false; // Parent-Child relationship @@ -288,17 +295,62 @@ export abstract class DomainObject { return true; // to be overridden } + // ================================================== + // VIRTUAL METHODS: Render styles + // ================================================== + + /** + * Override if the render style is taken from another domain object, for instance the parent + * or somewhere else in the hieracy + * @returns The render style root + */ + public get renderStyleRoot(): DomainObject | undefined { + return undefined; + } + + /** + * Factory method to create the render style for the domain object. + * Override this method to create a custom render style. + * @returns The render style + */ + public createRenderStyle(): RenderStyle | undefined { + return undefined; + } + + /** + * Verifies the render style for the domain object, because the render style may + * be not valid in some cases. In this method you can change the render style. + * You can also change som fields in the rebnderstyle to get default values + * dependent of the domain object itself. + * Override this method when needed + */ + public verifyRenderStyle(_style: RenderStyle): void {} + + // ================================================== + // VIRTUAL METHODS: Create dragger + // ================================================== + + /** + * Factory method to create a dragger to interpret the mouse dragging operation + * This function is used in BaseEditTool + * @returns The render style + */ + // override when creating a dragger operation in the BaseEditTool + public createDragger(_intersection: DomainObjectIntersection): BaseDragger | undefined { + return undefined; + } + // ================================================== // VIRTUAL METHODS: Visibility // ================================================== - public getVisibleState(target: RevealRenderTarget): VisibleState { + public getVisibleState(renderTarget: RevealRenderTarget): VisibleState { let numCandidates = 0; let numAll = 0; let numNone = 0; for (const child of this.children) { - const childState = child.getVisibleState(target); + const childState = child.getVisibleState(renderTarget); if (childState === VisibleState.Disabled) { continue; } @@ -319,26 +371,26 @@ export abstract class DomainObject { return VisibleState.All; } if (numCandidates === numNone) { - return this.canBeChecked(target) ? VisibleState.None : VisibleState.CanNotBeChecked; + return this.canBeChecked(renderTarget) ? VisibleState.None : VisibleState.CanNotBeChecked; } return VisibleState.Some; } public setVisibleInteractive( visible: boolean, - target: RevealRenderTarget, + renderTarget: RevealRenderTarget, topLevel = true // When calling this from outside, this value should alwaus be true ): boolean { - const visibleState = this.getVisibleState(target); + const visibleState = this.getVisibleState(renderTarget); if (visibleState === VisibleState.Disabled) { return false; } - if (visibleState === VisibleState.None && !this.canBeChecked(target)) { + if (visibleState === VisibleState.None && !this.canBeChecked(renderTarget)) { return false; } let hasChanged = false; for (const child of this.children) { - if (child.setVisibleInteractive(visible, target, false)) { + if (child.setVisibleInteractive(visible, renderTarget, false)) { hasChanged = true; } } @@ -351,6 +403,10 @@ export abstract class DomainObject { return true; } + // ================================================== + // INSTANCE METHODS: Visibility + // ================================================== + protected notifyVisibleStateChange(): void { const change = new DomainObjectChange(Changes.visibleState); this.notify(change); @@ -362,36 +418,11 @@ export abstract class DomainObject { } } - public toggleVisibleInteractive(target: RevealRenderTarget): void { - const visibleState = this.getVisibleState(target); - if (visibleState === VisibleState.None) this.setVisibleInteractive(true, target); + public toggleVisibleInteractive(renderTarget: RevealRenderTarget): void { + const visibleState = this.getVisibleState(renderTarget); + if (visibleState === VisibleState.None) this.setVisibleInteractive(true, renderTarget); else if (visibleState === VisibleState.Some || visibleState === VisibleState.All) - this.setVisibleInteractive(false, target); - } - - // ================================================== - // VIRTUAL METHODS: Render styles - // ================================================== - - public get renderStyleRoot(): DomainObject | undefined { - return undefined; // Override if the render style is taken from another domain object - } - - public createRenderStyle(): RenderStyle | undefined { - return undefined; // Override when creating a render style - } - - public verifyRenderStyle(_style: RenderStyle): void { - // override when validating the render style - } - - // ================================================== - // VIRTUAL METHODS: Create dragger - // ================================================== - - // override when creating a dragger operation in the BaseEditTool - public createDragger(_intersection: DomainObjectIntersection): BaseDragger | undefined { - return undefined; + this.setVisibleInteractive(false, renderTarget); } // ================================================== @@ -470,18 +501,9 @@ export abstract class DomainObject { } // ================================================== - // INSTANCE METHODS: Get descendants + // INSTANCE METHODS: Get descendants (returning a generator) // ================================================== - public getSelected(): DomainObject | undefined { - for (const descendant of this.getThisAndDescendants()) { - if (descendant.isSelected) { - return descendant; - } - } - return undefined; - } - public *getDescendants(): Generator { for (const child of this.children) { yield child; @@ -498,6 +520,36 @@ export abstract class DomainObject { } } + public *getDescendantsByType(classType: Class): Generator { + for (const descendant of this.getDescendants()) { + if (isInstanceOf(descendant, classType)) { + yield descendant; + } + } + } + + public *getThisAndDescendantsByType(classType: Class): Generator { + for (const descendant of this.getThisAndDescendants()) { + if (isInstanceOf(descendant, classType)) { + yield descendant; + } + } + } + + // ================================================== + // INSTANCE METHODS: Get single descendant + // (returning a DomainObject | undefined) + // ================================================== + + public getDescendantByType(classType: Class): T | undefined { + for (const descendant of this.getDescendants()) { + if (isInstanceOf(descendant, classType)) { + return descendant; + } + } + return undefined; + } + public getDescendantByName(name: string): DomainObject | undefined { for (const descendant of this.getDescendants()) { if (descendant.hasEqualName(name)) { @@ -519,33 +571,27 @@ export abstract class DomainObject { return undefined; } - public getDescendantByType(classType: Class): T | undefined { - for (const descendant of this.getDescendants()) { - if (isInstanceOf(descendant, classType)) { + public getSelectedDescendant(): DomainObject | undefined { + for (const descendant of this.getThisAndDescendants()) { + if (descendant.isSelected) { return descendant; } } return undefined; } - public *getDescendantsByType(classType: Class): Generator { - for (const child of this.children) { - if (isInstanceOf(child, classType)) { - yield child; - } - for (const descendant of child.getDescendantsByType(classType)) { - yield descendant; + public getSelectedDescendantByType(classType: Class): T | undefined { + for (const descendant of this.getDescendantsByType(classType)) { + if (descendant.isSelected) { + return descendant; } } + return undefined; } public getActiveDescendantByType(classType: Class): T | undefined { - for (const child of this.children) { - if (child.isActive && isInstanceOf(child, classType)) { - return child; - } - const descendant = child.getActiveDescendantByType(classType); - if (descendant !== undefined) { + for (const descendant of this.getDescendantsByType(classType)) { + if (descendant.isActive) { return descendant; } } @@ -650,13 +696,16 @@ export abstract class DomainObject { // ================================================== public getRenderStyle(): RenderStyle | undefined { + // Find the root of the render style const root = this.renderStyleRoot; if (root !== undefined && root !== this) { return root.getRenderStyle(); } + // Create it if not created if (this._renderStyle === undefined) { this._renderStyle = this.createRenderStyle(); } + // Verify it if (this._renderStyle !== undefined) { this.verifyRenderStyle(this._renderStyle); } @@ -671,11 +720,11 @@ export abstract class DomainObject { // INSTANCE METHODS: Get auto name and color // ================================================== - protected generateNewColor(): Color { + private generateNewColor(): Color { return this.canChangeColor ? getNextColor().clone() : WHITE_COLOR.clone(); } - protected generateNewName(): string { + private generateNewName(): string { let result = this.typeName; if (!this.canChangeName) { return result; @@ -696,6 +745,11 @@ export abstract class DomainObject { return result; } + // ================================================== + // INSTANCE METHODS: Color type + // Used in the renderstyle to determin which of the color a doamin object should have. + // ================================================== + public supportsColorType(colorType: ColorType, solid: boolean): boolean { switch (colorType) { case ColorType.Specified: diff --git a/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts b/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts index dc9756c4b54..f77e09962cc 100644 --- a/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts @@ -7,17 +7,21 @@ import { ThreeView } from '../views/ThreeView'; import { VisibleState } from '../domainObjectsHelpers/VisibleState'; import { DomainObject } from './DomainObject'; +/** + * Represents a visual domain object that can be rendered and manipulated in a three-dimensional space. + * This class extends the `DomainObject` class and provides additional functionality for visualization. + */ export abstract class VisualDomainObject extends DomainObject { // ================================================== // OVERRIDES of DomainObject // ================================================== - public override getVisibleState(target: RevealRenderTarget): VisibleState { - if (this.isVisible(target)) { + public override getVisibleState(renderTarget: RevealRenderTarget): VisibleState { + if (this.isVisible(renderTarget)) { return VisibleState.All; } if (this.canCreateThreeView()) { - if (this.canBeChecked(target)) { + if (this.canBeChecked(renderTarget)) { return VisibleState.None; } return VisibleState.CanNotBeChecked; @@ -27,13 +31,13 @@ export abstract class VisualDomainObject extends DomainObject { public override setVisibleInteractive( visible: boolean, - target: RevealRenderTarget, + renderTarget: RevealRenderTarget, topLevel = true ): boolean { - if (visible && !this.canBeChecked(target)) { + if (visible && !this.canBeChecked(renderTarget)) { return false; } - if (!this.setVisible(visible, target)) { + if (!this.setVisible(visible, renderTarget)) { return false; } if (topLevel) { @@ -46,8 +50,17 @@ export abstract class VisualDomainObject extends DomainObject { // VIRTUAL METHODS // ================================================== + /** + * Factory methods to create its own three view for visualization in three.js + */ protected abstract createThreeView(): ThreeView | undefined; + /** + * Determines whether the visual domain object can create a three view. + * It may have a state when it can not create a view bacause of other dependencies + * + * @returns A boolean value indicating whether the visual domain object can create a three view. + */ protected canCreateThreeView(): boolean { return true; } @@ -56,27 +69,31 @@ export abstract class VisualDomainObject extends DomainObject { // INSTANCE METHODS // ================================================== - public getViewByTarget(target: RevealRenderTarget): ThreeView | undefined { + public getViewByTarget(renderTarget: RevealRenderTarget): ThreeView | undefined { for (const view of this.views.getByType(ThreeView)) { - if (view.renderTarget === target) { + if (view.renderTarget === renderTarget) { return view; } } } - public isVisible(target: RevealRenderTarget): boolean { - return this.getViewByTarget(target) !== undefined; + /** + * Checks if the visual domain object is visible in the specified render target. + * @param renderTarget - The render target to check visibility in. + * @returns `true` if the visual domain object is visible in the target, `false` otherwise. + */ + public isVisible(renderTarget: RevealRenderTarget): boolean { + return this.getViewByTarget(renderTarget) !== undefined; } /** * Sets the visibility of the visual domain object for a specific target. - * * @param visible - A boolean indicating whether the visual domain object should be visible or not. * @param target - The target RevealRenderTarget where the visual domain object will be attached. * @returns A boolean indicating whether the state has changed. */ - public setVisible(visible: boolean, target: RevealRenderTarget): boolean { - let view = this.getViewByTarget(target); + public setVisible(visible: boolean, renderTarget: RevealRenderTarget): boolean { + let view = this.getViewByTarget(renderTarget); if (visible) { if (view !== undefined) { return false; @@ -89,7 +106,7 @@ export abstract class VisualDomainObject extends DomainObject { return false; } this.views.addView(view); - view.attach(this, target); + view.attach(this, renderTarget); view.initialize(); view.onShow(); } else { diff --git a/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts b/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts index c0398338d15..2ff79c462d2 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts @@ -22,26 +22,30 @@ export abstract class BaseCreator { // INSTANCE PROPERTIES // ================================================== - public get points(): Vector3[] { + protected get points(): Vector3[] { return this._points; } - public get pointCount(): number { + protected get pointCount(): number { return this.points.length; } - public get realPointCount(): number { + protected get notPendingPointCount(): number { return this.lastIsPending ? this.pointCount - 1 : this.pointCount; } - public get firstPoint(): Vector3 { + protected get firstPoint(): Vector3 { return this.points[0]; } - public get lastPoint(): Vector3 { + protected get lastPoint(): Vector3 { return this.points[this.pointCount - 1]; } + protected get lastNotPendingPoint(): Vector3 { + return this.points[this.notPendingPointCount - 1]; + } + protected get lastIsPending(): boolean { return this._lastIsPending; } @@ -50,16 +54,37 @@ export abstract class BaseCreator { // VIRTUAL METHODS // ================================================== + /** + * Gets the value indicating whether to prefer intersection with somthing. + * If this is true, it will first try to intersect an object. If false the point + * will normally be calculatd based on the previous point and the ray in addPointCore + * + * @returns {boolean} The value indicating whether to prefer intersection. + */ public get preferIntersection(): boolean { return false; } public abstract get domainObject(): DomainObject; - public abstract get maximumPointCount(): number; - - public abstract get minimumPointCount(): number; - + /** + * @returns The minimum required points to create the domain object. + */ + protected abstract get minimumPointCount(): number; + + /** + * @returns The maximim required points to create the domain object. + */ + + protected abstract get maximumPointCount(): number; + /** + * Adds a new point + * @param ray - The ray the camera has (in Cdf coordinates) + * @param point - The point to add.(in Cdf coordinates). If undefined, it indicates that + * it wasn't intersection anything. Then then ray and the previous point can be used to calculate the point. + * @param isPending - Indicates whether the point is pending (hover over instead of clicking). + * @returns A boolean value indicating whether the point was successfully added. + */ protected abstract addPointCore( ray: Ray, point: Vector3 | undefined, @@ -73,7 +98,7 @@ export abstract class BaseCreator { // ================================================== public get isFinished(): boolean { - return this.realPointCount === this.maximumPointCount; + return this.notPendingPointCount === this.maximumPointCount; } public addPoint(ray: Ray, point: Vector3 | undefined, isPending: boolean): boolean { diff --git a/react-components/src/architecture/base/domainObjectsHelpers/Changes.ts b/react-components/src/architecture/base/domainObjectsHelpers/Changes.ts index 513f25812d8..883275159eb 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/Changes.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/Changes.ts @@ -3,26 +3,26 @@ */ export class Changes { - // States changed + // Domain object boolean states changed public static readonly visibleState: symbol = Symbol('visibleState'); public static readonly active: symbol = Symbol('active'); public static readonly expanded: symbol = Symbol('expanded'); public static readonly selected: symbol = Symbol('selected'); public static readonly focus: symbol = Symbol('focus'); - public static readonly loaded: symbol = Symbol('loaded'); - // Fields changed + // Domain object Fields changed public static readonly naming: symbol = Symbol('naming'); public static readonly color: symbol = Symbol('color'); public static readonly icon: symbol = Symbol('icon'); public static readonly colorMap: symbol = Symbol('colorMap'); - public static readonly geometry: symbol = Symbol('geometry'); public static readonly renderStyle: symbol = Symbol('renderStyle'); - // Parent-child relationship changed - public static readonly childDeleted: symbol = Symbol('childDeleted'); - public static readonly childAdded: symbol = Symbol('childAdded'); + // Something in the geometry changed + public static readonly geometry: symbol = Symbol('geometry'); - public static readonly added: symbol = Symbol('added'); - public static readonly deleted: symbol = Symbol('deleted'); + // Parent-child relationship changed + public static readonly added: symbol = Symbol('added'); // When added to the system + public static readonly deleted: symbol = Symbol('deleted'); // When deleted from the system + public static readonly childDeleted: symbol = Symbol('childDeleted'); // When a child is deleted + public static readonly childAdded: symbol = Symbol('childAdded'); // When a child is added } diff --git a/react-components/src/architecture/base/domainObjectsHelpers/ColorType.ts b/react-components/src/architecture/base/domainObjectsHelpers/ColorType.ts index 3ee6febda21..e4b391b1945 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/ColorType.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/ColorType.ts @@ -3,10 +3,10 @@ */ export enum ColorType { - ColorMap, // Color by the given color map - Specified, // Use the color of the node - Parent, // Use the color of the parent node + Specified, // Use the color of the domain object itself + Parent, // Use the color of the parent domain object Black, White, - Different // Use different colors (normally use for debugging) + Different, // Use different colors (normally use for debugging) + ColorMap // Color by the given color map } diff --git a/react-components/src/architecture/base/domainObjectsHelpers/DomainObjectChange.ts b/react-components/src/architecture/base/domainObjectsHelpers/DomainObjectChange.ts index 3a874328405..73c5e1f69e6 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/DomainObjectChange.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/DomainObjectChange.ts @@ -2,6 +2,8 @@ * Copyright 2024 Cognite AS */ +import { equalsIgnoreCaseAndSpace } from '../utilities/extensions/stringExtensions'; + export class DomainObjectChange { // ================================================== // INSTANCE FIELDS @@ -13,9 +15,9 @@ export class DomainObjectChange { // CONSTRUCTOR // ================================================== - public constructor(change?: symbol, name?: string) { + public constructor(change?: symbol, fieldName?: string) { if (change !== undefined) { - this.add(change, name); + this.add(change, fieldName); } } @@ -27,6 +29,11 @@ export class DomainObjectChange { return this._changes === undefined || this._changes.length === 0; } + /** + * Checks if the domain object has been changed based on the specified changes. + * @param changes - The changes to check. + * @returns `true` if any of the specified changes are present in the domain object, `false` otherwise. + */ public isChanged(...changes: symbol[]): boolean { if (this._changes === undefined) { return false; @@ -39,40 +46,24 @@ export class DomainObjectChange { return false; } - public isNameChanged(change: symbol, ...names: string[]): boolean { + /** + * Checks if the domain object has been changed based on the specified change and some specific fieldnames. + * For instance + * if (isFieldNameChanged(Changes.renderStyle, 'lineWidth', 'lineColor')) { + * // Now you have to update the line matrial only + * } + * @param change - The symbol representing the change. + * @param fieldNames - The field names to compare against the change symbol. + * @returns A boolean indicating whether the name has changed or not. + */ + public isFieldNameChanged(change: symbol, ...fieldNames: string[]): boolean { // This igonores space and case. - const name = this.getName(change); - if (name === undefined) { + const fieldName = this.getFieldNameBySymbol(change); + if (fieldName === undefined) { return false; } - - const isSpace = (s: string): boolean => s === ' '; - - const { length } = name; - for (const otherName of names) { - let found = true; - const otherLength = otherName.length; - - for (let i = 0, j = 0; i < length && found; i++) { - const a = name.charAt(i); - if (isSpace(a)) { - continue; - } - const lowerA = a.toLowerCase(); - for (; j < otherLength; j++) { - const b = otherName.charAt(j); - if (isSpace(b)) { - continue; - } - const lowerB = b.toLowerCase(); - if (lowerB === lowerA) { - continue; - } - found = false; - break; - } - } - if (found) { + for (const otherFieldName of fieldNames) { + if (equalsIgnoreCaseAndSpace(fieldName, otherFieldName)) { return true; } } @@ -90,23 +81,23 @@ export class DomainObjectChange { return this._changes.find((desc: ChangedDescription) => desc.change === change); } - private getName(change: symbol): string | undefined { + private getFieldNameBySymbol(change: symbol): string | undefined { const changedDescription = this.getChangedDescription(change); - return changedDescription === undefined ? undefined : changedDescription.name; + return changedDescription === undefined ? undefined : changedDescription.fieldName; } // ================================================== // INSTANCE METHODS: Operations // ================================================== - public add(change: symbol, name?: string): void { + public add(change: symbol, fieldName?: string): void { if (change === undefined) { return; } if (this._changes === undefined) { this._changes = []; } - this._changes.push(new ChangedDescription(change, name)); + this._changes.push(new ChangedDescription(change, fieldName)); } } @@ -116,10 +107,10 @@ export class DomainObjectChange { class ChangedDescription { public change: symbol; - public name: string | undefined; + public fieldName: string | undefined; - public constructor(change: symbol, name?: string) { + public constructor(change: symbol, fieldName?: string) { this.change = change; - this.name = name; + this.fieldName = fieldName; } } diff --git a/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts b/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts index 0d5b02109c5..140d105f979 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/VisibleState.ts @@ -6,6 +6,6 @@ export enum VisibleState { All, // Visible Some, // Partly visible None, // None visible - CanNotBeChecked, // Cab not be checked + CanNotBeChecked, // Can not be checked on, but it can be visible Disabled // Visiable disabled } diff --git a/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts b/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts index bf84cac6c48..f8c05c6ed29 100644 --- a/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts +++ b/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts @@ -59,20 +59,19 @@ export function isBetween(min: number, value: number, max: number): boolean { // FUNCTIONS: Returning a number // ================================================== -export function max(a: number, b: number, c: number): number { - return Math.max(a, Math.max(b, c)); -} - -export function min(a: number, b: number, c: number): number { - return Math.min(a, Math.min(b, c)); -} - export function square(value: number): number { return value * value; } -export function roundInc(increment: number): number { - // Get the exponent for the number [1-10] and scale the inc so the number is between 1 and 10. +/** + * Round a number closest to one of these values: 2*10^N, 2.5*10^N, 5*10^N or 10*10^N. + * This is used to give axies a natural increment between the ticks or + * contour intervals on a terrain surface + * @param increment - The value to be rounded + * @returns The rounded value + */ +export function roundIncrement(increment: number): number { + // First get the exponent for the number [1-10] and scale the inc so the number is between 1 and 10. let exp = 0; let inc = increment; let found = false; diff --git a/react-components/src/architecture/base/utilities/extensions/rayExtensions.ts b/react-components/src/architecture/base/utilities/extensions/rayExtensions.ts index 3c7f647b392..44314e4575a 100644 --- a/react-components/src/architecture/base/utilities/extensions/rayExtensions.ts +++ b/react-components/src/architecture/base/utilities/extensions/rayExtensions.ts @@ -3,6 +3,14 @@ */ import { Vector3, type Ray } from 'three'; +/** + * Calculates the closest point on a line to a given ray. + * @param ray - The ray to calculate the closest point from. + * @param lineDirection - The direction of the line. + * @param pointOnLine - A point on the line. + * @param optionalClosestPointOnLine - An optional Vector3 to store the closest point on the line. + * @returns The closest point on the line to the ray. + */ export function getClosestPointOnLine( ray: Ray, lineDirection: Vector3, diff --git a/react-components/src/architecture/base/utilities/extensions/stringExtensions.ts b/react-components/src/architecture/base/utilities/extensions/stringExtensions.ts index 6b8f78c0350..9d5e398ed33 100644 --- a/react-components/src/architecture/base/utilities/extensions/stringExtensions.ts +++ b/react-components/src/architecture/base/utilities/extensions/stringExtensions.ts @@ -10,15 +10,28 @@ export function equalsIgnoreCase(value1: string, value2: string): boolean { return value1.toLowerCase() === value2.toLowerCase(); } -export function isNumber(text: string): boolean { - const value = Number(text); - return !Number.isNaN(value); -} +export function equalsIgnoreCaseAndSpace(value1: string, value2: string): boolean { + const isSpace = (s: string): boolean => s === ' '; + const { length: length1 } = value1; + const { length: length2 } = value2; -export function getNumber(text: string): number { - const value = Number(text); - if (Number.isNaN(value)) { - return Number.NaN; + for (let i = 0, j = 0; i < length1; i++) { + const char1 = value1.charAt(i); + if (isSpace(char1)) { + continue; + } + const lowerChar1 = char1.toLowerCase(); + for (; j < length2; j++) { + const char2 = value2.charAt(j); + if (isSpace(char2)) { + continue; + } + const lowerChar2 = char2.toLowerCase(); + if (lowerChar2 === lowerChar1) { + break; + } + return false; + } } - return value; + return true; } diff --git a/react-components/src/architecture/base/utilities/geometry/Range1.ts b/react-components/src/architecture/base/utilities/geometry/Range1.ts index 39ce6e636c6..9e32d44002a 100644 --- a/react-components/src/architecture/base/utilities/geometry/Range1.ts +++ b/react-components/src/architecture/base/utilities/geometry/Range1.ts @@ -2,7 +2,12 @@ * Copyright 2024 Cognite AS */ -import { ceil, floor, isIncrement, roundInc as roundIncrement } from '../extensions/mathExtensions'; +import { + ceil, + floor, + isIncrement, + roundIncrement as roundIncrement +} from '../extensions/mathExtensions'; const MAX_NUMBER_OF_TICKS = 1000; export class Range1 { diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts index 5af7e11c7cc..c826c8664c7 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts @@ -86,7 +86,7 @@ export class MeasureBoxCreator extends BaseCreator { } public override handleEscape(): void { - if (this.realPointCount < this.minimumPointCount) { + if (this.notPendingPointCount < this.minimumPointCount) { this._domainObject.removeInteractive(); } } @@ -105,11 +105,11 @@ export class MeasureBoxCreator extends BaseCreator { } // Recalculate the point anywhy for >= 1 points // This makes it more natural and you can pick in empty space - if (this.realPointCount === 1 || this.realPointCount === 2) { + if (this.notPendingPointCount === 1 || this.notPendingPointCount === 2) { const plane = new Plane().setFromNormalAndCoplanarPoint(UP_VECTOR, this.firstPoint); const newPoint = ray.intersectPlane(plane, new Vector3()); return newPoint ?? undefined; - } else if (this.realPointCount === 3 && measureType === MeasureType.Volume) { + } else if (this.notPendingPointCount === 3 && measureType === MeasureType.Volume) { return getClosestPointOnLine(ray, UP_VECTOR, this.points[2], point); } return point; diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts index fdf106735ff..9224feaa6a2 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts @@ -65,8 +65,8 @@ export class MeasureLineCreator extends BaseCreator { isPending: boolean ): boolean { // Figure out where the point should be if no intersection - if (isPending && this.realPointCount >= 1 && point === undefined) { - const lastPoint = this.points[this.realPointCount - 1]; + if (isPending && this.notPendingPointCount >= 1 && point === undefined) { + const lastPoint = this.lastNotPendingPoint; const plane = new Plane().setFromNormalAndCoplanarPoint(ray.direction, lastPoint); const newPoint = ray.intersectPlane(plane, new Vector3()); if (newPoint === null) { @@ -91,7 +91,7 @@ export class MeasureLineCreator extends BaseCreator { public override handleEscape(): void { const domainObject = this._domainObject; - if (this.realPointCount < this.minimumPointCount) { + if (this.notPendingPointCount < this.minimumPointCount) { domainObject.removeInteractive(); } else if (this.lastIsPending) { domainObject.points.pop(); diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts index bc0ccc71bdd..0bd5541251f 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureRenderStyle.ts @@ -16,6 +16,6 @@ export abstract class MeasureRenderStyle extends RenderStyle { public colorType = ColorType.Specified; public textColor = WHITE_COLOR.clone(); public textBgColor = new Color().setScalar(0.05); // Dark gray - public textOpacity = 0.75; // Dark gray + public textOpacity = 0.75; public relativeTextSize = 0.05; // Relative to diagonal of the measurment object for box and average of lenght of line segments for line } diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementFunctions.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementFunctions.ts deleted file mode 100644 index e524204d598..00000000000 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementFunctions.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*! - * Copyright 2024 Cognite AS - * BaseTool: Base class for the tool are used to interact with the render target. - */ - -import { type RevealRenderTarget } from '../../base/renderTarget/RevealRenderTarget'; -import { MeasureDomainObject } from './MeasureDomainObject'; - -// ================================================== -// PUBLIC FUNCTIONS -// ================================================== - -export function getAnyMeasureDomainObject( - renderTarget: RevealRenderTarget -): MeasureDomainObject | undefined { - // eslint-disable-next-line no-unreachable-loop - for (const domainObject of getMeasureDomainObjects(renderTarget)) { - return domainObject; - } - return undefined; -} - -export function* getMeasureDomainObjects( - renderTarget: RevealRenderTarget -): Generator { - const { rootDomainObject } = renderTarget; - for (const descendant of rootDomainObject.getDescendantsByType(MeasureDomainObject)) { - yield descendant; - } -} diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index 9796f3c3e91..63310312561 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -15,14 +15,13 @@ import { type BaseCreator } from '../../base/domainObjectsHelpers/BaseCreator'; import { MeasureLineCreator } from './MeasureLineCreator'; import { BaseEditTool } from '../../base/commands/BaseEditTool'; import { MeasureLineDomainObject } from './MeasureLineDomainObject'; -import { getAnyMeasureDomainObject, getMeasureDomainObjects } from './MeasurementFunctions'; import { MeasureRenderStyle } from './MeasureRenderStyle'; import { type DomainObject } from '../../base/domainObjects/DomainObject'; -import { type RevealRenderTarget } from '../../base/renderTarget/RevealRenderTarget'; import { MeasureDomainObject } from './MeasureDomainObject'; import { ShowMeasurmentsOnTopCommand } from './ShowMeasurmentsOnTopCommand'; import { SetMeasurmentTypeCommand } from './SetMeasurmentTypeCommand'; import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; +import { type RootDomainObject } from '../../base/domainObjects/RootDomainObject'; export class MeasurementTool extends BaseEditTool { // ================================================== @@ -84,10 +83,8 @@ export class MeasurementTool extends BaseEditTool { public override onKey(event: KeyboardEvent, down: boolean): void { if (down && event.key === 'Delete') { - for (const domainObject of getMeasureDomainObjects(this.renderTarget)) { - if (!domainObject.isSelected) { - continue; - } + const domainObject = this.rootDomainObject.getSelectedDescendantByType(MeasureDomainObject); + if (domainObject !== undefined) { domainObject.removeInteractive(); } this._creator = undefined; @@ -196,14 +193,14 @@ export class MeasurementTool extends BaseEditTool { } const ray = this.getRay(event); if (creator === undefined) { - const creator = (this._creator = this.createCreator()); + const creator = (this._creator = createCreator(this.measureType)); if (creator === undefined) { await super.onClick(event); return; } if (creator.addPoint(ray, intersection.point, false)) { const { domainObject } = creator; - initializeStyle(domainObject, renderTarget); + initializeStyle(domainObject, this.rootDomainObject); this.deselectAll(); rootDomainObject.addChildInteractive(domainObject); domainObject.setSelectedInteractive(true); @@ -239,7 +236,7 @@ export class MeasurementTool extends BaseEditTool { } private setAllMeasurementsVisible(visible: boolean): void { - for (const domainObject of getMeasureDomainObjects(this.renderTarget)) { + for (const domainObject of this.rootDomainObject.getDescendantsByType(MeasureDomainObject)) { domainObject.setVisibleInteractive(visible, this.renderTarget); } } @@ -299,23 +296,8 @@ export class MeasurementTool extends BaseEditTool { } } - private createCreator(): BaseCreator | undefined { - switch (this.measureType) { - case MeasureType.Line: - case MeasureType.Polyline: - case MeasureType.Polygon: - return new MeasureLineCreator(this.measureType); - case MeasureType.HorizontalArea: - case MeasureType.VerticalArea: - case MeasureType.Volume: - return new MeasureBoxCreator(this.measureType); - default: - return undefined; - } - } - protected defocusAll(except?: DomainObject | undefined): void { - for (const domainObject of getMeasureDomainObjects(this.renderTarget)) { + for (const domainObject of this.rootDomainObject.getDescendantsByType(MeasureDomainObject)) { if (except !== undefined && domainObject === except) { continue; } @@ -329,8 +311,12 @@ export class MeasurementTool extends BaseEditTool { } } -function initializeStyle(domainObject: DomainObject, renderTarget: RevealRenderTarget): void { - const otherDomainObject = getAnyMeasureDomainObject(renderTarget); +// ================================================== +// PRIVATE FUNCTIONS +// ================================================== + +function initializeStyle(domainObject: DomainObject, rootDomainObject: RootDomainObject): void { + const otherDomainObject = rootDomainObject.getDescendantByType(MeasureDomainObject); if (otherDomainObject === undefined) { return; } @@ -341,3 +327,18 @@ function initializeStyle(domainObject: DomainObject, renderTarget: RevealRenderT } style.depthTest = otherStyle.depthTest; } + +function createCreator(measureType: MeasureType): BaseCreator | undefined { + switch (measureType) { + case MeasureType.Line: + case MeasureType.Polyline: + case MeasureType.Polygon: + return new MeasureLineCreator(measureType); + case MeasureType.HorizontalArea: + case MeasureType.VerticalArea: + case MeasureType.Volume: + return new MeasureBoxCreator(measureType); + default: + return undefined; + } +} diff --git a/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts b/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts index be6e75bbd44..1d1ddcfd074 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts @@ -24,13 +24,6 @@ export class SetMeasurmentTypeCommand extends RenderTargetCommand { // OVERRIDES of BaseCommand // ================================================== - public override equals(other: BaseCommand): boolean { - if (!(other instanceof SetMeasurmentTypeCommand)) { - return false; - } - return this._measureType === other._measureType; - } - public override get icon(): string { return getIconByMeasureType(this._measureType); } @@ -70,6 +63,13 @@ export class SetMeasurmentTypeCommand extends RenderTargetCommand { return true; } + public override equals(other: BaseCommand): boolean { + if (!(other instanceof SetMeasurmentTypeCommand)) { + return false; + } + return this._measureType === other._measureType; + } + // ================================================== // INSTANCE METHODS // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/ShowMeasurmentsOnTopCommand.ts b/react-components/src/architecture/concrete/boxDomainObject/ShowMeasurmentsOnTopCommand.ts index 319fcc7a964..9d66341e6c5 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/ShowMeasurmentsOnTopCommand.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/ShowMeasurmentsOnTopCommand.ts @@ -6,7 +6,7 @@ import { RenderTargetCommand } from '../../base/commands/RenderTargetCommand'; import { type Tooltip } from '../../base/commands/BaseCommand'; import { Changes } from '../../base/domainObjectsHelpers/Changes'; -import { getAnyMeasureDomainObject, getMeasureDomainObjects } from './MeasurementFunctions'; +import { MeasureDomainObject } from './MeasureDomainObject'; export class ShowMeasurmentsOnTopCommand extends RenderTargetCommand { // ================================================== @@ -22,7 +22,7 @@ export class ShowMeasurmentsOnTopCommand extends RenderTargetCommand { } public override get isEnabled(): boolean { - const domainObject = getAnyMeasureDomainObject(this.renderTarget); + const domainObject = this.rootDomainObject.getDescendantByType(MeasureDomainObject); return domainObject !== undefined; } @@ -36,7 +36,7 @@ export class ShowMeasurmentsOnTopCommand extends RenderTargetCommand { protected override invokeCore(): boolean { const depthTest = this.getDepthTest(); - for (const domainObject of getMeasureDomainObjects(this.renderTarget)) { + for (const domainObject of this.rootDomainObject.getDescendantsByType(MeasureDomainObject)) { const style = domainObject.renderStyle; style.depthTest = !depthTest; domainObject.notify(Changes.renderStyle); @@ -49,7 +49,7 @@ export class ShowMeasurmentsOnTopCommand extends RenderTargetCommand { // ================================================== public getDepthTest(): boolean { - const domainObject = getAnyMeasureDomainObject(this.renderTarget); + const domainObject = this.rootDomainObject.getDescendantByType(MeasureDomainObject); if (domainObject === undefined) { return false; } diff --git a/react-components/src/architecture/concrete/terrainDomainObject/TerrainDomainObject.ts b/react-components/src/architecture/concrete/terrainDomainObject/TerrainDomainObject.ts index dcaad6dd14d..16f75151fea 100644 --- a/react-components/src/architecture/concrete/terrainDomainObject/TerrainDomainObject.ts +++ b/react-components/src/architecture/concrete/terrainDomainObject/TerrainDomainObject.ts @@ -50,7 +50,9 @@ export class TerrainDomainObject extends VisualDomainObject { if (!(style instanceof TerrainRenderStyle)) { return; } - // The rest checks if the increment is valid. To many contour lines with hang/crash the app. + // The rest checks if the increment is valid. + // Too many contour lines with hang/crash the app, so it recalculate + // its value if it is not set or too large/small. const { grid } = this; if (grid === undefined) { return; diff --git a/react-components/src/architecture/concrete/terrainDomainObject/geometry/ContouringService.ts b/react-components/src/architecture/concrete/terrainDomainObject/geometry/ContouringService.ts index fe7c7ece6b9..8beb4a574d6 100644 --- a/react-components/src/architecture/concrete/terrainDomainObject/geometry/ContouringService.ts +++ b/react-components/src/architecture/concrete/terrainDomainObject/geometry/ContouringService.ts @@ -5,7 +5,7 @@ import { Vector3 } from 'three'; import { Range1 } from '../../../base/utilities/geometry/Range1'; import { type RegularGrid2 } from './RegularGrid2'; -import { isAbsEqual, isBetween, max, min } from '../../../base/utilities/extensions/mathExtensions'; +import { isAbsEqual, isBetween } from '../../../base/utilities/extensions/mathExtensions'; export class ContouringService { // ================================================== @@ -80,7 +80,7 @@ export class ContouringService { // ================================================== private addTriangle(a: Vector3, b: Vector3, c: Vector3): void { - this._tempRange.set(min(a.z, b.z, c.z), max(a.z, b.z, c.z)); + this._tempRange.set(Math.min(a.z, b.z, c.z), Math.max(a.z, b.z, c.z)); for (const anyTick of this._tempRange.getFastTicks(this._increment, this._tolerance)) { const z = Number(anyTick); From 9ac6395ddc90e4e86b66004d7c3f4f85e23bb058 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sat, 25 May 2024 12:03:15 +0200 Subject: [PATCH 02/27] Some movements --- .../base/domainObjects/DomainObject.ts | 117 +++++++++--------- .../Architecture/DomainObjectPanel.tsx | 4 +- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/react-components/src/architecture/base/domainObjects/DomainObject.ts b/react-components/src/architecture/base/domainObjects/DomainObject.ts index de31761cc66..d1374c21dd8 100644 --- a/react-components/src/architecture/base/domainObjects/DomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/DomainObject.ts @@ -72,33 +72,6 @@ export abstract class DomainObject { return `${this.name} [${nameExtension}]`; } - // ================================================== - // VIRTUAL METHODS: Others - // ================================================== - - /** - * Initializes the core functionality of the domain object. - * This method should be overridden in derived classes to provide custom implementation. - * @param change - The change object representing the update. - * @remarks - * Always call `super.initializeCore()` in the overrides. - */ - protected initializeCore(): void {} - - /** - * Removes the core functionality of the domain object. - * This method should be overridden in derived classes to provide custom implementation. - * @remarks - * Always call `super.removeCore()` in the overrides. - */ - protected removeCore(): void { - this.views.clear(); - } - - public get icon(): string { - return 'Unknown'; - } - // ================================================== // INSTANCE/VIRTUAL METHODS: Nameing // ================================================== @@ -126,29 +99,6 @@ export abstract class DomainObject { return equalsIgnoreCase(this.name, name); } - // ================================================== - // INSTANCE/VIRTUAL METHODS: Notification - // ================================================== - - /** - * Notifies the registered views and listeners about a change in the domain object. - * This method should be overridden in derived classes to provide custom implementation. - * @param change - The change object representing the update. - * @remarks - * Always call `super.notifyCore()` in the overrides. - */ - protected notifyCore(change: DomainObjectChange): void { - this.views.notify(this, change); - } - - public notify(change: DomainObjectChange | symbol): void { - if (change instanceof DomainObjectChange) { - this.notifyCore(change); - } else { - this.notify(new DomainObjectChange(change)); - } - } - // ================================================== // INSTANCE/VIRTUAL METHODS: Color // ================================================== @@ -245,7 +195,7 @@ export abstract class DomainObject { } // ================================================== - // INSTANCE/INSTANCE METHODS: Expanded + // INSTANCE/INSTANCE METHODS: Expanded in tree view // ================================================== public get canBeExpanded(): boolean { @@ -269,6 +219,33 @@ export abstract class DomainObject { return true; } + // ================================================== + // VIRTUAL METHODS: Appearance in the tree view + // ================================================== + + public get canBeRemoved(): boolean { + return true; // to be overridden + } + + public canBeChecked(_target: RevealRenderTarget): boolean { + return true; // to be overridden + } + + // ================================================== + // VIRTUAL METHODS: Notification + // ================================================== + + /** + * Notifies the registered views and listeners about a change in the domain object. + * This method should be overridden in derived classes to provide custom implementation. + * @param change - The change object representing the update. + * @remarks + * Always call `super.notifyCore()` in the overrides. + */ + protected notifyCore(change: DomainObjectChange): void { + this.views.notify(this, change); + } + // ================================================== // VIRTUAL METHODS: For updating the panel // ================================================== @@ -284,15 +261,21 @@ export abstract class DomainObject { } // ================================================== - // VIRTUAL METHODS: Appearance in the explorer + // VIRTUAL METHODS: Others // ================================================== - public get canBeDeleted(): boolean { - return true; // to be overridden + public get icon(): string { + return 'Unknown'; } - public canBeChecked(_target: RevealRenderTarget): boolean { - return true; // to be overridden + /** + * Removes the core functionality of the domain object. + * This method should be overridden in derived classes to provide custom implementation. + * @remarks + * Always call `super.removeCore()` in the overrides. + */ + protected removeCore(): void { + this.views.clear(); } // ================================================== @@ -404,9 +387,25 @@ export abstract class DomainObject { } // ================================================== - // INSTANCE METHODS: Visibility + // INSTANCE METHODS: Notification // ================================================== + public notify(change: DomainObjectChange | symbol): void { + if (!(change instanceof DomainObjectChange)) { + change = new DomainObjectChange(change); + } + this.notifyCore(change); + } + + public notifyRecursive(change: DomainObjectChange | symbol): void { + if (!(change instanceof DomainObjectChange)) { + change = new DomainObjectChange(change); + } + for (const descendant of this.getDescendants()) { + descendant.notify(change); + } + } + protected notifyVisibleStateChange(): void { const change = new DomainObjectChange(Changes.visibleState); this.notify(change); @@ -417,6 +416,9 @@ export abstract class DomainObject { descendant.notify(change); } } + // ================================================== + // INSTANCE METHODS: Visibility + // ================================================== public toggleVisibleInteractive(renderTarget: RevealRenderTarget): void { const visibleState = this.getVisibleState(renderTarget); @@ -678,6 +680,7 @@ export abstract class DomainObject { } public removeInteractive(): void { + // You may call canBeDeleted() before calling this for (const child of this.children) { child.removeInteractive(); } diff --git a/react-components/src/components/Architecture/DomainObjectPanel.tsx b/react-components/src/components/Architecture/DomainObjectPanel.tsx index c00bb7125cb..e4a0758d9b8 100644 --- a/react-components/src/components/Architecture/DomainObjectPanel.tsx +++ b/react-components/src/components/Architecture/DomainObjectPanel.tsx @@ -64,7 +64,9 @@ export const DomainObjectPanel = (): ReactElement => { appendTo={document.body}> From 0c69ba7fe02d02cd6ab7db262018f02b2a78f42c Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sat, 25 May 2024 12:05:01 +0200 Subject: [PATCH 03/27] Fix linter problems --- .../src/architecture/base/utilities/geometry/Range1.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/react-components/src/architecture/base/utilities/geometry/Range1.ts b/react-components/src/architecture/base/utilities/geometry/Range1.ts index 9e32d44002a..4144fb8d33c 100644 --- a/react-components/src/architecture/base/utilities/geometry/Range1.ts +++ b/react-components/src/architecture/base/utilities/geometry/Range1.ts @@ -2,12 +2,7 @@ * Copyright 2024 Cognite AS */ -import { - ceil, - floor, - isIncrement, - roundIncrement as roundIncrement -} from '../extensions/mathExtensions'; +import { ceil, floor, isIncrement, roundIncrement } from '../extensions/mathExtensions'; const MAX_NUMBER_OF_TICKS = 1000; export class Range1 { From 53499d66062a44108a81fcdfad6112bd1499feeb Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sat, 25 May 2024 12:19:29 +0200 Subject: [PATCH 04/27] Make the axis a tiny better --- .../utilities/extensions/mathExtensions.ts | 2 +- .../src/architecture/base/views/ThreeView.ts | 2 +- .../concrete/axis/AxisRenderStyle.ts | 2 +- .../concrete/axis/AxisThreeView.ts | 28 ++++++++----------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts b/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts index f8c05c6ed29..70625ce2e96 100644 --- a/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts +++ b/react-components/src/architecture/base/utilities/extensions/mathExtensions.ts @@ -100,7 +100,7 @@ export function roundIncrement(increment: number): number { } else { inc = 10; } - // Upscale the inc to the real number + // Upscale the increment to the real number if (exp < 0) { for (; exp !== 0; exp++) inc /= 10; } else { diff --git a/react-components/src/architecture/base/views/ThreeView.ts b/react-components/src/architecture/base/views/ThreeView.ts index 143e6045e0d..43e3813da1d 100644 --- a/react-components/src/architecture/base/views/ThreeView.ts +++ b/react-components/src/architecture/base/views/ThreeView.ts @@ -19,7 +19,7 @@ export abstract class ThreeView extends BaseView { // INSTANCE FIELDS // ================================================== - private _boundingBox: Box3 | undefined = undefined; // Cashe of the bounding box of the view + private _boundingBox: Box3 | undefined = undefined; // Cache of the bounding box of the view private _renderTarget: RevealRenderTarget | undefined = undefined; // ================================================== diff --git a/react-components/src/architecture/concrete/axis/AxisRenderStyle.ts b/react-components/src/architecture/concrete/axis/AxisRenderStyle.ts index 5992ff0fcbf..ba594132ba0 100644 --- a/react-components/src/architecture/concrete/axis/AxisRenderStyle.ts +++ b/react-components/src/architecture/concrete/axis/AxisRenderStyle.ts @@ -25,7 +25,7 @@ export class AxisRenderStyle extends RenderStyle { public showAxisTicks = true; public showGrid = true; - public numberOfTicks = 30; // Appoximately number of ticks for the largest axis + public numberOfTicks = 40; // Appoximately number of ticks for the largest axis public tickLength = 0.005; // In fraction of the bounding box diagonal public tickFontSize = 2; // In fraction of the real tickLength public axisLabelFontSize = 4; // In fraction of the real tickLength diff --git a/react-components/src/architecture/concrete/axis/AxisThreeView.ts b/react-components/src/architecture/concrete/axis/AxisThreeView.ts index 3e0d916008b..4cbd7396196 100644 --- a/react-components/src/architecture/concrete/axis/AxisThreeView.ts +++ b/react-components/src/architecture/concrete/axis/AxisThreeView.ts @@ -42,7 +42,7 @@ export class AxisThreeView extends GroupThreeView { private readonly _corners: Vector3[]; private readonly _faceCenters: Vector3[]; - private readonly _sceneBoundingBox: Box3 = new Box3().makeEmpty(); // Caching the bounding box of the scene + private readonly _sceneBoundingBox: Box3 = new Box3().makeEmpty(); // Casehing the bounding box of the scene private readonly _expandedSceneBoundingBox: Range3 = new Range3(); // ================================================== @@ -295,19 +295,14 @@ export class AxisThreeView extends GroupThreeView { // Find the best position by collision detect const position = newVector3(); - if (labelCount >= 2) { - let tick = minLabelTick + Math.round(0.5 * labelCount - 0.5) * labelInc; - if (labelInc === increment) { - tick -= increment / 2; - } else { - tick -= increment; - } - position.copy(this._corners[i0]); - position.setComponent(dimension, tick); + let tick = minLabelTick + Math.round(0.5 * labelCount - 0.5) * labelInc; + if (labelInc === increment) { + tick -= increment / 2; } else { - position.copy(this._corners[i0]); - position.add(this._corners[i1]); + tick -= increment; } + position.copy(this._corners[i0]); + position.setComponent(dimension, tick); position.addScaledVector(tickDirection, tickLength * 5); const sprite = createSpriteWithText( @@ -315,11 +310,12 @@ export class AxisThreeView extends GroupThreeView { labelFontSize, style.textColor ); - if (sprite !== undefined) { - moveSpriteByPositionAndDirection(sprite, position, tickDirection); - this.addChild(sprite); - this.setUserDataOnAxis(sprite, faceIndex1, faceIndex2, true); + if (sprite === undefined) { + return; } + moveSpriteByPositionAndDirection(sprite, position, tickDirection); + this.addChild(sprite); + this.setUserDataOnAxis(sprite, faceIndex1, faceIndex2, true); } } From 648d9162bffe05d3a790c4d74d550345f4511e8f Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sat, 25 May 2024 12:32:53 +0200 Subject: [PATCH 05/27] Comments only --- .../src/architecture/base/views/GroupThreeView.ts | 15 ++++++++++++++- .../src/architecture/base/views/ThreeView.ts | 7 +++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/react-components/src/architecture/base/views/GroupThreeView.ts b/react-components/src/architecture/base/views/GroupThreeView.ts index e79fc8fbedb..b0b62a2fac4 100644 --- a/react-components/src/architecture/base/views/GroupThreeView.ts +++ b/react-components/src/architecture/base/views/GroupThreeView.ts @@ -15,9 +15,12 @@ import { type DomainObjectIntersection } from '../domainObjectsHelpers/DomainObj /** * Represents an abstract class for a Three.js view that renders an Object3D. - * This class extends the ThreeView class. + * This class extends the ThreeView class. It created a group object3D and adds children to it. * @remarks * You only have to override addChildren() to create the object3D to be added to the group. + * Other methods you may override is: + * - intersectIfCloser() to determine the exact intersection point. + * - calculateBoundingBox() to calculate the bounding box if you don not relay on three.js. */ export abstract class GroupThreeView extends ThreeView implements ICustomObject { @@ -33,6 +36,8 @@ export abstract class GroupThreeView extends ThreeView implements ICustomObject // ================================================== // IMPLEMENTATION of ICustomObject + // some of the methods are virtual and can be overridden, + // see each method. // ================================================== public get object(): Object3D { @@ -131,6 +136,14 @@ export abstract class GroupThreeView extends ThreeView implements ICustomObject // OVERRIDES of ThreeView // ================================================== + /** + * Calculates the bounding box of the object. + * Overrides this if you want to calculate the bounding box in a + * different way that three.js does it. For instance if you don't + * want to include text labels in the bounding box. + * + * @returns The calculated bounding box. + */ protected override calculateBoundingBox(): Box3 { if (this.object === undefined) { return new Box3().makeEmpty(); diff --git a/react-components/src/architecture/base/views/ThreeView.ts b/react-components/src/architecture/base/views/ThreeView.ts index 43e3813da1d..9718315d77e 100644 --- a/react-components/src/architecture/base/views/ThreeView.ts +++ b/react-components/src/architecture/base/views/ThreeView.ts @@ -12,7 +12,10 @@ import { type PerspectiveCamera, type Box3 } from 'three'; /** * Represents an abstract base class for a Three.js view in the application. - * Extends the `BaseView` class. + * Extends the `BaseView` class. It only has the poiinter to the renderTarget and a bounding box. + * Inirit from this class if you want some visualization that do dot require a group object3D as the root object. + * It can for instance be a view that chages somting on another view, dor instance texture on a surface or whatever. + * I just wanted to make it ready for some corner cases I have seen during a long time as 3D develper. */ export abstract class ThreeView extends BaseView { // ================================================== @@ -87,7 +90,7 @@ export abstract class ThreeView extends BaseView { /** * This method is called before rendering the view. * Override this function to perform any necessary operations - * just before rendering.Have in mind that the Object3D are build at the time this is + * just before rendering. Have in mind that the Object3D are build at the time this is * called, so you can only do adjustment on existing object3D's. * @remarks * Always call `super.beforeRender(camera)` in the overrides. From c23aa7d6f682e8c9e547d0b6611cb56255410cc5 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sat, 25 May 2024 16:10:44 +0200 Subject: [PATCH 06/27] Some rafactoring --- .../base/commands/BaseEditTool.ts | 16 ++- .../architecture/base/commands/BaseTool.ts | 7 +- .../base/domainObjects/DomainObject.ts | 16 --- .../base/domainObjects/VisualDomainObject.ts | 20 ++++ .../base/domainObjectsHelpers/BaseDragger.ts | 12 +- .../src/architecture/base/views/BaseView.ts | 14 ++- .../src/architecture/base/views/ThreeView.ts | 2 +- .../concrete/axis/SetAxisVisibleCommand.ts | 6 +- .../boxDomainObject/MeasureBoxDomainObject.ts | 10 +- .../boxDomainObject/MeasureBoxDragger.ts | 11 +- .../boxDomainObject/MeasureBoxView.ts | 112 +++++++++--------- .../boxDomainObject/MeasureLineView.ts | 30 ++--- .../boxDomainObject/MeasurementTool.ts | 7 +- .../SetTerrainVisibleCommand.ts | 4 +- .../terrainDomainObject/TerrainThreeView.ts | 28 ++--- .../UpdateTerrainCommand.ts | 7 +- 16 files changed, 151 insertions(+), 151 deletions(-) diff --git a/react-components/src/architecture/base/commands/BaseEditTool.ts b/react-components/src/architecture/base/commands/BaseEditTool.ts index 91bb1dd4d98..afb6f425d98 100644 --- a/react-components/src/architecture/base/commands/BaseEditTool.ts +++ b/react-components/src/architecture/base/commands/BaseEditTool.ts @@ -6,6 +6,8 @@ import { NavigationTool } from './NavigationTool'; import { type DomainObject } from '../domainObjects/DomainObject'; import { isDomainObjectIntersection } from '../domainObjectsHelpers/DomainObjectIntersection'; import { type BaseDragger } from '../domainObjectsHelpers/BaseDragger'; +import { type VisualDomainObject } from '../domainObjects/VisualDomainObject'; +import { CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; /** * The `BaseEditTool` class is an abstract class that extends the `NavigationTool` class. @@ -64,7 +66,7 @@ export abstract class BaseEditTool extends NavigationTool { } // ================================================== - // VIRTUALS METHODS + // VIRTUAL METHODS // ================================================== /** @@ -80,11 +82,16 @@ export abstract class BaseEditTool extends NavigationTool { if (!isDomainObjectIntersection(intersection)) { return undefined; } - const domainObject = intersection.domainObject; + const domainObject = intersection.domainObject as VisualDomainObject; if (domainObject === undefined) { return undefined; } - return domainObject.createDragger(intersection); + const ray = this.getRay(event); + const matrix = CDF_TO_VIEWER_TRANSFORMATION.clone().invert(); + const point = intersection.point.clone(); + point.applyMatrix4(matrix); + ray.applyMatrix4(matrix); + return domainObject.createDragger({ intersection, point, ray }); } // ================================================== @@ -92,8 +99,7 @@ export abstract class BaseEditTool extends NavigationTool { // ================================================== protected deselectAll(except?: DomainObject | undefined): void { - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; + const { rootDomainObject } = this; for (const domainObject of rootDomainObject.getDescendants()) { if (except !== undefined && domainObject === except) { continue; diff --git a/react-components/src/architecture/base/commands/BaseTool.ts b/react-components/src/architecture/base/commands/BaseTool.ts index 8e31fe6de94..af3ca3a4b20 100644 --- a/react-components/src/architecture/base/commands/BaseTool.ts +++ b/react-components/src/architecture/base/commands/BaseTool.ts @@ -120,7 +120,9 @@ export abstract class BaseTool extends RenderTargetCommand { this.renderTarget.cursor = this.defaultCursor; } - protected async getIntersection(event: PointerEvent): Promise { + protected async getIntersection( + event: PointerEvent | WheelEvent + ): Promise { const { renderTarget } = this; const { viewer } = renderTarget; const point = viewer.getPixelCoordinatesFromEvent(event); @@ -136,8 +138,7 @@ export abstract class BaseTool extends RenderTargetCommand { classType: Class ): DomainObjectIntersection | undefined { // This function is similar to getIntersection, but it only considers a specific DomainObject - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; + const { renderTarget, rootDomainObject } = this; const { viewer } = renderTarget; const point = viewer.getPixelCoordinatesFromEvent(event); diff --git a/react-components/src/architecture/base/domainObjects/DomainObject.ts b/react-components/src/architecture/base/domainObjects/DomainObject.ts index d1374c21dd8..ec7db29f776 100644 --- a/react-components/src/architecture/base/domainObjects/DomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/DomainObject.ts @@ -14,8 +14,6 @@ import { getNextColor } from '../utilities/colors/getNextColor'; import { type RevealRenderTarget } from '../renderTarget/RevealRenderTarget'; import { ColorType } from '../domainObjectsHelpers/ColorType'; import { BLACK_COLOR, WHITE_COLOR } from '../utilities/colors/colorExtensions'; -import { type DomainObjectIntersection } from '../domainObjectsHelpers/DomainObjectIntersection'; -import { type BaseDragger } from '../domainObjectsHelpers/BaseDragger'; import { Views } from '../domainObjectsHelpers/Views'; import { type PanelInfo } from '../domainObjectsHelpers/PanelInfo'; import { PopupStyle } from '../domainObjectsHelpers/PopupStyle'; @@ -309,20 +307,6 @@ export abstract class DomainObject { */ public verifyRenderStyle(_style: RenderStyle): void {} - // ================================================== - // VIRTUAL METHODS: Create dragger - // ================================================== - - /** - * Factory method to create a dragger to interpret the mouse dragging operation - * This function is used in BaseEditTool - * @returns The render style - */ - // override when creating a dragger operation in the BaseEditTool - public createDragger(_intersection: DomainObjectIntersection): BaseDragger | undefined { - return undefined; - } - // ================================================== // VIRTUAL METHODS: Visibility // ================================================== diff --git a/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts b/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts index f77e09962cc..29ba7a64eea 100644 --- a/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/VisualDomainObject.ts @@ -6,11 +6,15 @@ import { type RevealRenderTarget } from '../renderTarget/RevealRenderTarget'; import { ThreeView } from '../views/ThreeView'; import { VisibleState } from '../domainObjectsHelpers/VisibleState'; import { DomainObject } from './DomainObject'; +import { type DomainObjectIntersection } from '../domainObjectsHelpers/DomainObjectIntersection'; +import { type BaseDragger } from '../domainObjectsHelpers/BaseDragger'; +import { type Vector3, type Ray } from 'three'; /** * Represents a visual domain object that can be rendered and manipulated in a three-dimensional space. * This class extends the `DomainObject` class and provides additional functionality for visualization. */ + export abstract class VisualDomainObject extends DomainObject { // ================================================== // OVERRIDES of DomainObject @@ -65,6 +69,16 @@ export abstract class VisualDomainObject extends DomainObject { return true; } + /** + * Factory method to create a dragger to interpret the mouse dragging operation + * This function is used in BaseEditTool + * + * @returns The dragger + */ + public createDragger(_props: CreateDraggerProps): BaseDragger | undefined { + return undefined; + } + // ================================================== // INSTANCE METHODS // ================================================== @@ -118,3 +132,9 @@ export abstract class VisualDomainObject extends DomainObject { return true; // State has changed } } + +export type CreateDraggerProps = { + intersection: DomainObjectIntersection; + point: Vector3; + ray: Ray; +}; diff --git a/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts b/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts index c2c918c8721..aee6d3481c9 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts @@ -2,8 +2,9 @@ * Copyright 2024 Cognite AS */ -import { type Ray, Vector3 } from 'three'; +import { Ray, type Vector3 } from 'three'; import { type DomainObject } from '../domainObjects/DomainObject'; +import { type CreateDraggerProps } from '../domainObjects/VisualDomainObject'; /** * The `BaseDragger` class represents a utility for dragging and manipulating any object in 3D space. @@ -14,14 +15,16 @@ export abstract class BaseDragger { // INSTANCE FIELDS // ================================================== - public readonly point: Vector3 = new Vector3(); // Intersection point at pointer down + protected readonly point: Vector3; // Intersection point at pointer down in CDF coordinates + protected readonly ray: Ray = new Ray(); // Intersection point at pointer down in CDF coordinates // ================================================== // CONTRUCTOR // ================================================== - protected constructor(startDragPoint: Vector3) { - this.point.copy(startDragPoint); + protected constructor(props: CreateDraggerProps) { + this.point = props.point; + this.ray = props.ray; } // ================================================== @@ -35,6 +38,7 @@ export abstract class BaseDragger { } // This must be overriden + // Notte that the ray comes in CDF coordinates public abstract onPointerDrag(_event: PointerEvent, ray: Ray): boolean; public onPointerUp(_event: PointerEvent): void { diff --git a/react-components/src/architecture/base/views/BaseView.ts b/react-components/src/architecture/base/views/BaseView.ts index 4f0a41c3d0d..4f35878ea0b 100644 --- a/react-components/src/architecture/base/views/BaseView.ts +++ b/react-components/src/architecture/base/views/BaseView.ts @@ -24,17 +24,13 @@ export abstract class BaseView { return this._domainObject !== undefined; } - public get domainObject(): DomainObject { + protected get domainObject(): DomainObject { if (this._domainObject === undefined) { throw Error('The DomainObject is missing in the view'); } return this._domainObject; } - protected set domainObject(value: DomainObject) { - this._domainObject = value; - } - // ================================================== // VIRTUAL METHODS // ================================================== @@ -91,4 +87,12 @@ export abstract class BaseView { public dispose(): void { this._domainObject = undefined; } + + // ================================================== + // INSTANCE METHODS + // ================================================== + + public setDomainObject(domainObject: DomainObject): void { + this._domainObject = domainObject; + } } diff --git a/react-components/src/architecture/base/views/ThreeView.ts b/react-components/src/architecture/base/views/ThreeView.ts index 9718315d77e..600e7a61e1b 100644 --- a/react-components/src/architecture/base/views/ThreeView.ts +++ b/react-components/src/architecture/base/views/ThreeView.ts @@ -113,7 +113,7 @@ export abstract class ThreeView extends BaseView { } public attach(domainObject: DomainObject, renderTarget: RevealRenderTarget): void { - this.domainObject = domainObject; + super.setDomainObject(domainObject); this._renderTarget = renderTarget; } diff --git a/react-components/src/architecture/concrete/axis/SetAxisVisibleCommand.ts b/react-components/src/architecture/concrete/axis/SetAxisVisibleCommand.ts index 81191d5f650..80882314e15 100644 --- a/react-components/src/architecture/concrete/axis/SetAxisVisibleCommand.ts +++ b/react-components/src/architecture/concrete/axis/SetAxisVisibleCommand.ts @@ -29,8 +29,7 @@ export class SetAxisVisibleCommand extends RenderTargetCommand { } public override get isChecked(): boolean { - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; + const { renderTarget, rootDomainObject } = this; const axis = rootDomainObject.getDescendantByType(AxisDomainObject); if (axis === undefined) { @@ -40,8 +39,7 @@ export class SetAxisVisibleCommand extends RenderTargetCommand { } protected override invokeCore(): boolean { - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; + const { renderTarget, rootDomainObject } = this; let axis = rootDomainObject.getDescendantByType(AxisDomainObject); if (axis === undefined) { diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts index 2c5db32b464..399fd6baa31 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts @@ -11,13 +11,13 @@ import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { BoxFace } from '../../base/utilities/box/BoxFace'; import { FocusType } from '../../base/domainObjectsHelpers/FocusType'; import { MeasureType } from './MeasureType'; -import { type DomainObjectIntersection } from '../../base/domainObjectsHelpers/DomainObjectIntersection'; import { type BoxPickInfo } from '../../base/utilities/box/BoxPickInfo'; import { type BaseDragger } from '../../base/domainObjectsHelpers/BaseDragger'; import { MeasureBoxDragger } from './MeasureBoxDragger'; import { MeasureDomainObject } from './MeasureDomainObject'; import { NumberType, PanelInfo } from '../../base/domainObjectsHelpers/PanelInfo'; import { radToDeg } from 'three/src/math/MathUtils.js'; +import { type CreateDraggerProps } from '../../base/domainObjects/VisualDomainObject'; export const MIN_BOX_SIZE = 0.01; @@ -101,12 +101,12 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { return new MeasureBoxRenderStyle(); } - public override createDragger(intersection: DomainObjectIntersection): BaseDragger | undefined { - const pickInfo = intersection.userData as BoxPickInfo; + public override createDragger(props: CreateDraggerProps): BaseDragger | undefined { + const pickInfo = props.intersection.userData as BoxPickInfo; if (pickInfo === undefined) { - return undefined; + return undefined; // If the BoxPickInfo isn't specified, no dragger iscreated } - return new MeasureBoxDragger(this, intersection.point, pickInfo); + return new MeasureBoxDragger(props, this); } public override getPanelInfo(): PanelInfo | undefined { diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts index 46711e76ce9..69723791092 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts @@ -15,7 +15,7 @@ import { MeasureType } from './MeasureType'; import { getClosestPointOnLine } from '../../base/utilities/extensions/rayExtensions'; import { type MeasureBoxDomainObject } from './MeasureBoxDomainObject'; import { BaseDragger } from '../../base/domainObjectsHelpers/BaseDragger'; -import { CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; +import { type CreateDraggerProps } from '../../base/domainObjects/VisualDomainObject'; /** * The `BoxDragger` class represents a utility for dragging and manipulating a box in a 3D space. @@ -57,11 +57,10 @@ export class MeasureBoxDragger extends BaseDragger { // CONTRUCTOR // ================================================== - public constructor(domainObject: MeasureBoxDomainObject, point: Vector3, pickInfo: BoxPickInfo) { - point = point.clone(); - point.applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION.clone().invert()); - super(point); + public constructor(props: CreateDraggerProps, domainObject: MeasureBoxDomainObject) { + super(props); + const pickInfo = props.intersection.userData as BoxPickInfo; this._domainObject = domainObject; this._face = pickInfo.face; this._focusType = pickInfo.focusType; @@ -72,7 +71,7 @@ export class MeasureBoxDragger extends BaseDragger { this._normal.applyMatrix4(rotationMatrix); this._normal.normalize(); - this._planeOfBox.setFromNormalAndCoplanarPoint(this._normal, point); + this._planeOfBox.setFromNormalAndCoplanarPoint(this._normal, this.point); // Back up the original values this._sizeOfBox.copy(this._domainObject.size); diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts index 734b0ccf63f..146f662c4cc 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts @@ -66,7 +66,7 @@ export class MeasureBoxView extends GroupThreeView { // INSTANCE PROPERTIES // ================================================== - protected get boxDomainObject(): MeasureBoxDomainObject { + protected override get domainObject(): MeasureBoxDomainObject { return super.domainObject as MeasureBoxDomainObject; } @@ -112,10 +112,10 @@ export class MeasureBoxView extends GroupThreeView { // ================================================== protected override addChildren(): void { - const { boxDomainObject } = this; + const { domainObject } = this; const matrix = this.getMatrix(); - const { focusType } = boxDomainObject; + const { focusType } = domainObject; this.addChild(this.createSolid(matrix)); this.addChild(this.createLines(matrix)); if (showMarkers(focusType)) { @@ -129,8 +129,8 @@ export class MeasureBoxView extends GroupThreeView { intersectInput: CustomObjectIntersectInput, closestDistance: number | undefined ): undefined | CustomObjectIntersection { - const { boxDomainObject } = this; - if (boxDomainObject.focusType === FocusType.Pending) { + const { domainObject } = this; + if (domainObject.focusType === FocusType.Pending) { return undefined; // Should never be picked } const orientedBox = createOrientedBox(); @@ -165,7 +165,7 @@ export class MeasureBoxView extends GroupThreeView { distanceToCamera, userData: new BoxPickInfo(boxFace, focusType, cornerSign), customObject: this, - domainObject: boxDomainObject + domainObject }; if (this.shouldPickBoundingBox) { customObjectIntersection.boundingBox = this.boundingBox; @@ -178,26 +178,26 @@ export class MeasureBoxView extends GroupThreeView { // ================================================== private getTextHeight(relativeTextSize: number): number { - return relativeTextSize * this.boxDomainObject.diagonal; + return relativeTextSize * this.domainObject.diagonal; } private getFaceRadius(boxFace: BoxFace): number { - const { size } = this.boxDomainObject; + const { size } = this.domainObject; const size1 = size.getComponent(boxFace.tangentIndex1); const size2 = size.getComponent(boxFace.tangentIndex2); return (size1 + size2) / 4; } private getMatrix(): Matrix4 { - const { boxDomainObject } = this; - const matrix = boxDomainObject.getMatrix(); + const { domainObject } = this; + const matrix = domainObject.getMatrix(); matrix.premultiply(CDF_TO_VIEWER_TRANSFORMATION); return matrix; } private getRotationMatrix(): Matrix4 { - const { boxDomainObject } = this; - const matrix = boxDomainObject.getRotationMatrix(); + const { domainObject } = this; + const matrix = domainObject.getRotationMatrix(); matrix.premultiply(CDF_TO_VIEWER_TRANSFORMATION); return matrix; } @@ -207,11 +207,11 @@ export class MeasureBoxView extends GroupThreeView { // ================================================== private createSolid(matrix: Matrix4): Object3D | undefined { - const { boxDomainObject } = this; + const { domainObject } = this; const { style } = this; const material = new MeshPhongMaterial(); - updateSolidMaterial(material, boxDomainObject, style); + updateSolidMaterial(material, domainObject, style); const geometry = new BoxGeometry(1, 1, 1); const result = new Mesh(geometry, material); result.applyMatrix4(matrix); @@ -219,11 +219,11 @@ export class MeasureBoxView extends GroupThreeView { } private createLines(matrix: Matrix4): Object3D | undefined { - const { boxDomainObject } = this; + const { domainObject } = this; const { style } = this; const material = new LineBasicMaterial(); - updateLineSegmentsMaterial(material, boxDomainObject, style); + updateLineSegmentsMaterial(material, domainObject, style); const geometry = createLineSegmentsBufferGeometryForBox(); const result = new LineSegments(geometry, material); @@ -235,8 +235,8 @@ export class MeasureBoxView extends GroupThreeView { if (!this.isFaceVisible(TOP_FACE)) { return undefined; } - const { boxDomainObject } = this; - const degrees = radToDeg(boxDomainObject.zRotation); + const { domainObject } = this; + const degrees = radToDeg(domainObject.zRotation); const text = degrees.toFixed(1); if (text === '0.0') { return undefined; // Not show when about 0 @@ -247,7 +247,7 @@ export class MeasureBoxView extends GroupThreeView { } const faceCenter = TOP_FACE.getCenter(newVector3()); faceCenter.applyMatrix4(matrix); - adjustLabel(faceCenter, boxDomainObject, spriteHeight); + adjustLabel(faceCenter, domainObject, spriteHeight); sprite.position.copy(faceCenter); return sprite; } @@ -262,7 +262,7 @@ export class MeasureBoxView extends GroupThreeView { } const faceCenter = TOP_FACE.getCenter(newVector3()); faceCenter.applyMatrix4(matrix); - adjustLabel(faceCenter, this.boxDomainObject, spriteHeight); + adjustLabel(faceCenter, this.domainObject, spriteHeight); sprite.position.copy(faceCenter); return sprite; } @@ -271,8 +271,8 @@ export class MeasureBoxView extends GroupThreeView { if (!this.isFaceVisible(TOP_FACE)) { return undefined; } - const { boxDomainObject, style } = this; - const { focusType } = boxDomainObject; + const { domainObject, style } = this; + const { focusType } = domainObject; const radius = this.getFaceRadius(TOP_FACE); const outerRadius = RELATIVE_ROTATION_RADIUS.max * radius; @@ -280,7 +280,7 @@ export class MeasureBoxView extends GroupThreeView { const geometry = new RingGeometry(innerRadius, outerRadius, CIRCULAR_SEGMENTS); const material = new MeshPhongMaterial(); - updateMarkerMaterial(material, boxDomainObject, style, focusType === FocusType.Rotation); + updateMarkerMaterial(material, domainObject, style, focusType === FocusType.Rotation); material.clippingPlanes = this.createClippingPlanes(matrix, TOP_FACE.index); const mesh = new Mesh(geometry, material); @@ -292,12 +292,12 @@ export class MeasureBoxView extends GroupThreeView { } private createEdgeCircle(matrix: Matrix4, material: Material, face: BoxFace): Mesh | undefined { - const { boxDomainObject } = this; - const adjecentSize1 = boxDomainObject.size.getComponent(face.tangentIndex1); + const { domainObject } = this; + const adjecentSize1 = domainObject.size.getComponent(face.tangentIndex1); if (!isValid(adjecentSize1)) { return undefined; } - const adjecentSize2 = boxDomainObject.size.getComponent(face.tangentIndex2); + const adjecentSize2 = domainObject.size.getComponent(face.tangentIndex2); if (!isValid(adjecentSize2)) { return undefined; } @@ -317,13 +317,13 @@ export class MeasureBoxView extends GroupThreeView { } else if (face.face === 5) { mesh.rotateX(Math.PI / 2); } else if (face.face === 0) { - mesh.rotateY(Math.PI / 2 + boxDomainObject.zRotation); + mesh.rotateY(Math.PI / 2 + domainObject.zRotation); } else if (face.face === 3) { - mesh.rotateY(-Math.PI / 2 + boxDomainObject.zRotation); + mesh.rotateY(-Math.PI / 2 + domainObject.zRotation); } else if (face.face === 1) { - mesh.rotateY(Math.PI + boxDomainObject.zRotation); + mesh.rotateY(Math.PI + domainObject.zRotation); } else if (face.face === 4) { - mesh.rotateY(boxDomainObject.zRotation); + mesh.rotateY(domainObject.zRotation); } return mesh; } @@ -349,11 +349,11 @@ export class MeasureBoxView extends GroupThreeView { // ================================================== private addLabels(matrix: Matrix4): void { - const { boxDomainObject, style } = this; + const { domainObject, style } = this; const spriteHeight = this.getTextHeight(style.relativeTextSize); clear(this._sprites); for (let index = 0; index < 3; index++) { - const size = boxDomainObject.size.getComponent(index); + const size = domainObject.size.getComponent(index); if (!isValid(size)) { this._sprites.push(undefined); continue; @@ -367,8 +367,8 @@ export class MeasureBoxView extends GroupThreeView { this.addChild(sprite); } this.updateLabels(this.renderTarget.camera); - const { focusType } = boxDomainObject; - if (focusType === FocusType.Pending && boxDomainObject.hasArea) { + const { focusType } = domainObject; + if (focusType === FocusType.Pending && domainObject.hasArea) { this.addChild(this.createPendingLabel(matrix, spriteHeight)); } else if (showRotationLabel(focusType)) { this.addChild(this.createRotationLabel(matrix, spriteHeight)); @@ -376,11 +376,11 @@ export class MeasureBoxView extends GroupThreeView { } private updateLabels(camera: Camera): void { - const { boxDomainObject, style } = this; + const { domainObject, style } = this; const matrix = this.getMatrix(); const rotationMatrix = this.getRotationMatrix(); - const centerOfBox = newVector3(boxDomainObject.center); + const centerOfBox = newVector3(domainObject.center); centerOfBox.applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION); const cameraPosition = camera.getWorldPosition(newVector3()); const cameraDirection = centerOfBox.sub(cameraPosition).normalize(); @@ -423,7 +423,7 @@ export class MeasureBoxView extends GroupThreeView { const edgeCenter = faceCenter2.add(faceCenter1); edgeCenter.applyMatrix4(matrix); - adjustLabel(edgeCenter, boxDomainObject, spriteHeight); + adjustLabel(edgeCenter, domainObject, spriteHeight); // Move the sprite slightly away from the box to avoid z-fighting edgeCenter.addScaledVector(cameraDirection, -spriteHeight / 2); @@ -439,13 +439,13 @@ export class MeasureBoxView extends GroupThreeView { } private addEdgeCircles(matrix: Matrix4): void { - const { boxDomainObject, style } = this; - let selectedFace = boxDomainObject.focusFace; - if (this.boxDomainObject.focusType !== FocusType.Face) { + const { domainObject, style } = this; + let selectedFace = domainObject.focusFace; + if (this.domainObject.focusType !== FocusType.Face) { selectedFace = undefined; } const material = new MeshPhongMaterial(); - updateMarkerMaterial(material, boxDomainObject, style, false); + updateMarkerMaterial(material, domainObject, style, false); for (const boxFace of BoxFace.getAllFaces()) { if (!this.isFaceVisible(boxFace)) { continue; @@ -456,7 +456,7 @@ export class MeasureBoxView extends GroupThreeView { } if (selectedFace !== undefined && this.isFaceVisible(selectedFace)) { const material = new MeshPhongMaterial(); - updateMarkerMaterial(material, boxDomainObject, style, true); + updateMarkerMaterial(material, domainObject, style, true); this.addChild(this.createEdgeCircle(matrix, material, selectedFace)); } } @@ -470,9 +470,9 @@ export class MeasureBoxView extends GroupThreeView { boxFace: BoxFace, outputCornerSign: Vector3 ): FocusType { - const { boxDomainObject } = this; + const { domainObject } = this; const scale = newVector3().setScalar(this.getFaceRadius(boxFace)); - const scaledMatrix = boxDomainObject.getScaledMatrix(scale); + const scaledMatrix = domainObject.getScaledMatrix(scale); scaledMatrix.invert(); const scaledPositionAtFace = newVector3(realPosition).applyMatrix4(scaledMatrix); const planePoint = boxFace.getPlanePoint(scaledPositionAtFace); @@ -496,9 +496,9 @@ export class MeasureBoxView extends GroupThreeView { } private getCornerSign(realPosition: Vector3, boxFace: BoxFace): Vector3 { - const { boxDomainObject } = this; + const { domainObject } = this; const scale = newVector3().setScalar(this.getFaceRadius(boxFace)); - const scaledMatrix = boxDomainObject.getScaledMatrix(scale); + const scaledMatrix = domainObject.getScaledMatrix(scale); scaledMatrix.invert(); const scaledPositionAtFace = realPosition.clone().applyMatrix4(scaledMatrix); scaledPositionAtFace.setComponent(boxFace.index, 0); @@ -514,17 +514,17 @@ export class MeasureBoxView extends GroupThreeView { } private getCorner(cornerSign: Vector3, boxFace: BoxFace): Vector3 { - const { boxDomainObject } = this; + const { domainObject } = this; const center = boxFace.getCenter(new Vector3()); // In range (-0.5, 0.5) const corner = center.addScaledVector(cornerSign, 0.5); - const matrix = boxDomainObject.getMatrix(); + const matrix = domainObject.getMatrix(); corner.applyMatrix4(matrix); return corner; } private isFaceVisible(boxFace: BoxFace): boolean { - const { boxDomainObject } = this; - switch (boxDomainObject.measureType) { + const { domainObject } = this; + switch (domainObject.measureType) { case MeasureType.VerticalArea: return boxFace.index === 1; // Y Face visible @@ -566,11 +566,11 @@ function showMarkers(focusType: FocusType): boolean { function updateSolidMaterial( material: MeshPhongMaterial, - boxDomainObject: MeasureBoxDomainObject, + domainObject: MeasureBoxDomainObject, style: MeasureBoxRenderStyle ): void { - const color = boxDomainObject.getColorByColorType(style.colorType); - const isSelected = boxDomainObject.isSelected; + const color = domainObject.getColorByColorType(style.colorType); + const isSelected = domainObject.isSelected; const opacity = isSelected ? style.opacity : style.opacity / 4; material.polygonOffset = true; material.polygonOffsetFactor = 1; @@ -588,10 +588,10 @@ function updateSolidMaterial( function updateLineSegmentsMaterial( material: LineBasicMaterial, - boxDomainObject: MeasureBoxDomainObject, + domainObject: MeasureBoxDomainObject, style: MeasureBoxRenderStyle ): void { - const color = boxDomainObject.getColorByColorType(style.colorType); + const color = domainObject.getColorByColorType(style.colorType); material.color = color; material.transparent = true; material.depthWrite = false; @@ -600,7 +600,7 @@ function updateLineSegmentsMaterial( function updateMarkerMaterial( material: MeshPhongMaterial, - boxDomainObject: MeasureBoxDomainObject, + domainObject: MeasureBoxDomainObject, style: MeasureBoxRenderStyle, hasFocus: boolean ): void { diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts index a0f74b9186a..c4017d62a3d 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts @@ -39,7 +39,7 @@ export class MeasureLineView extends GroupThreeView { // INSTANCE PROPERTIES // ================================================== - protected get lineDomainObject(): MeasureLineDomainObject { + protected override get domainObject(): MeasureLineDomainObject { return super.domainObject as MeasureLineDomainObject; } @@ -81,7 +81,7 @@ export class MeasureLineView extends GroupThreeView { intersectInput: CustomObjectIntersectInput, closestDistance: number | undefined ): undefined | CustomObjectIntersection { - if (this.lineDomainObject.focusType === FocusType.Pending) { + if (this.domainObject.focusType === FocusType.Pending) { return undefined; // Should never be picked } return super.intersectIfCloser(intersectInput, closestDistance); @@ -92,8 +92,8 @@ export class MeasureLineView extends GroupThreeView { // ================================================== private createCylinders(): Mesh | undefined { - const { lineDomainObject, style } = this; - const { points } = lineDomainObject; + const { domainObject, style } = this; + const { points } = domainObject; const { length } = points; if (length < 2) { return undefined; @@ -103,7 +103,7 @@ export class MeasureLineView extends GroupThreeView { return; } const geometries: CylinderGeometry[] = []; - const loopLength = lineDomainObject.measureType === MeasureType.Polygon ? length + 1 : length; + const loopLength = domainObject.measureType === MeasureType.Polygon ? length + 1 : length; // Just allocate all needed objects once const prevPoint = new Vector3(); @@ -134,18 +134,18 @@ export class MeasureLineView extends GroupThreeView { prevPoint.copy(thisPoint); } const material = new MeshPhongMaterial(); - updateSolidMaterial(material, lineDomainObject, style); + updateSolidMaterial(material, domainObject, style); return new Mesh(mergeGeometries(geometries, false), material); } private createLines(): Wireframe | undefined { - const { lineDomainObject, style } = this; - const vertices = createVertices(lineDomainObject); + const { domainObject, style } = this; + const vertices = createVertices(domainObject); if (vertices === undefined) { return undefined; } - const color = lineDomainObject.getColorByColorType(style.colorType); - const linewidth = lineDomainObject.isSelected ? style.selectedLineWidth : style.lineWidth; + const color = domainObject.getColorByColorType(style.colorType); + const linewidth = domainObject.isSelected ? style.selectedLineWidth : style.lineWidth; const geometry = new LineSegmentsGeometry().setPositions(vertices); const material = new LineMaterial({ linewidth: linewidth / 50, @@ -158,8 +158,8 @@ export class MeasureLineView extends GroupThreeView { } private addLabels(): void { - const { lineDomainObject, style } = this; - const { points } = lineDomainObject; + const { domainObject, style } = this; + const { points } = domainObject; const { length } = points; if (length < 2) { return; @@ -168,7 +168,7 @@ export class MeasureLineView extends GroupThreeView { if (spriteHeight <= 0) { return; } - const loopLength = lineDomainObject.measureType === MeasureType.Polygon ? length : length - 1; + const loopLength = domainObject.measureType === MeasureType.Polygon ? length : length - 1; const center = new Vector3(); for (let i = 0; i < loopLength; i++) { const point1 = points[i % length]; @@ -181,14 +181,14 @@ export class MeasureLineView extends GroupThreeView { if (sprite === undefined) { continue; } - adjustLabel(center, lineDomainObject, style, spriteHeight); + adjustLabel(center, domainObject, style, spriteHeight); sprite.position.copy(center); this.addChild(sprite); } } private getTextHeight(relativeTextSize: number): number { - return relativeTextSize * this.lineDomainObject.getAverageLength(); + return relativeTextSize * this.domainObject.getAverageLength(); } } diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index 63310312561..b80ade1ed87 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -163,8 +163,7 @@ export class MeasurementTool extends BaseEditTool { } public override async onClick(event: PointerEvent): Promise { - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; + const { renderTarget, rootDomainObject } = this; const { _creator: creator } = this; // Click in the "air" @@ -244,10 +243,6 @@ export class MeasurementTool extends BaseEditTool { private getMeasurement( intersection: AnyIntersection | undefined ): MeasureDomainObject | undefined { - if (intersection === undefined) { - return undefined; - } - // Do not want to click on other boxes if (!isDomainObjectIntersection(intersection)) { return undefined; } diff --git a/react-components/src/architecture/concrete/terrainDomainObject/SetTerrainVisibleCommand.ts b/react-components/src/architecture/concrete/terrainDomainObject/SetTerrainVisibleCommand.ts index 60e3a491190..0e164f4e97f 100644 --- a/react-components/src/architecture/concrete/terrainDomainObject/SetTerrainVisibleCommand.ts +++ b/react-components/src/architecture/concrete/terrainDomainObject/SetTerrainVisibleCommand.ts @@ -24,9 +24,7 @@ export class SetTerrainVisibleCommand extends RenderTargetCommand { } protected override invokeCore(): boolean { - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; - + const { renderTarget, rootDomainObject } = this; let terrainDomainObject = rootDomainObject.getDescendantByTypeAndName( TerrainDomainObject, DEFAULT_TERRAIN_NAME diff --git a/react-components/src/architecture/concrete/terrainDomainObject/TerrainThreeView.ts b/react-components/src/architecture/concrete/terrainDomainObject/TerrainThreeView.ts index f51d16972c9..7901c9a3753 100644 --- a/react-components/src/architecture/concrete/terrainDomainObject/TerrainThreeView.ts +++ b/react-components/src/architecture/concrete/terrainDomainObject/TerrainThreeView.ts @@ -40,7 +40,7 @@ export class TerrainThreeView extends GroupThreeView { // INSTANCE PROPERTIES // ================================================== - private get terrainDomainObject(): TerrainDomainObject { + protected override get domainObject(): TerrainDomainObject { return super.domainObject as TerrainDomainObject; } @@ -73,7 +73,7 @@ export class TerrainThreeView extends GroupThreeView { this.addChild(this.createSolid()); this.invalidateRenderTarget(); } else if (solid !== undefined) { - updateSolidMaterial(solid.material as MeshPhongMaterial, this.terrainDomainObject, style); + updateSolidMaterial(solid.material as MeshPhongMaterial, this.domainObject, style); this.invalidateRenderTarget(); } } @@ -86,11 +86,7 @@ export class TerrainThreeView extends GroupThreeView { this.addChild(this.createContours()); this.invalidateRenderTarget(); } else if (contours !== undefined) { - updateContoursMaterial( - contours.material as LineBasicMaterial, - this.terrainDomainObject, - style - ); + updateContoursMaterial(contours.material as LineBasicMaterial, this.domainObject, style); this.invalidateRenderTarget(); } } @@ -102,8 +98,7 @@ export class TerrainThreeView extends GroupThreeView { // ================================================== protected override calculateBoundingBox(): Box3 { - const { terrainDomainObject } = this; - const { grid } = terrainDomainObject; + const { grid } = this.domainObject; if (grid === undefined) { return new Box3().makeEmpty(); } @@ -125,8 +120,7 @@ export class TerrainThreeView extends GroupThreeView { // ================================================== protected override addChildren(): void { - const { terrainDomainObject } = this; - const { grid } = terrainDomainObject; + const { grid } = this.domainObject; if (grid === undefined) { return undefined; } @@ -143,8 +137,8 @@ export class TerrainThreeView extends GroupThreeView { if (!style.showSolid) { return undefined; } - const { terrainDomainObject } = this; - const { grid } = terrainDomainObject; + const { domainObject } = this; + const { grid } = domainObject; if (grid === undefined) { return undefined; } @@ -152,7 +146,7 @@ export class TerrainThreeView extends GroupThreeView { const geometry = buffers.createBufferGeometry(); const material = new MeshPhongMaterial(); - updateSolidMaterial(material, terrainDomainObject, style); + updateSolidMaterial(material, domainObject, style); const result = new Mesh(geometry, material); result.name = SOLID_NAME; @@ -165,8 +159,8 @@ export class TerrainThreeView extends GroupThreeView { if (!style.showContours) { return undefined; } - const { terrainDomainObject } = this; - const { grid } = terrainDomainObject; + const { domainObject } = this; + const { grid } = domainObject; if (grid === undefined) { return undefined; } @@ -179,7 +173,7 @@ export class TerrainThreeView extends GroupThreeView { geometry.setAttribute('position', new Float32BufferAttribute(contoursBuffer, 3)); const material = new LineBasicMaterial(); - updateContoursMaterial(material, terrainDomainObject, style); + updateContoursMaterial(material, domainObject, style); const result = new LineSegments(geometry, material); result.name = CONTOURS_NAME; diff --git a/react-components/src/architecture/concrete/terrainDomainObject/UpdateTerrainCommand.ts b/react-components/src/architecture/concrete/terrainDomainObject/UpdateTerrainCommand.ts index d23d5eb55a3..1358fb736ef 100644 --- a/react-components/src/architecture/concrete/terrainDomainObject/UpdateTerrainCommand.ts +++ b/react-components/src/architecture/concrete/terrainDomainObject/UpdateTerrainCommand.ts @@ -25,8 +25,7 @@ export class UpdateTerrainCommand extends RenderTargetCommand { } public override get isEnabled(): boolean { - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; + const { renderTarget, rootDomainObject } = this; const terrainDomainObject = rootDomainObject.getDescendantByTypeAndName( TerrainDomainObject, DEFAULT_TERRAIN_NAME @@ -38,9 +37,7 @@ export class UpdateTerrainCommand extends RenderTargetCommand { } protected override invokeCore(): boolean { - const { renderTarget } = this; - const { rootDomainObject } = renderTarget; - + const { renderTarget, rootDomainObject } = this; const terrainDomainObject = rootDomainObject.getDescendantByTypeAndName( TerrainDomainObject, DEFAULT_TERRAIN_NAME From e0bed2197bccf07519b5dfcac6c7f8cfa2477d6f Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sun, 26 May 2024 08:46:05 +0200 Subject: [PATCH 07/27] Make it better --- .../base/concreteCommands/FitViewCommand.ts | 2 +- .../base/domainObjectsHelpers/BaseCreator.ts | 16 ++++--- .../base/renderTarget/RevealRenderTarget.ts | 39 ++++++++++++++- .../base/utilities/box/BoxFace.ts | 40 ++++++++++++++-- .../base/utilities/geometry/Range3.ts | 16 ++----- .../concrete/axis/AxisThreeView.ts | 48 +++++++++++++++++-- .../boxDomainObject/MeasureBoxDomainObject.ts | 16 ++++++- .../boxDomainObject/MeasureBoxView.ts | 19 +------- .../boxDomainObject/MeasurementTool.ts | 16 ++++--- 9 files changed, 159 insertions(+), 53 deletions(-) diff --git a/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts b/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts index 9d830a965e2..ef8b25bad69 100644 --- a/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts +++ b/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts @@ -18,7 +18,7 @@ export class FitViewCommand extends RenderTargetCommand { const { renderTarget } = this; const { viewer } = renderTarget; - const boundingBox = viewer.getSceneBoundingBox(); + const boundingBox = renderTarget.sceneBoundingBox; if (boundingBox.isEmpty()) { return false; } diff --git a/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts b/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts index 2ff79c462d2..1cdb82ae887 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts @@ -2,9 +2,9 @@ * Copyright 2024 Cognite AS */ -import { type Ray, type Vector3 } from 'three'; +import { type Vector3, type Ray } from 'three'; import { replaceLast } from '../utilities/extensions/arrayExtensions'; -import { CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; +import { type AnyIntersection, CDF_TO_VIEWER_TRANSFORMATION } from '@cognite/reveal'; import { type DomainObject } from '../domainObjects/DomainObject'; /** @@ -98,13 +98,15 @@ export abstract class BaseCreator { // ================================================== public get isFinished(): boolean { - return this.notPendingPointCount === this.maximumPointCount; + return this.notPendingPointCount >= this.maximumPointCount; } - public addPoint(ray: Ray, point: Vector3 | undefined, isPending: boolean): boolean { - if (point !== undefined) { - point = point.clone(); - } + public addPoint( + ray: Ray, + intersection: AnyIntersection | undefined, + isPending: boolean = false + ): boolean { + const point = intersection?.point.clone(); this.convertToCdfCoords(ray, point); return this.addPointCore(ray, point, isPending); } diff --git a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts index dc1678b1104..222f49dc533 100644 --- a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts +++ b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts @@ -17,7 +17,8 @@ import { DirectionalLight, type PerspectiveCamera, type Box3, - type WebGLRenderer + type WebGLRenderer, + type Plane } from 'three'; import { ToolControllers } from './ToolController'; import { RootDomainObject } from '../domainObjects/RootDomainObject'; @@ -25,6 +26,7 @@ import { getOctDir } from '../utilities/extensions/vectorExtensions'; import { getResizeCursor } from '../utilities/geometry/getResizeCursor'; import { VisualDomainObject } from '../domainObjects/VisualDomainObject'; import { ThreeView } from '../views/ThreeView'; +import { type DomainObject } from '../domainObjects/DomainObject'; const DIRECTIONAL_LIGHT_NAME = 'DirectionalLight'; @@ -39,6 +41,8 @@ export class RevealRenderTarget { private _axisGizmoTool: AxisGizmoTool | undefined; private _ambientLight: AmbientLight | undefined; private _directionalLight: DirectionalLight | undefined; + private _cropBoxBoundingBox: Box3 | undefined; + private _cropBoxName: string | undefined = undefined; // ================================================== // CONTRUCTORS @@ -109,7 +113,11 @@ export class RevealRenderTarget { } public get sceneBoundingBox(): Box3 { - return this.viewer.getSceneBoundingBox(); + const boundingBox = this.viewer.getSceneBoundingBox(); + if (this._cropBoxBoundingBox !== undefined) { + boundingBox.intersect(this._cropBoxBoundingBox); + } + return boundingBox; } // ================================================== @@ -192,6 +200,33 @@ export class RevealRenderTarget { } }; + // ================================================== + // INSTANCE METHODS: Crpp box + // ================================================== + + public setGlobalCropBox(clippingPlanes: Plane[], boundingBox: Box3, name: string): void { + // Input in Viewer coordinates + this.viewer.setGlobalClippingPlanes(clippingPlanes); + this._cropBoxBoundingBox = boundingBox; + this._cropBoxName = name; + } + + public clearGlobalCropBox(): void { + this.viewer.setGlobalClippingPlanes([]); + this._cropBoxBoundingBox = undefined; + this._cropBoxName = undefined; + } + + public isGlobalCropBox(domainObject: DomainObject): boolean { + return this._cropBoxName !== undefined && domainObject.hasEqualName(this._cropBoxName); + } + + public get isGlobalCropBoxActive(): boolean { + return ( + this.viewer.getGlobalClippingPlanes().length > 0 && this._cropBoxBoundingBox !== undefined + ); + } + // ================================================== // INSTANCE METHODS: Cursor // See: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor diff --git a/react-components/src/architecture/base/utilities/box/BoxFace.ts b/react-components/src/architecture/base/utilities/box/BoxFace.ts index a4a84d7b212..44f2139947a 100644 --- a/react-components/src/architecture/base/utilities/box/BoxFace.ts +++ b/react-components/src/architecture/base/utilities/box/BoxFace.ts @@ -2,7 +2,7 @@ * Copyright 2024 Cognite AS */ -import { Vector2, Vector3 } from 'three'; +import { type Matrix4, Plane, Vector2, Vector3 } from 'three'; /** * Represents a face of a box. @@ -11,10 +11,18 @@ export class BoxFace { // Face is 0-5, where 0-2 are positive faces and 3-5 are negative faces private _face: number = 0; + // ================================================== + // CONTRUCTOR + // ================================================== + public constructor(face: number = 0) { this._face = face; } + // ================================================== + // INSTANCE PROPERTIES + // ================================================== + public get face(): number { return this._face; } @@ -39,12 +47,16 @@ export class BoxFace { return this._face < 3 ? 1 : -1; } - copy(other: BoxFace): this { + // ================================================== + // INSTANCE METHODS + // ================================================== + + public copy(other: BoxFace): this { this._face = other._face; return this; } - equals(other: BoxFace): boolean { + public equals(other: BoxFace): boolean { return this.face === other.face; } @@ -114,6 +126,10 @@ export class BoxFace { return target.setComponent(this.index, this.sign * 0.5); } + // ================================================== + // STATIC METHODS + // ================================================== + public static *getAllFaces(target?: BoxFace): Generator { if (target === undefined) { target = new BoxFace(); @@ -123,6 +139,24 @@ export class BoxFace { } } + public static createClippingPlanes(matrix: Matrix4, exceptFaceIndex?: number): Plane[] { + const planes: Plane[] = []; + + const center = new Vector3(); + const normal = new Vector3(); + for (const boxFace of BoxFace.getAllFaces()) { + if (exceptFaceIndex !== undefined && boxFace.index === exceptFaceIndex) { + continue; + } + boxFace.getCenter(center); + boxFace.getNormal(normal).negate(); + const plane = new Plane().setFromNormalAndCoplanarPoint(normal, center); + plane.applyMatrix4(matrix); + planes.push(plane); + } + return planes; + } + public static equals(face: BoxFace | undefined, other: BoxFace | undefined): boolean { if (face === undefined || other === undefined) { return true; diff --git a/react-components/src/architecture/base/utilities/geometry/Range3.ts b/react-components/src/architecture/base/utilities/geometry/Range3.ts index b5d5dbadb43..c31b53674cb 100644 --- a/react-components/src/architecture/base/utilities/geometry/Range3.ts +++ b/react-components/src/architecture/base/utilities/geometry/Range3.ts @@ -224,19 +224,11 @@ export class Range3 { // STATIC METHODS // ================================================== - public static createByMinAndMax(xmin: number, ymin: number, xmax: number, ymax: number): Range3 { + public static createCube(halfSize: number): Range3 { const range = new Range3(); - range.x.set(xmin, xmax); - range.y.set(ymin, ymax); - range.z.set(0, 0); - return range; - } - - public static createByMinAndDelta(xmin: number, ymin: number, dx: number, dy: number): Range3 { - const range = new Range3(); - range.x.set(xmin, xmin + dx); - range.y.set(ymin, ymin + dy); - range.z.set(0, 0); + range.x.set(-halfSize, halfSize); + range.y.set(-halfSize, halfSize); + range.z.set(-halfSize, halfSize); return range; } } diff --git a/react-components/src/architecture/concrete/axis/AxisThreeView.ts b/react-components/src/architecture/concrete/axis/AxisThreeView.ts index 4cbd7396196..dcb5c129123 100644 --- a/react-components/src/architecture/concrete/axis/AxisThreeView.ts +++ b/react-components/src/architecture/concrete/axis/AxisThreeView.ts @@ -273,9 +273,9 @@ export class AxisThreeView extends GroupThreeView { } labelCount += 1; end.add(vector); - // Add sprite - const sprite = createSpriteWithText(`${tick}`, tickFontSize, style.textColor); + const text = incrementToString(tick); + const sprite = createSpriteWithText(text, tickFontSize, style.textColor); if (sprite !== undefined) { moveSpriteByPositionAndDirection(sprite, end, tickDirection); this.addChild(sprite); @@ -532,7 +532,7 @@ function createLineSegments(vertices: number[], color: Color, linewidth: number) } // ================================================== -// PRIVATE METHODS: Some math for Range3 +// PRIVATE METHODS: Some math // ================================================== // Corner and faces is pr. definition: @@ -603,6 +603,48 @@ function getTickDirection(faceIndex1: number, faceIndex2: number, target: Vector return target; } +function incrementToString(value: number): string { + // Sometimes the number comes out like this: 1.20000005 or 1.19999992 due to numeric precision limitations. + // To get better rounded values, I wrote this myself: Multiply by some high integer and round it, then + // convert to text, and insert the comma manually afterwards. + + // Small number get less accurate result in tjhis algorithm,, so use the default string conversion. + if (Math.abs(value) < 0.001) { + return `${value}`; + } + const sign = Math.sign(value); + const rounded = Math.abs(Math.round(value * 1e5)); + let text = `${rounded}`; + if (text.length === 1) { + text = `${'0.0000'}${text}`; + } else if (text.length === 2) { + text = `${'0.000'}${text}`; + } else if (text.length === 3) { + text = `${'0.00'}${text}`; + } else if (text.length === 4) { + text = `${'0.0'}${text}`; + } else if (text.length === 5) { + text = `${'0.'}${text}`; + } else if (text.length >= 6) { + const i = text.length - 5; + text = `${text.slice(0, i)}${'.'}${text.slice(i)}`; + } + // Since we know that the comma are there, + // we can safely remove trailing zeros + while (text[text.length - 1] === '0') { + text = text.slice(0, -1); + } + // Remove if last is a comma + if (text[text.length - 1] === '.') { + text = text.slice(0, -1); + } + // Put the negative sign in the front + if (sign < 0) { + text = `${'-'}${text}`; + } + return text; +} + // ================================================== // PRIVATE FUNCTIONS: Vector pool // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts index 399fd6baa31..a815f401fe6 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts @@ -6,7 +6,7 @@ import { MeasureBoxRenderStyle } from './MeasureBoxRenderStyle'; import { type RenderStyle } from '../../base/domainObjectsHelpers/RenderStyle'; import { type ThreeView } from '../../base/views/ThreeView'; import { MeasureBoxView } from './MeasureBoxView'; -import { Matrix4, Vector3 } from 'three'; +import { Box3, Matrix4, Vector3 } from 'three'; import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { BoxFace } from '../../base/utilities/box/BoxFace'; import { FocusType } from '../../base/domainObjectsHelpers/FocusType'; @@ -18,6 +18,7 @@ import { MeasureDomainObject } from './MeasureDomainObject'; import { NumberType, PanelInfo } from '../../base/domainObjectsHelpers/PanelInfo'; import { radToDeg } from 'three/src/math/MathUtils.js'; import { type CreateDraggerProps } from '../../base/domainObjects/VisualDomainObject'; +import { Range3 } from '../../base/utilities/geometry/Range3'; export const MIN_BOX_SIZE = 0.01; @@ -173,6 +174,19 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { size.z = Math.max(MIN_BOX_SIZE, size.z); } + public getBoundingBox(): Box3 { + const matrix = this.getMatrix(); + const boundingBox = new Box3().makeEmpty(); + const unitCube = Range3.createCube(0.5); + const corner = new Vector3(); + for (let i = 0; i < 8; i++) { + unitCube.getCornerPoint(i, corner); + corner.applyMatrix4(matrix); + boundingBox.expandByPoint(corner); + } + return boundingBox; + } + public getRotationMatrix(matrix: Matrix4 = new Matrix4()): Matrix4 { matrix.makeRotationZ(this.zRotation); return matrix; diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts index 146f662c4cc..5bfe2dbde29 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts @@ -18,7 +18,6 @@ import { type Camera, CircleGeometry, type Material, - Plane, FrontSide, type PerspectiveCamera } from 'three'; @@ -281,7 +280,7 @@ export class MeasureBoxView extends GroupThreeView { const material = new MeshPhongMaterial(); updateMarkerMaterial(material, domainObject, style, focusType === FocusType.Rotation); - material.clippingPlanes = this.createClippingPlanes(matrix, TOP_FACE.index); + material.clippingPlanes = BoxFace.createClippingPlanes(matrix, TOP_FACE.index); const mesh = new Mesh(geometry, material); const center = TOP_FACE.getCenter(newVector3()); @@ -328,22 +327,6 @@ export class MeasureBoxView extends GroupThreeView { return mesh; } - private createClippingPlanes(matrix: Matrix4, faceIndex: number): Plane[] { - const planes: Plane[] = []; - - for (const boxFace of BoxFace.getAllFaces()) { - if (boxFace.index === faceIndex) { - continue; - } - const center = boxFace.getCenter(newVector3()); - const normal = boxFace.getNormal(newVector3()).negate(); - const plane = new Plane().setFromNormalAndCoplanarPoint(normal, center); - plane.applyMatrix4(matrix); - planes.push(plane); - } - return planes; - } - // ================================================== // INSTANCE METHODS: Add Object3D's // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index b80ade1ed87..4b66b8d2bee 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -22,6 +22,7 @@ import { ShowMeasurmentsOnTopCommand } from './ShowMeasurmentsOnTopCommand'; import { SetMeasurmentTypeCommand } from './SetMeasurmentTypeCommand'; import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; import { type RootDomainObject } from '../../base/domainObjects/RootDomainObject'; +import { SetCropBoxCommand } from './SetCropBoxCommand'; export class MeasurementTool extends BaseEditTool { // ================================================== @@ -53,6 +54,7 @@ export class MeasurementTool extends BaseEditTool { result.push(new SetMeasurmentTypeCommand(MeasureType.Volume)); result.push(undefined); // Means separator result.push(new ShowMeasurmentsOnTopCommand()); + result.push(new SetCropBoxCommand()); return result; } @@ -126,7 +128,7 @@ export class MeasurementTool extends BaseEditTool { return; } const ray = this.getRay(event); - if (creator.addPoint(ray, intersection.point, true)) { + if (creator.addPoint(ray, intersection, true)) { this.setDefaultCursor(); return; } @@ -169,10 +171,11 @@ export class MeasurementTool extends BaseEditTool { // Click in the "air" if (creator !== undefined && !creator.preferIntersection) { const ray = this.getRay(event); - if (creator.addPoint(ray, undefined, false)) { + if (creator.addPoint(ray, undefined)) { if (creator.isFinished) { this._creator = undefined; } + this.renderTarget.toolController.update(); return; } } @@ -184,10 +187,9 @@ export class MeasurementTool extends BaseEditTool { } const measurment = this.getMeasurement(intersection); if (measurment !== undefined) { - // Click at "a measurement" - // Do not want to click on other measurments this.deselectAll(measurment); measurment.setSelectedInteractive(true); + this.renderTarget.toolController.update(); return; } const ray = this.getRay(event); @@ -197,7 +199,7 @@ export class MeasurementTool extends BaseEditTool { await super.onClick(event); return; } - if (creator.addPoint(ray, intersection.point, false)) { + if (creator.addPoint(ray, intersection)) { const { domainObject } = creator; initializeStyle(domainObject, this.rootDomainObject); this.deselectAll(); @@ -207,7 +209,7 @@ export class MeasurementTool extends BaseEditTool { this.renderTarget.toolController.update(); } } else { - if (creator.addPoint(ray, intersection.point, false)) { + if (creator.addPoint(ray, intersection)) { if (creator.isFinished) { this._creator = undefined; } @@ -311,6 +313,7 @@ export class MeasurementTool extends BaseEditTool { // ================================================== function initializeStyle(domainObject: DomainObject, rootDomainObject: RootDomainObject): void { + // Just copy the style the depthTest field from any other MeasureDomainObject const otherDomainObject = rootDomainObject.getDescendantByType(MeasureDomainObject); if (otherDomainObject === undefined) { return; @@ -329,6 +332,7 @@ function createCreator(measureType: MeasureType): BaseCreator | undefined { case MeasureType.Polyline: case MeasureType.Polygon: return new MeasureLineCreator(measureType); + case MeasureType.HorizontalArea: case MeasureType.VerticalArea: case MeasureType.Volume: From 39f5ae6383c2e718cccb09b75ff2bf8f39a8ca74 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sun, 26 May 2024 08:49:04 +0200 Subject: [PATCH 08/27] Update MeasurementTool.ts --- .../src/architecture/concrete/boxDomainObject/MeasurementTool.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index 4b66b8d2bee..f8c14f5eb9b 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -54,7 +54,6 @@ export class MeasurementTool extends BaseEditTool { result.push(new SetMeasurmentTypeCommand(MeasureType.Volume)); result.push(undefined); // Means separator result.push(new ShowMeasurmentsOnTopCommand()); - result.push(new SetCropBoxCommand()); return result; } From 89243a8b2fbb99d433ae1c5e7d3f750e01550d7a Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sun, 26 May 2024 09:36:48 +0200 Subject: [PATCH 09/27] Fix lint --- .../src/architecture/concrete/boxDomainObject/MeasurementTool.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index f8c14f5eb9b..36e32d5640c 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -22,7 +22,6 @@ import { ShowMeasurmentsOnTopCommand } from './ShowMeasurmentsOnTopCommand'; import { SetMeasurmentTypeCommand } from './SetMeasurmentTypeCommand'; import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; import { type RootDomainObject } from '../../base/domainObjects/RootDomainObject'; -import { SetCropBoxCommand } from './SetCropBoxCommand'; export class MeasurementTool extends BaseEditTool { // ================================================== From 1f05ec0edc567bdd8389f57901fe6416c0a0c0ca Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sun, 26 May 2024 18:11:00 +0200 Subject: [PATCH 10/27] Make better axis tick labels, but still not perfect --- .../concrete/axis/AxisThreeView.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/react-components/src/architecture/concrete/axis/AxisThreeView.ts b/react-components/src/architecture/concrete/axis/AxisThreeView.ts index dcb5c129123..48cc4dd4a46 100644 --- a/react-components/src/architecture/concrete/axis/AxisThreeView.ts +++ b/react-components/src/architecture/concrete/axis/AxisThreeView.ts @@ -245,6 +245,7 @@ export class AxisThreeView extends GroupThreeView { // Draw ticks const labelInc = range.getBoldIncrement(increment); const tickDirection = getTickDirection(faceIndex1, faceIndex2, new Vector3()); + tickDirection.normalize(); // Add tick marks and labels if (style.showAxisTicks || style.showAxisNumbers) { @@ -253,14 +254,11 @@ export class AxisThreeView extends GroupThreeView { for (const tick of range.getTicks(increment)) { const start = newVector3(this._corners[i0]); start.setComponent(dimension, tick); - const end = newVector3(start); - const vector = newVector3(tickDirection); - vector.multiplyScalar(tickLength); // Add tick mark if (style.showAxisTicks) { - end.add(vector); + end.addScaledVector(tickDirection, tickLength); vertices.push(...start); vertices.push(...end); } @@ -272,15 +270,15 @@ export class AxisThreeView extends GroupThreeView { minLabelTick = tick; } labelCount += 1; - end.add(vector); - // Add sprite + end.addScaledVector(tickDirection, 2 * tickLength); const text = incrementToString(tick); const sprite = createSpriteWithText(text, tickFontSize, style.textColor); - if (sprite !== undefined) { - moveSpriteByPositionAndDirection(sprite, end, tickDirection); - this.addChild(sprite); - this.setUserDataOnAxis(sprite, faceIndex1, faceIndex2, true); + if (sprite === undefined) { + continue; } + sprite.position.copy(end); + this.addChild(sprite); + this.setUserDataOnAxis(sprite, faceIndex1, faceIndex2, true); } } if (style.showAxisTicks) { From 3f8ed413b0b44b0eb852133e389a6529ac529062 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sun, 26 May 2024 18:39:18 +0200 Subject: [PATCH 11/27] handleEscape better --- .../base/domainObjectsHelpers/BaseCreator.ts | 9 ++++++++- .../concrete/boxDomainObject/MeasureBoxCreator.ts | 8 +++++--- .../concrete/boxDomainObject/MeasureLineCreator.ts | 4 +++- .../concrete/boxDomainObject/SetMeasurmentTypeCommand.ts | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts b/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts index 1cdb82ae887..0735a332859 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/BaseCreator.ts @@ -91,7 +91,14 @@ export abstract class BaseCreator { isPending: boolean ): boolean; - public handleEscape(): void {} + /** + * Handles the escape key press event. + * + * @returns {boolean} Returns true if the pending object is created successfully, false if it is removed + */ + public handleEscape(): boolean { + return false; + } // ================================================== // INSTANCE METHODS diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts index c826c8664c7..001ec405c29 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxCreator.ts @@ -85,10 +85,12 @@ export class MeasureBoxCreator extends BaseCreator { return true; } - public override handleEscape(): void { - if (this.notPendingPointCount < this.minimumPointCount) { - this._domainObject.removeInteractive(); + public override handleEscape(): boolean { + if (this.notPendingPointCount >= this.minimumPointCount) { + return true; // Successfully } + this._domainObject.removeInteractive(); + return false; // Removed } // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts index 9224feaa6a2..0504dbe3209 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts @@ -89,14 +89,16 @@ export class MeasureLineCreator extends BaseCreator { return true; } - public override handleEscape(): void { + public override handleEscape(): boolean { const domainObject = this._domainObject; if (this.notPendingPointCount < this.minimumPointCount) { domainObject.removeInteractive(); + return false; // Removed } else if (this.lastIsPending) { domainObject.points.pop(); this.removePendingPoint(); domainObject.notify(Changes.geometry); + return true; // Successfully } } } diff --git a/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts b/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts index 1d1ddcfd074..51b3017b80f 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/SetMeasurmentTypeCommand.ts @@ -53,13 +53,13 @@ export class SetMeasurmentTypeCommand extends RenderTargetCommand { if (measurementTool === undefined) { return false; } + measurementTool.handleEscape(); + measurementTool.clearDragging(); if (measurementTool.measureType === this._measureType) { measurementTool.measureType = MeasureType.None; } else { measurementTool.measureType = this._measureType; } - measurementTool.handleEscape(); - measurementTool.clearDragging(); return true; } From bc6145937be43e46073594a2b988bc79691329cb Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sun, 26 May 2024 18:47:28 +0200 Subject: [PATCH 12/27] Changes in RevealRenderTarget --- .../base/concreteCommands/FitViewCommand.ts | 9 +------ .../base/domainObjects/RootDomainObject.ts | 11 +++++++- .../base/renderTarget/RevealRenderTarget.ts | 25 ++++++++++++++++--- .../boxDomainObject/MeasurementTool.ts | 11 ++++++-- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts b/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts index ef8b25bad69..a3b1cfa64cb 100644 --- a/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts +++ b/react-components/src/architecture/base/concreteCommands/FitViewCommand.ts @@ -16,13 +16,6 @@ export class FitViewCommand extends RenderTargetCommand { protected override invokeCore(): boolean { const { renderTarget } = this; - const { viewer } = renderTarget; - - const boundingBox = renderTarget.sceneBoundingBox; - if (boundingBox.isEmpty()) { - return false; - } - viewer.fitCameraToBoundingBox(boundingBox); - return true; + return renderTarget.fitView(); } } diff --git a/react-components/src/architecture/base/domainObjects/RootDomainObject.ts b/react-components/src/architecture/base/domainObjects/RootDomainObject.ts index 2ca67d20e3f..ccb362b30a5 100644 --- a/react-components/src/architecture/base/domainObjects/RootDomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/RootDomainObject.ts @@ -2,16 +2,25 @@ * Copyright 2024 Cognite AS */ +import { type RevealRenderTarget } from '../renderTarget/RevealRenderTarget'; import { DomainObject } from './DomainObject'; export class RootDomainObject extends DomainObject { + private readonly _renderTarget: RevealRenderTarget; + + public get renderTarget(): RevealRenderTarget { + return this._renderTarget; + } + // ================================================== // CONSTRUCTOR // ================================================== - public constructor() { + public constructor(renderTarget: RevealRenderTarget) { super(); this.name = 'Root'; + this._renderTarget = renderTarget; + console.log(this._renderTarget); } // ================================================== diff --git a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts index 222f49dc533..58f0cf9aea7 100644 --- a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts +++ b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts @@ -57,7 +57,7 @@ export class RevealRenderTarget { } this._toolController = new ToolControllers(this.domElement); this._toolController.addEventListeners(); - this._rootDomainObject = new RootDomainObject(); + this._rootDomainObject = new RootDomainObject(this); this.initializeLights(); this._viewer.on('cameraChange', this.cameraChangeHandler); @@ -201,14 +201,31 @@ export class RevealRenderTarget { }; // ================================================== - // INSTANCE METHODS: Crpp box + // INSTANCE METHODS: Fit operations // ================================================== - public setGlobalCropBox(clippingPlanes: Plane[], boundingBox: Box3, name: string): void { + public fitView(): boolean { + const boundingBox = this.sceneBoundingBox; + if (boundingBox.isEmpty()) { + return false; + } + this.viewer.fitCameraToBoundingBox(this.sceneBoundingBox); + return true; + } + + // ================================================== + // INSTANCE METHODS: Crop box operations (Experimental code) + // ================================================== + + public setGlobalCropBox( + clippingPlanes: Plane[], + boundingBox: Box3, + domainObject: DomainObject + ): void { // Input in Viewer coordinates this.viewer.setGlobalClippingPlanes(clippingPlanes); this._cropBoxBoundingBox = boundingBox; - this._cropBoxName = name; + this._cropBoxName = domainObject.name; } public clearGlobalCropBox(): void { diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index 36e32d5640c..0bfe823b63d 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -172,8 +172,9 @@ export class MeasurementTool extends BaseEditTool { if (creator.addPoint(ray, undefined)) { if (creator.isFinished) { this._creator = undefined; + this.measureType = MeasureType.None; + this.renderTarget.toolController.update(); } - this.renderTarget.toolController.update(); return; } } @@ -209,6 +210,8 @@ export class MeasurementTool extends BaseEditTool { } else { if (creator.addPoint(ray, intersection)) { if (creator.isFinished) { + this.measureType = MeasureType.None; + this.renderTarget.toolController.update(); this._creator = undefined; } } @@ -230,7 +233,11 @@ export class MeasurementTool extends BaseEditTool { if (this._creator === undefined) { return; } - this._creator.handleEscape(); + if (this._creator.handleEscape()) { + // Sucessfully created, set it back to none + this.measureType = MeasureType.None; + this.renderTarget.toolController.update(); + } this._creator = undefined; } From 7d0b716ef167c3773b67ad071081f4e4b4beec15 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Sun, 26 May 2024 18:49:57 +0200 Subject: [PATCH 13/27] Update RootDomainObject.ts --- .../src/architecture/base/domainObjects/RootDomainObject.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/react-components/src/architecture/base/domainObjects/RootDomainObject.ts b/react-components/src/architecture/base/domainObjects/RootDomainObject.ts index ccb362b30a5..14972f804da 100644 --- a/react-components/src/architecture/base/domainObjects/RootDomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/RootDomainObject.ts @@ -20,7 +20,6 @@ export class RootDomainObject extends DomainObject { super(); this.name = 'Root'; this._renderTarget = renderTarget; - console.log(this._renderTarget); } // ================================================== From 96f9bd1bef9eb0ee7ff99294458988135ab6388a Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Mon, 27 May 2024 07:08:35 +0200 Subject: [PATCH 14/27] Fix smal bug --- .../architecture/concrete/boxDomainObject/MeasureLineCreator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts index 0504dbe3209..2179eec0fab 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineCreator.ts @@ -98,7 +98,7 @@ export class MeasureLineCreator extends BaseCreator { domainObject.points.pop(); this.removePendingPoint(); domainObject.notify(Changes.geometry); - return true; // Successfully } + return true; // Successfully } } From 2a4de0ab08267983fd8b3d23d6867bf70d1b07c1 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Mon, 27 May 2024 07:11:23 +0200 Subject: [PATCH 15/27] Moving code --- .../boxDomainObject/MeasureBoxDomainObject.ts | 114 ++++++++++-------- .../boxDomainObject/MeasureBoxView.ts | 11 +- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts index a815f401fe6..49a70857e83 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDomainObject.ts @@ -39,49 +39,6 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { // INSTANCE PROPERTIES // ================================================== - public get diagonal(): number { - return this.size.length(); - } - - public get hasArea(): boolean { - let count = 0; - if (isValid(this.size.x)) count++; - if (isValid(this.size.y)) count++; - if (isValid(this.size.z)) count++; - return count >= 2; - } - - public get area(): number { - switch (this.measureType) { - case MeasureType.HorizontalArea: - return this.size.x * this.size.y; - case MeasureType.VerticalArea: - return this.size.x * this.size.z; - case MeasureType.Volume: { - const a = this.size.x * this.size.y + this.size.y * this.size.z + this.size.z * this.size.x; - return a * 2; - } - default: - throw new Error('Unknown MeasureType type'); - } - } - - public get hasHorizontalArea(): boolean { - return isValid(this.size.x) && isValid(this.size.y); - } - - public get horizontalArea(): number { - return this.size.x * this.size.y; - } - - public get hasVolume(): boolean { - return isValid(this.size.x) && isValid(this.size.y) && isValid(this.size.z); - } - - public get volume(): number { - return this.size.x * this.size.y * this.size.z; - } - public override get renderStyle(): MeasureBoxRenderStyle { return this.getRenderStyle() as MeasureBoxRenderStyle; } @@ -126,13 +83,13 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { info.setHeader('MEASUREMENTS_VOLUME', 'Volume'); break; } - if (isFinished || isValid(this.size.x)) { + if (isFinished || isValidSize(this.size.x)) { add('MEASUREMENTS_LENGTH', 'Length', this.size.x, NumberType.Length); } - if (measureType !== MeasureType.VerticalArea && (isFinished || isValid(this.size.y))) { + if (measureType !== MeasureType.VerticalArea && (isFinished || isValidSize(this.size.y))) { add('MEASUREMENTS_DEPTH', 'Depth', this.size.y, NumberType.Length); } - if (measureType !== MeasureType.HorizontalArea && (isFinished || isValid(this.size.z))) { + if (measureType !== MeasureType.HorizontalArea && (isFinished || isValidSize(this.size.z))) { add('MEASUREMENTS_HEIGHT', 'Height', this.size.z, NumberType.Length); } if (measureType !== MeasureType.Volume && (isFinished || this.hasArea)) { @@ -164,14 +121,50 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { } // ================================================== - // INSTANCE METHODS + // INSTANCE METHODS: Getters/Properties // ================================================== - public forceMinSize(): void { - const { size } = this; - size.x = Math.max(MIN_BOX_SIZE, size.x); - size.y = Math.max(MIN_BOX_SIZE, size.y); - size.z = Math.max(MIN_BOX_SIZE, size.z); + public get diagonal(): number { + return this.size.length(); + } + + public get hasArea(): boolean { + let count = 0; + if (isValidSize(this.size.x)) count++; + if (isValidSize(this.size.y)) count++; + if (isValidSize(this.size.z)) count++; + return count >= 2; + } + + public get area(): number { + switch (this.measureType) { + case MeasureType.HorizontalArea: + return this.size.x * this.size.y; + case MeasureType.VerticalArea: + return this.size.x * this.size.z; + case MeasureType.Volume: { + const a = this.size.x * this.size.y + this.size.y * this.size.z + this.size.z * this.size.x; + return a * 2; + } + default: + throw new Error('Unknown MeasureType type'); + } + } + + public get hasHorizontalArea(): boolean { + return isValidSize(this.size.x) && isValidSize(this.size.y); + } + + public get horizontalArea(): number { + return this.size.x * this.size.y; + } + + public get hasVolume(): boolean { + return isValidSize(this.size.x) && isValidSize(this.size.y) && isValidSize(this.size.z); + } + + public get volume(): number { + return this.size.x * this.size.y * this.size.z; } public getBoundingBox(): Box3 { @@ -203,6 +196,17 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { return matrix; } + // ================================================== + // INSTANCE METHODS: Others + // ================================================== + + public forceMinSize(): void { + const { size } = this; + size.x = Math.max(MIN_BOX_SIZE, size.x); + size.y = Math.max(MIN_BOX_SIZE, size.y); + size.z = Math.max(MIN_BOX_SIZE, size.z); + } + public setFocusInteractive(focusType: FocusType, focusFace?: BoxFace): boolean { if (focusType === FocusType.None) { if (this.focusType === FocusType.None) { @@ -222,6 +226,10 @@ export class MeasureBoxDomainObject extends MeasureDomainObject { } } -function isValid(value: number): boolean { +// ================================================== +// PUBLIC FUNCTIONS +// ================================================== + +export function isValidSize(value: number): boolean { return value > MIN_BOX_SIZE; } diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts index 5bfe2dbde29..8ff105bf68f 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxView.ts @@ -21,7 +21,7 @@ import { FrontSide, type PerspectiveCamera } from 'three'; -import { type MeasureBoxDomainObject, MIN_BOX_SIZE } from './MeasureBoxDomainObject'; +import { type MeasureBoxDomainObject, isValidSize } from './MeasureBoxDomainObject'; import { type DomainObjectChange } from '../../base/domainObjectsHelpers/DomainObjectChange'; import { Changes } from '../../base/domainObjectsHelpers/Changes'; import { type MeasureBoxRenderStyle } from './MeasureBoxRenderStyle'; @@ -293,11 +293,11 @@ export class MeasureBoxView extends GroupThreeView { private createEdgeCircle(matrix: Matrix4, material: Material, face: BoxFace): Mesh | undefined { const { domainObject } = this; const adjecentSize1 = domainObject.size.getComponent(face.tangentIndex1); - if (!isValid(adjecentSize1)) { + if (!isValidSize(adjecentSize1)) { return undefined; } const adjecentSize2 = domainObject.size.getComponent(face.tangentIndex2); - if (!isValid(adjecentSize2)) { + if (!isValidSize(adjecentSize2)) { return undefined; } const radius = RELATIVE_RESIZE_RADIUS * this.getFaceRadius(face); @@ -337,7 +337,7 @@ export class MeasureBoxView extends GroupThreeView { clear(this._sprites); for (let index = 0; index < 3; index++) { const size = domainObject.size.getComponent(index); - if (!isValid(size)) { + if (!isValidSize(size)) { this._sprites.push(undefined); continue; } @@ -625,9 +625,6 @@ function adjustLabel( } } -function isValid(value: number): boolean { - return value > MIN_BOX_SIZE; -} // ================================================== // PRIVATE FUNCTIONS: Vector pool // ================================================== From a580f5536dc3810e50504c682fc896867698586b Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Mon, 27 May 2024 10:21:39 +0200 Subject: [PATCH 16/27] Fix typo --- .../src/architecture/concrete/axis/AxisThreeView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/src/architecture/concrete/axis/AxisThreeView.ts b/react-components/src/architecture/concrete/axis/AxisThreeView.ts index 48cc4dd4a46..75a7a7f6a29 100644 --- a/react-components/src/architecture/concrete/axis/AxisThreeView.ts +++ b/react-components/src/architecture/concrete/axis/AxisThreeView.ts @@ -42,7 +42,7 @@ export class AxisThreeView extends GroupThreeView { private readonly _corners: Vector3[]; private readonly _faceCenters: Vector3[]; - private readonly _sceneBoundingBox: Box3 = new Box3().makeEmpty(); // Casehing the bounding box of the scene + private readonly _sceneBoundingBox: Box3 = new Box3().makeEmpty(); // Cashe the bounding box of the scene private readonly _expandedSceneBoundingBox: Range3 = new Range3(); // ================================================== From 75700599633aa507509c84c7b9b01e5673762c4d Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Mon, 27 May 2024 10:26:43 +0200 Subject: [PATCH 17/27] Set new default value --- .../architecture/concrete/boxDomainObject/MeasurementTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index 0bfe823b63d..31b7455036a 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -29,7 +29,7 @@ export class MeasurementTool extends BaseEditTool { // ================================================== private _creator: BaseCreator | undefined = undefined; - public measureType: MeasureType = MeasureType.Line; + public measureType: MeasureType = MeasureType.None; // Default none, let the user decide // ================================================== // OVERRIDES of BaseCommand From 0f91bd08e36c872f324f21b763b5e18b4b6227f8 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Mon, 27 May 2024 16:24:29 +0200 Subject: [PATCH 18/27] Fix typo --- .../src/architecture/concrete/axis/AxisThreeView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/src/architecture/concrete/axis/AxisThreeView.ts b/react-components/src/architecture/concrete/axis/AxisThreeView.ts index 75a7a7f6a29..9f4c1cba658 100644 --- a/react-components/src/architecture/concrete/axis/AxisThreeView.ts +++ b/react-components/src/architecture/concrete/axis/AxisThreeView.ts @@ -42,7 +42,7 @@ export class AxisThreeView extends GroupThreeView { private readonly _corners: Vector3[]; private readonly _faceCenters: Vector3[]; - private readonly _sceneBoundingBox: Box3 = new Box3().makeEmpty(); // Cashe the bounding box of the scene + private readonly _sceneBoundingBox: Box3 = new Box3().makeEmpty(); // Cache the bounding box of the scene private readonly _expandedSceneBoundingBox: Range3 = new Range3(); // ================================================== From ad63029d7cac04ac7ee449a384568ec6ddd127b2 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 12:07:21 +0200 Subject: [PATCH 19/27] Initial commit --- .../architecture/base/commands/BaseTool.ts | 4 +- .../base/domainObjects/DomainObject.ts | 25 +++- .../base/domainObjectsHelpers/BaseDragger.ts | 3 +- .../base/domainObjectsHelpers/PopupStyle.ts | 13 ++ .../base/renderTarget/BaseRevealConfig.ts | 31 ++++ .../base/renderTarget/RevealRenderTarget.ts | 30 ++-- .../boxDomainObject/MeasureLineRenderStyle.ts | 2 +- .../boxDomainObject/MeasurementTool.ts | 24 ++-- .../concrete/config/StoryBookConfig.ts | 46 ++++++ .../ExampleDomainObject.ts | 105 ++++++++++++++ .../exampleDomainObject/ExampleDragger.ts | 49 +++++++ .../exampleDomainObject/ExampleRenderStyle.ts | 23 +++ .../exampleDomainObject/ExampleTool.ts | 133 ++++++++++++++++++ .../exampleDomainObject/ExampleView.ts | 111 +++++++++++++++ .../commands/DeleteAllExamplesCommand.ts | 40 ++++++ .../commands/ResetAllExamplesCommand.ts | 39 +++++ .../commands/ShowAllExamplesCommand.ts | 50 +++++++ .../Architecture/DomainObjectPanel.tsx | 28 ++-- .../components/Architecture/ToolButtons.tsx | 2 + .../{ActiveToolToolbar.tsx => Toolbar.tsx} | 53 +++++-- react-components/src/index.ts | 2 +- .../stories/Architecture.stories.tsx | 51 +++---- .../utilities/RevealStoryContainer.tsx | 3 +- react-components/yarn.lock | 8 ++ 24 files changed, 789 insertions(+), 86 deletions(-) create mode 100644 react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts create mode 100644 react-components/src/architecture/concrete/config/StoryBookConfig.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/commands/DeleteAllExamplesCommand.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts create mode 100644 react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts rename react-components/src/components/Architecture/{ActiveToolToolbar.tsx => Toolbar.tsx} (59%) diff --git a/react-components/src/architecture/base/commands/BaseTool.ts b/react-components/src/architecture/base/commands/BaseTool.ts index af3ca3a4b20..cf09e2cb230 100644 --- a/react-components/src/architecture/base/commands/BaseTool.ts +++ b/react-components/src/architecture/base/commands/BaseTool.ts @@ -55,8 +55,8 @@ export abstract class BaseTool extends RenderTargetCommand { return 'default'; } - public getToolbar(): Array | undefined { - return undefined; // Override this to add extra buttons to a separate toolbar + public getToolbar(): Array { + return []; // Override this to add extra buttons to a separate toolbar } public getToolbarStyle(): PopupStyle { diff --git a/react-components/src/architecture/base/domainObjects/DomainObject.ts b/react-components/src/architecture/base/domainObjects/DomainObject.ts index ec7db29f776..591d554faf5 100644 --- a/react-components/src/architecture/base/domainObjects/DomainObject.ts +++ b/react-components/src/architecture/base/domainObjects/DomainObject.ts @@ -17,6 +17,7 @@ import { BLACK_COLOR, WHITE_COLOR } from '../utilities/colors/colorExtensions'; import { Views } from '../domainObjectsHelpers/Views'; import { type PanelInfo } from '../domainObjectsHelpers/PanelInfo'; import { PopupStyle } from '../domainObjectsHelpers/PopupStyle'; +import { RootDomainObject } from './RootDomainObject'; /** * Represents an abstract base class for domain objects. @@ -242,6 +243,22 @@ export abstract class DomainObject { */ protected notifyCore(change: DomainObjectChange): void { this.views.notify(this, change); + + // This is a little bit dirty, but will be refacored by using onIdle() + if ( + change.isChanged( + Changes.visibleState, + Changes.active, + Changes.active, + Changes.selected, + Changes.childAdded, + Changes.childDeleted + ) + ) { + if (this.root instanceof RootDomainObject) { + this.root.renderTarget.toolController.update(); + } + } } // ================================================== @@ -663,10 +680,12 @@ export abstract class DomainObject { return true; } - public removeInteractive(): void { - // You may call canBeDeleted() before calling this + public removeInteractive(checkCanBeDeleted = true): void { + if (checkCanBeDeleted && !this.canBeRemoved) { + return; + } for (const child of this.children) { - child.removeInteractive(); + child.removeInteractive(false); // If parent can be removed, so the children also } const { parent } = this; this.notify(Changes.deleted); diff --git a/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts b/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts index aee6d3481c9..b9f88196d92 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/BaseDragger.ts @@ -23,6 +23,7 @@ export abstract class BaseDragger { // ================================================== protected constructor(props: CreateDraggerProps) { + // Note: that yje point and the ray comes in CDF coordinates this.point = props.point; this.ray = props.ray; } @@ -38,7 +39,7 @@ export abstract class BaseDragger { } // This must be overriden - // Notte that the ray comes in CDF coordinates + // Note: that the ray comes in CDF coordinates public abstract onPointerDrag(_event: PointerEvent, ray: Ray): boolean; public onPointerUp(_event: PointerEvent): void { diff --git a/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts b/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts index 2da9a0998d4..2f3a2fcdb73 100644 --- a/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts +++ b/react-components/src/architecture/base/domainObjectsHelpers/PopupStyle.ts @@ -4,6 +4,7 @@ */ type PopupProps = { + horizontal?: boolean; left?: number; right?: number; top?: number; @@ -13,6 +14,7 @@ type PopupProps = { }; export class PopupStyle { + private readonly _horizontal: boolean = true; private readonly _left?: number = undefined; private readonly _right?: number = undefined; private readonly _top?: number = undefined; @@ -21,6 +23,9 @@ export class PopupStyle { private readonly _padding: number = 16; // margin inside the popup public constructor(props: PopupProps) { + if (props.horizontal !== undefined) { + this._horizontal = props.horizontal; + } this._left = props.left; this._right = props.right; this._top = props.top; @@ -33,6 +38,14 @@ export class PopupStyle { } } + public get flexFlow(): string { + return this._horizontal ? 'row' : 'column'; + } + + public get isDividerHorizontal(): boolean { + return !this._horizontal; + } + public get leftPx(): string { return PopupStyle.getStringWithPx(this._left); } diff --git a/react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts b/react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts new file mode 100644 index 00000000000..839e6f5e10d --- /dev/null +++ b/react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts @@ -0,0 +1,31 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { type AxisGizmoTool } from '@cognite/reveal/tools'; +import { type BaseCommand } from '../commands/BaseCommand'; +import { PopupStyle } from '../domainObjectsHelpers/PopupStyle'; +import { NavigationTool } from '../commands/NavigationTool'; +import { type BaseTool } from '../commands/BaseTool'; + +export class BaseRevealConfig { + // ================================================== + // VIRTUAL METHODS: Override these to config the viewer + // ================================================== + + public createMainToolbar(): Array { + return []; + } + + public createMainToolbarStyle(): PopupStyle { + return new PopupStyle({ right: 0, top: 0, horizontal: false }); + } + + public createAxisGizmoTool(): AxisGizmoTool | undefined { + return undefined; + } + + public createDefaultTool(): BaseTool { + return new NavigationTool(); + } +} diff --git a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts index 447039d1596..7f91eb67dc4 100644 --- a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts +++ b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts @@ -9,7 +9,6 @@ import { type Cognite3DViewer, type IFlexibleCameraManager } from '@cognite/reveal'; -import { NavigationTool } from '../commands/NavigationTool'; import { Vector3, AmbientLight, @@ -26,7 +25,8 @@ import { getResizeCursor } from '../utilities/geometry/getResizeCursor'; import { VisualDomainObject } from '../domainObjects/VisualDomainObject'; import { ThreeView } from '../views/ThreeView'; import { type DomainObject } from '../domainObjects/DomainObject'; -import { AxisGizmoTool } from '@cognite/reveal/tools'; +import { type AxisGizmoTool } from '@cognite/reveal/tools'; +import { type BaseRevealConfig } from './BaseRevealConfig'; const DIRECTIONAL_LIGHT_NAME = 'DirectionalLight'; @@ -43,6 +43,7 @@ export class RevealRenderTarget { private _cropBoxBoundingBox: Box3 | undefined; private _cropBoxName: string | undefined = undefined; private _axisGizmoTool: AxisGizmoTool | undefined; + private _config: BaseRevealConfig | undefined = undefined; // ================================================== // CONTRUCTORS @@ -62,11 +63,6 @@ export class RevealRenderTarget { this.initializeLights(); this._viewer.on('cameraChange', this.cameraChangeHandler); this._viewer.on('beforeSceneRendered', this.beforeSceneRenderedHandler); - - const navigationTool = new NavigationTool(); - navigationTool.attach(this); - this.toolController.add(navigationTool); - this.toolController.setDefaultTool(navigationTool); } // ================================================== @@ -77,6 +73,10 @@ export class RevealRenderTarget { return this._viewer; } + public get config(): BaseRevealConfig | undefined { + return this._config; + } + public get rootDomainObject(): RootDomainObject { return this._rootDomainObject; } @@ -129,9 +129,19 @@ export class RevealRenderTarget { // INSTANCE METHODS // ================================================== - public addAxisGizmo(): void { - this._axisGizmoTool = new AxisGizmoTool(); - this._axisGizmoTool.connect(this._viewer); + public setConfig(config: BaseRevealConfig): void { + this._config = config; + + const defaultTool = config.createDefaultTool(); + defaultTool.attach(this); + this.toolController.add(defaultTool); + this.toolController.setDefaultTool(defaultTool); + + const axisGizmoTool = config.createAxisGizmoTool(); + if (axisGizmoTool !== undefined) { + axisGizmoTool.connect(this._viewer); + this._axisGizmoTool = axisGizmoTool; + } } public dispose(): void { diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts index 729ec22e9f4..79a632cd1eb 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts @@ -12,7 +12,7 @@ export class MeasureLineRenderStyle extends MeasureRenderStyle { // ================================================== public lineWidth = 2; - public pipeRadius = 0.03; + public pipeRadius = 0.015; public selectedLineWidth = 2; // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts index 31b7455036a..efdbe2416a1 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasurementTool.ts @@ -43,17 +43,17 @@ export class MeasurementTool extends BaseEditTool { return { key: 'MEASUREMENTS', fallback: 'Measurements' }; } - public override getToolbar(): Array | undefined { - const result = new Array(); - result.push(new SetMeasurmentTypeCommand(MeasureType.Line)); - result.push(new SetMeasurmentTypeCommand(MeasureType.Polyline)); - result.push(new SetMeasurmentTypeCommand(MeasureType.Polygon)); - result.push(new SetMeasurmentTypeCommand(MeasureType.HorizontalArea)); - result.push(new SetMeasurmentTypeCommand(MeasureType.VerticalArea)); - result.push(new SetMeasurmentTypeCommand(MeasureType.Volume)); - result.push(undefined); // Means separator - result.push(new ShowMeasurmentsOnTopCommand()); - return result; + public override getToolbar(): Array { + return [ + new SetMeasurmentTypeCommand(MeasureType.Line), + new SetMeasurmentTypeCommand(MeasureType.Polyline), + new SetMeasurmentTypeCommand(MeasureType.Polygon), + new SetMeasurmentTypeCommand(MeasureType.HorizontalArea), + new SetMeasurmentTypeCommand(MeasureType.VerticalArea), + new SetMeasurmentTypeCommand(MeasureType.Volume), + undefined, // Separator + new ShowMeasurmentsOnTopCommand() + ]; } public override getToolbarStyle(): PopupStyle { @@ -188,7 +188,6 @@ export class MeasurementTool extends BaseEditTool { if (measurment !== undefined) { this.deselectAll(measurment); measurment.setSelectedInteractive(true); - this.renderTarget.toolController.update(); return; } const ray = this.getRay(event); @@ -205,7 +204,6 @@ export class MeasurementTool extends BaseEditTool { rootDomainObject.addChildInteractive(domainObject); domainObject.setSelectedInteractive(true); domainObject.setVisibleInteractive(true, renderTarget); - this.renderTarget.toolController.update(); } } else { if (creator.addPoint(ray, intersection)) { diff --git a/react-components/src/architecture/concrete/config/StoryBookConfig.ts b/react-components/src/architecture/concrete/config/StoryBookConfig.ts new file mode 100644 index 00000000000..ba1accbc731 --- /dev/null +++ b/react-components/src/architecture/concrete/config/StoryBookConfig.ts @@ -0,0 +1,46 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { FlexibleControlsType } from '@cognite/reveal'; +import { type BaseCommand } from '../../base/commands/BaseCommand'; +import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; +import { SetFlexibleControlsTypeCommand } from '../../base/concreteCommands/SetFlexibleControlsTypeCommand'; +import { SetTerrainVisibleCommand } from '../terrainDomainObject/SetTerrainVisibleCommand'; +import { UpdateTerrainCommand } from '../terrainDomainObject/UpdateTerrainCommand'; +import { FitViewCommand } from '../../base/concreteCommands/FitViewCommand'; +import { SetAxisVisibleCommand } from '../axis/SetAxisVisibleCommand'; +import { ExampleTool } from '../exampleDomainObject/ExampleTool'; +import { MeasurementTool } from '../boxDomainObject/MeasurementTool'; +import { AxisGizmoTool } from '@cognite/reveal/tools'; +import { BaseRevealConfig } from '../../base/renderTarget/BaseRevealConfig'; + +export class StoryBookConfig extends BaseRevealConfig { + // ================================================== + // OVERRIDES + // ================================================== + + public override createMainToolbar(): Array { + return [ + new SetFlexibleControlsTypeCommand(FlexibleControlsType.Orbit), + new SetFlexibleControlsTypeCommand(FlexibleControlsType.FirstPerson), + undefined, + new FitViewCommand(), + new SetAxisVisibleCommand(), + undefined, + new SetTerrainVisibleCommand(), + new UpdateTerrainCommand(), + undefined, + new ExampleTool(), + new MeasurementTool() + ]; + } + + public override createMainToolbarStyle(): PopupStyle { + return new PopupStyle({ right: 0, top: 0, horizontal: false }); + } + + public override createAxisGizmoTool(): AxisGizmoTool | undefined { + return new AxisGizmoTool(); + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts new file mode 100644 index 00000000000..152bf0a6103 --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts @@ -0,0 +1,105 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { ExampleRenderStyle } from './ExampleRenderStyle'; +import { type RenderStyle } from '../../base/domainObjectsHelpers/RenderStyle'; +import { type ThreeView } from '../../base/views/ThreeView'; +import { ExampleView } from './ExampleView'; +import { NumberType, PanelInfo } from '../../base/domainObjectsHelpers/PanelInfo'; +import { + type CreateDraggerProps, + VisualDomainObject +} from '../../base/domainObjects/VisualDomainObject'; +import { Vector3 } from 'three'; +import { PopupStyle } from '../../base/domainObjectsHelpers/PopupStyle'; +import { DomainObjectPanelUpdater } from '../../base/reactUpdaters/DomainObjectPanelUpdater'; +import { type DomainObjectChange } from '../../base/domainObjectsHelpers/DomainObjectChange'; +import { Changes } from '../../base/domainObjectsHelpers/Changes'; +import { type BaseDragger } from '../../base/domainObjectsHelpers/BaseDragger'; +import { ExampleDragger } from './ExampleDragger'; + +export class ExampleDomainObject extends VisualDomainObject { + // ================================================== + // INSTANCE FIELDS + // ================================================== + + public readonly center = new Vector3(); + + // ================================================== + // INSTANCE PROPERTIES + // ================================================== + + public get renderStyle(): ExampleRenderStyle { + return super.getRenderStyle() as ExampleRenderStyle; + } + + // ================================================== + // OVERRIDES of DomainObject + // ================================================== + + public override get canBeRemoved(): boolean { + return false; + } + + public override get icon(): string { + return 'Circle'; + } + + public override get typeName(): string { + return 'Example'; + } + + public override createRenderStyle(): RenderStyle | undefined { + return new ExampleRenderStyle(); + } + + public override createDragger(props: CreateDraggerProps): BaseDragger | undefined { + return new ExampleDragger(props, this); + } + + public override getPanelInfo(): PanelInfo | undefined { + const info = new PanelInfo(); + info.setHeader('NAME', this.name); + add('XCORDINATE', 'X coordinate', this.center.x, NumberType.Length); + add('YCORDINATE', 'Y coordinate', this.center.y, NumberType.Length); + add('ZCORDINATE', 'Z coordinate', this.center.z, NumberType.Length); + return info; + + function add(key: string, fallback: string, value: number, numberType: NumberType): void { + info.add({ key, fallback, value, numberType }); + } + } + + public override getPanelInfoStyle(): PopupStyle { + // bottom = 66 because the measurement toolbar is below + return new PopupStyle({ bottom: 66, left: 0 }); + } + + protected override notifyCore(change: DomainObjectChange): void { + super.notifyCore(change); + + if (!DomainObjectPanelUpdater.isActive) { + return; + } + if (this.isSelected) { + if (change.isChanged(Changes.deleted)) { + DomainObjectPanelUpdater.update(undefined); + } + if (change.isChanged(Changes.selected, Changes.geometry, Changes.naming)) { + DomainObjectPanelUpdater.update(this); + } + } else { + if (change.isChanged(Changes.selected)) { + DomainObjectPanelUpdater.update(undefined); // Deselected + } + } + } + // ================================================== + // OVERRIDES of VisualDomainObject + // ================================================== + + protected override createThreeView(): ThreeView | undefined { + return new ExampleView(); + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts new file mode 100644 index 00000000000..e9c0075e081 --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts @@ -0,0 +1,49 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { type Ray, Vector3, Plane } from 'three'; +import { Changes } from '../../base/domainObjectsHelpers/Changes'; +import { type DomainObject } from '../../base/domainObjects/DomainObject'; +import { type ExampleDomainObject } from './ExampleDomainObject'; +import { BaseDragger } from '../../base/domainObjectsHelpers/BaseDragger'; +import { type CreateDraggerProps } from '../../base/domainObjects/VisualDomainObject'; + +export class ExampleDragger extends BaseDragger { + // ================================================== + // INSTANCE FIELDS + // ================================================== + + private readonly _domainObject: ExampleDomainObject; + private readonly _center: Vector3; + private readonly _plane: Plane; + + // ================================================== + // CONTRUCTOR + // ================================================== + + public constructor(props: CreateDraggerProps, domainObject: ExampleDomainObject) { + super(props); + this._domainObject = domainObject; + this._center = this._domainObject.center.clone(); + this._plane = new Plane().setFromNormalAndCoplanarPoint(this.ray.direction, this._center); + } + + // ================================================== + // OVERRIDES + // ================================================== + + public get domainObject(): DomainObject { + return this._domainObject; + } + + public override onPointerDrag(_event: PointerEvent, ray: Ray): boolean { + const planeIntersect = ray.intersectPlane(this._plane, new Vector3()); + if (planeIntersect === null) { + return false; + } + this._domainObject.center.copy(planeIntersect); + this.domainObject.notify(Changes.geometry); + return true; + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts new file mode 100644 index 00000000000..27711a8a4aa --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleRenderStyle.ts @@ -0,0 +1,23 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { cloneDeep } from 'lodash'; +import { RenderStyle } from '../../base/domainObjectsHelpers/RenderStyle'; + +export class ExampleRenderStyle extends RenderStyle { + // ================================================== + // INSTANCE FIELDS + // ================================================== + + public radius = 1; + public opacity = 0.5; + + // ================================================== + // OVERRIDES of BaseStyle + // ================================================== + + public override clone(): RenderStyle { + return cloneDeep(this); + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts new file mode 100644 index 00000000000..aafb6327642 --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts @@ -0,0 +1,133 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { ExampleDomainObject } from './ExampleDomainObject'; +import { CDF_TO_VIEWER_TRANSFORMATION, type AnyIntersection } from '@cognite/reveal'; +import { type BaseCommand, type Tooltip } from '../../base/commands/BaseCommand'; +import { isDomainObjectIntersection } from '../../base/domainObjectsHelpers/DomainObjectIntersection'; +import { BaseEditTool } from '../../base/commands/BaseEditTool'; +import { Changes } from '../../base/domainObjectsHelpers/Changes'; +import { ResetAllExamplesCommand } from './commands/ResetAllExamplesCommand'; +import { DeleteAllExamplesCommand } from './commands/DeleteAllExamplesCommand'; +import { ShowAllExamplesCommand } from './commands/ShowAllExamplesCommand'; +import { clamp } from 'lodash'; +export class ExampleTool extends BaseEditTool { + // ================================================== + // OVERRIDES of BaseCommand + // ================================================== + + public override get icon(): string { + return 'Circle'; + } + + public override get tooltip(): Tooltip { + return { key: 'EXAMPLE_EDIT', fallback: 'Create or edit a point' }; + } + + // ================================================== + // OVERRIDES of BaseTool + // ================================================== + + public override onDeactivate(): void { + super.onDeactivate(); + this.deselectAll(); + } + + public override onKey(event: KeyboardEvent, down: boolean): void { + if (down && event.key === 'Delete') { + const domainObject = this.rootDomainObject.getSelectedDescendantByType(ExampleDomainObject); + if (domainObject !== undefined) { + domainObject.removeInteractive(); + } + return; + } + super.onKey(event, down); + } + + public override async onWheel(event: WheelEvent): Promise { + const intersection = await this.getIntersection(event); + const domainObject = this.getExampleDomainObject(intersection); + if (domainObject === undefined || !domainObject.isSelected) { + await super.onWheel(event); + return; + } + if (event.ctrlKey) { + const delta = Math.sign(event.deltaY) * 0.05; + domainObject.renderStyle.opacity = clamp(domainObject.renderStyle.opacity + delta, 0.2, 1); + domainObject.notify(Changes.renderStyle); + } else { + // Change radius + const factor = 1 - Math.sign(event.deltaY) * 0.1; + domainObject.renderStyle.radius *= factor; + } + domainObject.notify(Changes.renderStyle); + } + + public override async onHover(event: PointerEvent): Promise { + const intersection = await this.getIntersection(event); + const domainObject = this.getExampleDomainObject(intersection); + + if (domainObject instanceof ExampleDomainObject) { + this.renderTarget.setMoveCursor(); + } else if (intersection !== undefined) { + this.renderTarget.setCrosshairCursor(); + } else { + this.renderTarget.setNavigateCursor(); + } + } + + public override async onClick(event: PointerEvent): Promise { + const { renderTarget, rootDomainObject } = this; + + const intersection = await this.getIntersection(event); + if (intersection === undefined) { + await super.onClick(event); + return; + } + { + const domainObject = this.getExampleDomainObject(intersection); + if (domainObject !== undefined) { + this.deselectAll(domainObject); + domainObject.setSelectedInteractive(true); + return; + } + } + const center = intersection.point.clone(); + const matrix = CDF_TO_VIEWER_TRANSFORMATION.clone().invert(); + center.applyMatrix4(matrix); + + const domainObject = new ExampleDomainObject(); + domainObject.center.copy(center); + + rootDomainObject.addChildInteractive(domainObject); + domainObject.setVisibleInteractive(true, renderTarget); + this.deselectAll(); + domainObject.setSelectedInteractive(true); + } + + public override getToolbar(): Array { + return [ + new ResetAllExamplesCommand(), + new ShowAllExamplesCommand(), + new DeleteAllExamplesCommand() + ]; + } + + // ================================================== + // INSTANCE METHODS + // ================================================== + + private getExampleDomainObject( + intersection: AnyIntersection | undefined + ): ExampleDomainObject | undefined { + if (!isDomainObjectIntersection(intersection)) { + return undefined; + } + if (intersection.domainObject instanceof ExampleDomainObject) { + return intersection.domainObject; + } else { + return undefined; + } + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts new file mode 100644 index 00000000000..eac264506ea --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleView.ts @@ -0,0 +1,111 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { Mesh, MeshPhongMaterial, Sphere, SphereGeometry, Vector3 } from 'three'; +import { type ExampleDomainObject } from './ExampleDomainObject'; +import { type DomainObjectChange } from '../../base/domainObjectsHelpers/DomainObjectChange'; +import { Changes } from '../../base/domainObjectsHelpers/Changes'; +import { type ExampleRenderStyle } from './ExampleRenderStyle'; +import { GroupThreeView } from '../../base/views/GroupThreeView'; +import { + CDF_TO_VIEWER_TRANSFORMATION, + type CustomObjectIntersectInput, + type CustomObjectIntersection +} from '@cognite/reveal'; +import { type DomainObjectIntersection } from '../../base/domainObjectsHelpers/DomainObjectIntersection'; +import { WHITE_COLOR } from '../../base/utilities/colors/colorExtensions'; + +export class ExampleView extends GroupThreeView { + // ================================================== + // INSTANCE PROPERTIES + // ================================================== + + protected override get domainObject(): ExampleDomainObject { + return super.domainObject as ExampleDomainObject; + } + + protected override get style(): ExampleRenderStyle { + return super.style as ExampleRenderStyle; + } + + // ================================================== + // OVERRIDES of BaseView + // ================================================== + + public override update(change: DomainObjectChange): void { + super.update(change); + if (this.isEmpty) { + return; + } + if ( + change.isChanged(Changes.geometry) || + change.isChanged(Changes.selected) || + change.isChanged(Changes.renderStyle) || + change.isChanged(Changes.color) + ) { + this.removeChildren(); + this.invalidateBoundingBox(); + this.invalidateRenderTarget(); + } + } + + // ================================================== + // OVERRIDES of GroupThreeView + // ================================================== + + protected override addChildren(): void { + const { domainObject, style } = this; + + const color = domainObject.color; + const geometry = new SphereGeometry(style.radius, 32, 16); + const material = new MeshPhongMaterial({ + color, + emissive: WHITE_COLOR, + emissiveIntensity: domainObject.isSelected ? 0.4 : 0.0, + shininess: 5, + opacity: style.opacity, + transparent: true + }); + const sphere = new Mesh(geometry, material); + const center = domainObject.center.clone(); + center.applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION); + sphere.position.copy(center); + this.addChild(sphere); + } + + // Do actually not need this because the base class takes care of this + public override intersectIfCloser( + intersectInput: CustomObjectIntersectInput, + closestDistance: number | undefined + ): undefined | CustomObjectIntersection { + const { domainObject, style } = this; + + const ray = intersectInput.raycaster.ray; + const center = domainObject.center.clone(); + center.applyMatrix4(CDF_TO_VIEWER_TRANSFORMATION); + const sphere = new Sphere(center, style.radius); + const point = ray.intersectSphere(sphere, new Vector3()); + if (point === null) { + return undefined; + } + const distanceToCamera = point.distanceTo(ray.origin); + if (closestDistance !== undefined && closestDistance < distanceToCamera) { + return undefined; + } + if (!intersectInput.isVisible(point)) { + return undefined; + } + const customObjectIntersection: DomainObjectIntersection = { + type: 'customObject', + point, + distanceToCamera, + customObject: this, + domainObject + }; + if (this.shouldPickBoundingBox) { + customObjectIntersection.boundingBox = this.boundingBox; + } + return customObjectIntersection; + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/commands/DeleteAllExamplesCommand.ts b/react-components/src/architecture/concrete/exampleDomainObject/commands/DeleteAllExamplesCommand.ts new file mode 100644 index 00000000000..e6729404595 --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/commands/DeleteAllExamplesCommand.ts @@ -0,0 +1,40 @@ +/*! + * Copyright 2024 Cognite AS + * BaseTool: Base class for the tool are used to interact with the render target. + */ + +import { RenderTargetCommand } from '../../../base/commands/RenderTargetCommand'; +import { type Tooltip } from '../../../base/commands/BaseCommand'; +import { ExampleDomainObject } from '../ExampleDomainObject'; + +export class DeleteAllExamplesCommand extends RenderTargetCommand { + // ================================================== + // OVERRIDES + // ================================================== + + public override get tooltip(): Tooltip { + return { key: 'EXAMPLES_DELETE', fallback: 'Remove all examples' }; + } + + public override get icon(): string { + return 'Delete'; + } + + public override get isEnabled(): boolean { + const first = this.getFirst(); + return first !== undefined && first.canBeRemoved; + } + + protected override invokeCore(): boolean { + const array = Array.from(this.rootDomainObject.getDescendantsByType(ExampleDomainObject)); + array.reverse(); + for (const domainObject of array) { + domainObject.removeInteractive(); + } + return true; + } + + private getFirst(): ExampleDomainObject | undefined { + return this.rootDomainObject.getDescendantByType(ExampleDomainObject); + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts b/react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts new file mode 100644 index 00000000000..0ff53c31d66 --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/commands/ResetAllExamplesCommand.ts @@ -0,0 +1,39 @@ +/*! + * Copyright 2024 Cognite AS + * BaseTool: Base class for the tool are used to interact with the render target. + */ + +import { RenderTargetCommand } from '../../../base/commands/RenderTargetCommand'; +import { type Tooltip } from '../../../base/commands/BaseCommand'; +import { ExampleDomainObject } from '../ExampleDomainObject'; +import { Changes } from '../../../base/domainObjectsHelpers/Changes'; + +export class ResetAllExamplesCommand extends RenderTargetCommand { + // ================================================== + // OVERRIDES + // ================================================== + + public override get tooltip(): Tooltip { + return { key: 'EXAMPLES_RESET', fallback: 'Reset' }; + } + + public override get icon(): string { + return 'Copy'; + } + + public override get isEnabled(): boolean { + return this.getFirst() !== undefined; + } + + protected override invokeCore(): boolean { + for (const domainObject of this.rootDomainObject.getDescendantsByType(ExampleDomainObject)) { + domainObject.setRenderStyle(undefined); + domainObject.notify(Changes.renderStyle); + } + return true; + } + + private getFirst(): ExampleDomainObject | undefined { + return this.rootDomainObject.getDescendantByType(ExampleDomainObject); + } +} diff --git a/react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts b/react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts new file mode 100644 index 00000000000..fdf4b34616d --- /dev/null +++ b/react-components/src/architecture/concrete/exampleDomainObject/commands/ShowAllExamplesCommand.ts @@ -0,0 +1,50 @@ +/*! + * Copyright 2024 Cognite AS + * BaseTool: Base class for the tool are used to interact with the render target. + */ + +import { RenderTargetCommand } from '../../../base/commands/RenderTargetCommand'; +import { type Tooltip } from '../../../base/commands/BaseCommand'; +import { ExampleDomainObject } from '../ExampleDomainObject'; + +export class ShowAllExamplesCommand extends RenderTargetCommand { + // ================================================== + // OVERRIDES + // ================================================== + + public override get tooltip(): Tooltip { + return { key: 'EXAMPLES_SHOW', fallback: 'Show all examples' }; + } + + public override get icon(): string { + return 'EyeShow'; + } + + public override get isEnabled(): boolean { + return this.getFirst() !== undefined; + } + + public override get isCheckable(): boolean { + return true; + } + + public override get isChecked(): boolean { + const domainObject = this.getFirst(); + if (domainObject === undefined) { + return false; + } + return domainObject.isVisible(this.renderTarget); + } + + protected override invokeCore(): boolean { + const isVisible = this.isChecked; + for (const domainObject of this.rootDomainObject.getDescendantsByType(ExampleDomainObject)) { + domainObject.setVisibleInteractive(!isVisible, this.renderTarget); + } + return true; + } + + private getFirst(): ExampleDomainObject | undefined { + return this.rootDomainObject.getDescendantByType(ExampleDomainObject); + } +} diff --git a/react-components/src/components/Architecture/DomainObjectPanel.tsx b/react-components/src/components/Architecture/DomainObjectPanel.tsx index e4a0758d9b8..392d1619dc7 100644 --- a/react-components/src/components/Architecture/DomainObjectPanel.tsx +++ b/react-components/src/components/Architecture/DomainObjectPanel.tsx @@ -57,21 +57,21 @@ export const DomainObjectPanel = (): ReactElement => { {t(header.key, header.fallback)} )} - - - - - + }}> + + + + + )} CreateButton(new FitViewCommand()); @@ -20,6 +21,7 @@ export class RevealButtons { static SetTerrainVisible = (): ReactElement => CreateButton(new SetTerrainVisibleCommand()); static UpdateTerrain = (): ReactElement => CreateButton(new UpdateTerrainCommand()); static Measurement = (): ReactElement => CreateButton(new MeasurementTool()); + static Example = (): ReactElement => CreateButton(new ExampleTool()); static SetFlexibleControlsTypeOrbit = (): ReactElement => CreateButton(new SetFlexibleControlsTypeCommand(FlexibleControlsType.Orbit)); diff --git a/react-components/src/components/Architecture/ActiveToolToolbar.tsx b/react-components/src/components/Architecture/Toolbar.tsx similarity index 59% rename from react-components/src/components/Architecture/ActiveToolToolbar.tsx rename to react-components/src/components/Architecture/Toolbar.tsx index 954ae760eef..fdce7c5a538 100644 --- a/react-components/src/components/Architecture/ActiveToolToolbar.tsx +++ b/react-components/src/components/Architecture/Toolbar.tsx @@ -9,8 +9,27 @@ import { CommandButton } from './CommandButton'; import { type BaseCommand } from '../../architecture/base/commands/BaseCommand'; import { useRenderTarget } from '../RevealCanvas/ViewerContext'; import { ActiveToolUpdater } from '../../architecture/base/reactUpdaters/ActiveToolUpdater'; +import { type PopupStyle } from '../../architecture/base/domainObjectsHelpers/PopupStyle'; + +export const MainToolbar = (): ReactElement => { + const renderTarget = useRenderTarget(); + if (renderTarget === undefined) { + return <>; + } + const config = renderTarget.config; + if (config === undefined) { + return <>; + } + const commands = config.createMainToolbar(); + if (commands.length === 0) { + return <>; + } + const style = config.createMainToolbarStyle(); + return CreateToolToolbar(commands, style); +}; export const ActiveToolToolbar = (): ReactElement => { + // const [_activeToolUpdater, setActiveToolUpdater] = useState(0); ActiveToolUpdater.setCounterDelegate(setActiveToolUpdater); @@ -23,10 +42,21 @@ export const ActiveToolToolbar = (): ReactElement => { return <>; } const commands = activeTool.getToolbar(); - if (commands === undefined || commands.length === 0) { + if (commands.length === 0) { return <>; } const style = activeTool.getToolbarStyle(); + return CreateToolToolbar(commands, style); +}; + +const CreateToolToolbar = ( + commands: Array, + style: PopupStyle +): ReactElement => { + // + if (commands.length === 0 || style === null) { + return <>; + } return ( { margin: style.marginPx // Padding is not used here }}> - - <>{commands.map((command, index): ReactElement => addCommand(command, index))} + + <>{commands.map((command, index): ReactElement => addCommand(command, style, index))} ); }; -function addCommand(command: BaseCommand | undefined, index: number): ReactElement { +function addCommand( + command: BaseCommand | undefined, + style: PopupStyle, + index: number +): ReactElement { if (command === undefined) { - return ; + const direction = style.isDividerHorizontal ? 'horizontal' : 'vertical'; + return ; } return ; } @@ -57,6 +96,4 @@ const Container = styled.div` display: block; `; -const MyCustomToolbar = styled(withSuppressRevealEvents(ToolBar))` - flex-direction: row; -`; +const MyCustomToolbar = styled(withSuppressRevealEvents(ToolBar))``; diff --git a/react-components/src/index.ts b/react-components/src/index.ts index 9d8b7646a0c..cb06b695db7 100644 --- a/react-components/src/index.ts +++ b/react-components/src/index.ts @@ -148,7 +148,7 @@ export type { CriteriaTypes } from './components/RuleBasedOutputs/types'; -export { ActiveToolToolbar } from './components/Architecture/ActiveToolToolbar'; +export { ActiveToolToolbar } from './components/Architecture/Toolbar'; export { DomainObjectPanel } from './components/Architecture/DomainObjectPanel'; export { RevealButtons } from './components/Architecture/ToolButtons'; diff --git a/react-components/stories/Architecture.stories.tsx b/react-components/stories/Architecture.stories.tsx index f2489fe5239..2cbea51ef6c 100644 --- a/react-components/stories/Architecture.stories.tsx +++ b/react-components/stories/Architecture.stories.tsx @@ -7,21 +7,19 @@ import { CadModelContainer, type QualitySettings, RevealToolbar, - withSuppressRevealEvents, - withCameraStateUrlParam, useGetCameraStateFromUrlParam, useCameraNavigation } from '../src'; import { Color } from 'three'; -import styled from 'styled-components'; -import { Button, Menu, ToolBar } from '@cognite/cogs.js'; +import { Button, Menu } from '@cognite/cogs.js'; import { type ReactElement, useState, useEffect } from 'react'; import { signalStoryReadyForScreenshot } from './utilities/signalStoryReadyForScreenshot'; import { RevealStoryContainer } from './utilities/RevealStoryContainer'; import { getAddModelOptionsFromUrl } from './utilities/getAddModelOptionsFromUrl'; -import { RevealButtons } from '../src/components/Architecture/ToolButtons'; import { DomainObjectPanel } from '../src/components/Architecture/DomainObjectPanel'; -import { ActiveToolToolbar } from '../src/components/Architecture/ActiveToolToolbar'; +import { ActiveToolToolbar, MainToolbar } from '../src/components/Architecture/Toolbar'; +import { useRenderTarget } from '../src/components/RevealCanvas/ViewerContext'; +import { StoryBookConfig } from '../src/architecture/concrete/config/StoryBookConfig'; const meta = { title: 'Example/Architecture', @@ -32,12 +30,6 @@ const meta = { export default meta; type Story = StoryObj; -const MyCustomToolbar = styled(withSuppressRevealEvents(withCameraStateUrlParam(ToolBar)))` - position: absolute; - right: 20px; - top: 70px; -`; - const exampleCustomSettingElements = (): ReactElement => { const [originalCadColor, setOriginalCadColor] = useState(false); @@ -86,13 +78,14 @@ const exampleLowQualitySettings: QualitySettings = { export const Main: Story = { args: { - addModelOptions: getAddModelOptionsFromUrl('/primitives') + addModelOptions: getAddModelOptionsFromUrl('/cadModel') }, render: ({ addModelOptions }) => { return ( + - - <> - - - - <> - - - - <> - - - - <> - - - - - + + ); } }; +export const InitializeConfig = (): ReactElement => { + const renderTarget = useRenderTarget(); + if (renderTarget === undefined) { + return <>; + } + if (renderTarget.config === undefined) { + renderTarget.setConfig(new StoryBookConfig()); + } + return <>; +}; + function FitToUrlCameraState(): ReactElement { const getCameraState = useGetCameraStateFromUrlParam(); const cameraNavigation = useCameraNavigation(); diff --git a/react-components/stories/utilities/RevealStoryContainer.tsx b/react-components/stories/utilities/RevealStoryContainer.tsx index 2c6bff06d0e..d232733b117 100644 --- a/react-components/stories/utilities/RevealStoryContainer.tsx +++ b/react-components/stories/utilities/RevealStoryContainer.tsx @@ -17,6 +17,7 @@ import { import { type Image360AnnotationCache } from '../../src/components/CacheProvider/Image360AnnotationCache'; import { type SceneIdentifiers } from '../../src/components/SceneContainer/sceneTypes'; import { RevealRenderTarget } from '../../src/architecture/base/renderTarget/RevealRenderTarget'; +import { StoryBookConfig } from '../../src/architecture/concrete/config/StoryBookConfig'; type RevealStoryContainerProps = Omit & { sdk?: CogniteClient; @@ -51,7 +52,7 @@ export const RevealStoryContext = ({ useFlexibleCameraManager: true }); const renderTarget = new RevealRenderTarget(newViewer); - renderTarget.addAxisGizmo(); + renderTarget.setConfig(new StoryBookConfig()); return renderTarget; } }, [viewer]); diff --git a/react-components/yarn.lock b/react-components/yarn.lock index de057d906cc..fa0c9f5aade 100644 --- a/react-components/yarn.lock +++ b/react-components/yarn.lock @@ -1782,6 +1782,7 @@ __metadata: locize-cli: "npm:^8.0.1" lodash: "npm:^4.17.21" moq.ts: "npm:10.0.8" + node-on-idle: "npm:^1.0.0" playwright: "npm:^1.43.1" prettier: "npm:^3.2.5" prop-types: "npm:^15.8.1" @@ -11219,6 +11220,13 @@ __metadata: languageName: node linkType: hard +"node-on-idle@npm:^1.0.0": + version: 1.0.0 + resolution: "node-on-idle@npm:1.0.0" + checksum: 10/c18c17fc869cbc0c815fd47d53aa439acf12f80f0f50b2d7b5bcebe2b0820be4de81f246457e427a8677120df1376fa39d5d0472bfa5d3b04c6d9bebd5d999d2 + languageName: node + linkType: hard + "node-releases@npm:^2.0.14": version: 2.0.14 resolution: "node-releases@npm:2.0.14" From f2748f21f2c90e0d4a2e9953fe247b9956a09177 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 12:18:15 +0200 Subject: [PATCH 20/27] Update Architecture.stories.tsx --- react-components/stories/Architecture.stories.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/react-components/stories/Architecture.stories.tsx b/react-components/stories/Architecture.stories.tsx index 2cbea51ef6c..1dd8202e572 100644 --- a/react-components/stories/Architecture.stories.tsx +++ b/react-components/stories/Architecture.stories.tsx @@ -18,8 +18,6 @@ import { RevealStoryContainer } from './utilities/RevealStoryContainer'; import { getAddModelOptionsFromUrl } from './utilities/getAddModelOptionsFromUrl'; import { DomainObjectPanel } from '../src/components/Architecture/DomainObjectPanel'; import { ActiveToolToolbar, MainToolbar } from '../src/components/Architecture/Toolbar'; -import { useRenderTarget } from '../src/components/RevealCanvas/ViewerContext'; -import { StoryBookConfig } from '../src/architecture/concrete/config/StoryBookConfig'; const meta = { title: 'Example/Architecture', @@ -85,7 +83,6 @@ export const Main: Story = { - { - const renderTarget = useRenderTarget(); - if (renderTarget === undefined) { - return <>; - } - if (renderTarget.config === undefined) { - renderTarget.setConfig(new StoryBookConfig()); - } - return <>; -}; - function FitToUrlCameraState(): ReactElement { const getCameraState = useGetCameraStateFromUrlParam(); const cameraNavigation = useCameraNavigation(); From 98d84063e4767f6326f2368a3ac541c69155abc7 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 12:19:45 +0200 Subject: [PATCH 21/27] Update yarn.lock --- react-components/yarn.lock | 8 -------- 1 file changed, 8 deletions(-) diff --git a/react-components/yarn.lock b/react-components/yarn.lock index fa0c9f5aade..de057d906cc 100644 --- a/react-components/yarn.lock +++ b/react-components/yarn.lock @@ -1782,7 +1782,6 @@ __metadata: locize-cli: "npm:^8.0.1" lodash: "npm:^4.17.21" moq.ts: "npm:10.0.8" - node-on-idle: "npm:^1.0.0" playwright: "npm:^1.43.1" prettier: "npm:^3.2.5" prop-types: "npm:^15.8.1" @@ -11220,13 +11219,6 @@ __metadata: languageName: node linkType: hard -"node-on-idle@npm:^1.0.0": - version: 1.0.0 - resolution: "node-on-idle@npm:1.0.0" - checksum: 10/c18c17fc869cbc0c815fd47d53aa439acf12f80f0f50b2d7b5bcebe2b0820be4de81f246457e427a8677120df1376fa39d5d0472bfa5d3b04c6d9bebd5d999d2 - languageName: node - linkType: hard - "node-releases@npm:^2.0.14": version: 2.0.14 resolution: "node-releases@npm:2.0.14" From 316fd10dcf986dd6bc3a8e4df43ee0e06af03a7e Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 12:58:48 +0200 Subject: [PATCH 22/27] Fix MeasureLineView so lines are visible from long distance --- .../boxDomainObject/MeasureLineRenderStyle.ts | 2 +- .../boxDomainObject/MeasureLineView.ts | 50 +++++++++++++++---- .../ExampleDomainObject.ts | 2 +- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts index 79a632cd1eb..d1a12125ed8 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineRenderStyle.ts @@ -11,8 +11,8 @@ export class MeasureLineRenderStyle extends MeasureRenderStyle { // INSTANCE FIELDS // ================================================== - public lineWidth = 2; public pipeRadius = 0.015; + public lineWidth = 1; public selectedLineWidth = 2; // ================================================== diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts index c4017d62a3d..c60287d628f 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureLineView.ts @@ -4,8 +4,12 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ import { + BufferAttribute, + BufferGeometry, CylinderGeometry, FrontSide, + Line, + LineBasicMaterial, Mesh, MeshPhongMaterial, Quaternion, @@ -73,7 +77,8 @@ export class MeasureLineView extends GroupThreeView { // ================================================== protected override addChildren(): void { - this.addChild(this.createCylinders()); + this.addChild(this.createPipe()); + this.addChild(this.createLines()); // Create a line so it can be seen from long distance this.addLabels(); } @@ -91,17 +96,17 @@ export class MeasureLineView extends GroupThreeView { // INSTANCE METHODS: // ================================================== - private createCylinders(): Mesh | undefined { + private createPipe(): Mesh | undefined { const { domainObject, style } = this; + const radius = style.pipeRadius; + if (radius <= 0) { + return; + } const { points } = domainObject; const { length } = points; if (length < 2) { return undefined; } - const radius = style.pipeRadius; - if (radius <= 0) { - return; - } const geometries: CylinderGeometry[] = []; const loopLength = domainObject.measureType === MeasureType.Polygon ? length + 1 : length; @@ -138,17 +143,20 @@ export class MeasureLineView extends GroupThreeView { return new Mesh(mergeGeometries(geometries, false), material); } - private createLines(): Wireframe | undefined { + private createWireframe(): Wireframe | undefined { const { domainObject, style } = this; + const linewidth = domainObject.isSelected ? style.selectedLineWidth : style.lineWidth; + if (linewidth === 0) { + return undefined; + } const vertices = createVertices(domainObject); if (vertices === undefined) { return undefined; } const color = domainObject.getColorByColorType(style.colorType); - const linewidth = domainObject.isSelected ? style.selectedLineWidth : style.lineWidth; const geometry = new LineSegmentsGeometry().setPositions(vertices); const material = new LineMaterial({ - linewidth: linewidth / 50, + linewidth, color, resolution: new Vector2(1000, 1000), worldUnits: true, @@ -157,6 +165,30 @@ export class MeasureLineView extends GroupThreeView { return new Wireframe(geometry, material); } + private createLines(): Line | undefined { + const { domainObject, style } = this; + const vertices = createVertices(domainObject); + if (vertices === undefined) { + return undefined; + } + const color = domainObject.getColorByColorType(style.colorType); + const linewidth = domainObject.isSelected ? style.selectedLineWidth : style.lineWidth; + const geometry = createBufferGeometry(vertices); + const material = new LineBasicMaterial({ + linewidth, + color, + depthTest: style.depthTest + }); + return new Line(geometry, material); + + function createBufferGeometry(vertices: number[]): BufferGeometry { + const verticesArray = new Float32Array(vertices); + const geometry = new BufferGeometry(); + geometry.setAttribute('position', new BufferAttribute(verticesArray, 3)); + return geometry; + } + } + private addLabels(): void { const { domainObject, style } = this; const { points } = domainObject; diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts index 152bf0a6103..b2c4c6d4f84 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts @@ -39,7 +39,7 @@ export class ExampleDomainObject extends VisualDomainObject { // ================================================== public override get canBeRemoved(): boolean { - return false; + return true; } public override get icon(): string { From d42f04c566ad6213c3365a221da7adccaef23733 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 13:11:22 +0200 Subject: [PATCH 23/27] Make a DefaultRevealConfig --- .../src/architecture/base/renderTarget/BaseRevealConfig.ts | 2 +- .../architecture/base/renderTarget/DefaultRevealConfig.ts | 7 +++++++ .../architecture/base/renderTarget/RevealRenderTarget.ts | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 react-components/src/architecture/base/renderTarget/DefaultRevealConfig.ts diff --git a/react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts b/react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts index 839e6f5e10d..1ae5fca1d75 100644 --- a/react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts +++ b/react-components/src/architecture/base/renderTarget/BaseRevealConfig.ts @@ -8,7 +8,7 @@ import { PopupStyle } from '../domainObjectsHelpers/PopupStyle'; import { NavigationTool } from '../commands/NavigationTool'; import { type BaseTool } from '../commands/BaseTool'; -export class BaseRevealConfig { +export abstract class BaseRevealConfig { // ================================================== // VIRTUAL METHODS: Override these to config the viewer // ================================================== diff --git a/react-components/src/architecture/base/renderTarget/DefaultRevealConfig.ts b/react-components/src/architecture/base/renderTarget/DefaultRevealConfig.ts new file mode 100644 index 00000000000..76bd51afdf1 --- /dev/null +++ b/react-components/src/architecture/base/renderTarget/DefaultRevealConfig.ts @@ -0,0 +1,7 @@ +/*! + * Copyright 2024 Cognite AS + */ + +import { BaseRevealConfig } from './BaseRevealConfig'; + +export class DefaultRevealConfig extends BaseRevealConfig {} diff --git a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts index 7f91eb67dc4..6bac37e06ad 100644 --- a/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts +++ b/react-components/src/architecture/base/renderTarget/RevealRenderTarget.ts @@ -27,6 +27,7 @@ import { ThreeView } from '../views/ThreeView'; import { type DomainObject } from '../domainObjects/DomainObject'; import { type AxisGizmoTool } from '@cognite/reveal/tools'; import { type BaseRevealConfig } from './BaseRevealConfig'; +import { DefaultRevealConfig } from './DefaultRevealConfig'; const DIRECTIONAL_LIGHT_NAME = 'DirectionalLight'; @@ -63,6 +64,8 @@ export class RevealRenderTarget { this.initializeLights(); this._viewer.on('cameraChange', this.cameraChangeHandler); this._viewer.on('beforeSceneRendered', this.beforeSceneRenderedHandler); + + this.setConfig(new DefaultRevealConfig()); } // ================================================== From d4804cc0e11a2bccd5cdf31d7d10cefe6e3b056a Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 13:16:35 +0200 Subject: [PATCH 24/27] Add override keyword --- .../architecture/concrete/boxDomainObject/MeasureBoxDragger.ts | 2 +- .../architecture/concrete/exampleDomainObject/ExampleDragger.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts index 69723791092..f81638ede4a 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureBoxDragger.ts @@ -83,7 +83,7 @@ export class MeasureBoxDragger extends BaseDragger { // OVERRIDES // ================================================== - public get domainObject(): DomainObject { + public override get domainObject(): DomainObject { return this._domainObject; } diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts index e9c0075e081..5b472f34e96 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDragger.ts @@ -33,7 +33,7 @@ export class ExampleDragger extends BaseDragger { // OVERRIDES // ================================================== - public get domainObject(): DomainObject { + public override get domainObject(): DomainObject { return this._domainObject; } From b5bcab46c0e22cd68d2752a447c9122beda8d3c5 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 13:25:03 +0200 Subject: [PATCH 25/27] Remove some unneccesary code --- .../concrete/boxDomainObject/MeasureDomainObject.ts | 2 +- .../concrete/exampleDomainObject/ExampleDomainObject.ts | 2 +- .../src/components/Architecture/ToolButtons.tsx | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts b/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts index 0f5cd6f5434..200efd6cd99 100644 --- a/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts +++ b/react-components/src/architecture/concrete/boxDomainObject/MeasureDomainObject.ts @@ -47,7 +47,7 @@ export abstract class MeasureDomainObject extends VisualDomainObject { } public override getPanelInfoStyle(): PopupStyle { - // bottom = 66 because the measurement toolbar is below + // bottom = 66 because the toolbar is below return new PopupStyle({ bottom: 66, left: 0 }); } diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts index b2c4c6d4f84..f8fc5313ce1 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleDomainObject.ts @@ -72,7 +72,7 @@ export class ExampleDomainObject extends VisualDomainObject { } public override getPanelInfoStyle(): PopupStyle { - // bottom = 66 because the measurement toolbar is below + // bottom = 66 because the toolbar is below return new PopupStyle({ bottom: 66, left: 0 }); } diff --git a/react-components/src/components/Architecture/ToolButtons.tsx b/react-components/src/components/Architecture/ToolButtons.tsx index 0f83386f02a..2b321ea34ef 100644 --- a/react-components/src/components/Architecture/ToolButtons.tsx +++ b/react-components/src/components/Architecture/ToolButtons.tsx @@ -4,24 +4,18 @@ import { type ReactElement } from 'react'; import { NavigationTool } from '../../architecture/base/commands/NavigationTool'; -import { SetTerrainVisibleCommand } from '../../architecture/concrete/terrainDomainObject/SetTerrainVisibleCommand'; import { CreateButton } from './CommandButton'; -import { UpdateTerrainCommand } from '../../architecture/concrete/terrainDomainObject/UpdateTerrainCommand'; import { MeasurementTool } from '../../architecture/concrete/boxDomainObject/MeasurementTool'; import { FitViewCommand } from '../../architecture/base/concreteCommands/FitViewCommand'; import { FlexibleControlsType } from '@cognite/reveal'; import { SetFlexibleControlsTypeCommand } from '../../architecture/base/concreteCommands/SetFlexibleControlsTypeCommand'; import { SetAxisVisibleCommand } from '../../architecture/concrete/axis/SetAxisVisibleCommand'; -import { ExampleTool } from '../../architecture/concrete/exampleDomainObject/ExampleTool'; export class RevealButtons { static FitView = (): ReactElement => CreateButton(new FitViewCommand()); static NavigationTool = (): ReactElement => CreateButton(new NavigationTool()); static SetAxisVisible = (): ReactElement => CreateButton(new SetAxisVisibleCommand()); - static SetTerrainVisible = (): ReactElement => CreateButton(new SetTerrainVisibleCommand()); - static UpdateTerrain = (): ReactElement => CreateButton(new UpdateTerrainCommand()); static Measurement = (): ReactElement => CreateButton(new MeasurementTool()); - static Example = (): ReactElement => CreateButton(new ExampleTool()); static SetFlexibleControlsTypeOrbit = (): ReactElement => CreateButton(new SetFlexibleControlsTypeCommand(FlexibleControlsType.Orbit)); From f6bc2c0927aef8b57a5fc008f9f977ba06e4e261 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming Date: Tue, 28 May 2024 13:57:56 +0200 Subject: [PATCH 26/27] Update ExampleTool.ts --- .../architecture/concrete/exampleDomainObject/ExampleTool.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts b/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts index aafb6327642..52c030902a8 100644 --- a/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts +++ b/react-components/src/architecture/concrete/exampleDomainObject/ExampleTool.ts @@ -100,9 +100,9 @@ export class ExampleTool extends BaseEditTool { const domainObject = new ExampleDomainObject(); domainObject.center.copy(center); + this.deselectAll(); rootDomainObject.addChildInteractive(domainObject); domainObject.setVisibleInteractive(true, renderTarget); - this.deselectAll(); domainObject.setSelectedInteractive(true); } From 7fcbcc6a2eb28108c7e205168dc030410b8999b9 Mon Sep 17 00:00:00 2001 From: Nils Petter Fremming <35219649+nilscognite@users.noreply.github.com> Date: Tue, 28 May 2024 17:10:25 +0200 Subject: [PATCH 27/27] Update react-components/stories/Architecture.stories.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: HÃ¥kon Flatval <70905152+haakonflatval-cognite@users.noreply.github.com> --- react-components/stories/Architecture.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-components/stories/Architecture.stories.tsx b/react-components/stories/Architecture.stories.tsx index 1dd8202e572..524f3b59f73 100644 --- a/react-components/stories/Architecture.stories.tsx +++ b/react-components/stories/Architecture.stories.tsx @@ -76,7 +76,7 @@ const exampleLowQualitySettings: QualitySettings = { export const Main: Story = { args: { - addModelOptions: getAddModelOptionsFromUrl('/cadModel') + addModelOptions: getAddModelOptionsFromUrl('/primitives') }, render: ({ addModelOptions }) => { return (