Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/ThreeEditor/Simulation/Figures/FigureManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Signal } from 'signals';
import * as THREE from 'three';
import { Object3D } from 'three';

import { SimulationPropertiesType } from '../../../types/SimulationProperties';
import { YaptideEditor } from '../../js/YaptideEditor';
Expand Down Expand Up @@ -72,6 +73,8 @@ export class FigureManager

private editor: YaptideEditor;
private signals: {
figureAdded: Signal;
figureRemoved: Signal;
sceneGraphChanged: Signal;
};

Expand Down Expand Up @@ -99,13 +102,15 @@ export class FigureManager
addFigure(figure: BasicFigure<THREE.BufferGeometry>) {
this.figureContainer.add(figure);
this.editor.select(figure);
this.signals.sceneGraphChanged.dispatch();
this.editor.signals.figureAdded.dispatch(figure);
this.editor.signals.sceneGraphChanged.dispatch(figure);
}

removeFigure(figure: BasicFigure<THREE.BufferGeometry>) {
this.figureContainer.remove(figure);
this.editor.deselect();
this.signals.sceneGraphChanged.dispatch();
this.signals.figureRemoved.dispatch(figure);
this.signals.sceneGraphChanged.dispatch(figure);
}

getFigureByUuid(uuid: string) {
Expand Down Expand Up @@ -168,6 +173,18 @@ export class FigureManager
this.name = name;
this.figureContainer.fromSerialized(figures);

const dispatchFigureAdded = (object: Object3D) => {
this.signals.figureAdded.dispatch(object);

for (const child of object.children) {
dispatchFigureAdded(child);
}
};

for (const figure of this.figures) {
dispatchFigureAdded(figure);
}

return this;
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/ThreeEditor/Simulation/Zones/BooleanZone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class BooleanZone extends SimulationZone {
geometryChanged: Signal<THREE.Object3D>;
sceneGraphChanged: Signal;
zoneGeometryChanged: Signal<BooleanZone>;
zoneAdded: Signal<BooleanZone>;
zoneChanged: Signal<BooleanZone>;
zoneEmpty: Signal<BooleanZone>;
};
Expand Down Expand Up @@ -204,6 +205,9 @@ export class BooleanZone extends SimulationZone {

this.subscribedObjects = new CounterMap().fromSerialized(objectsJSON);

// Let the clipped view viewports know that the zone exists
this.signals.zoneAdded.dispatch(this);

return this;
}

Expand Down
2 changes: 2 additions & 0 deletions src/ThreeEditor/js/YaptideEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const JSON_VERSION = `0.12`;
export function YaptideEditor(container) {
this.signals = {
editorCleared: new Signal(),
simulatorChanged: new Signal(),

savingStarted: new Signal(),
savingFinished: new Signal(),
Expand Down Expand Up @@ -652,6 +653,7 @@ YaptideEditor.prototype = {
this.config.setKey('project/title', project.title ?? '');
this.config.setKey('project/description', project.description ?? '');
this.contextManager.currentSimulator = project.simulator ?? SimulatorType.COMMON;
this.signals.simulatorChanged.dispatch();
} else
console.warn('Project info was not found in JSON data. Skipping part 1 out of 11');

Expand Down
41 changes: 41 additions & 0 deletions src/ThreeEditor/js/commands/RemoveFigureCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Object3D } from 'three';

import { RemoveObjectCommand } from './RemoveObjectCommand';

class RemoveFigureCommand extends RemoveObjectCommand {
execute() {
super.execute();

// Dispatch remove commands from bottom to top so each time the innermost object is called
const dispatchRemove = (o: Object3D) => {
if (o.children.length > 0) {
for (const child of o.children) {
dispatchRemove(child);
}
}

this.editor.signals.figureRemoved.dispatch(o);
};

dispatchRemove(this.object);
}

undo() {
super.undo();

// Dispatch re-adding from top to bottom, so children are called after parent has been called
const dispatchReAdd = (o: Object3D) => {
this.editor.signals.figureAdded.dispatch(o);

if (o.children.length > 0) {
for (const child of o.children) {
dispatchReAdd(child);
}
}
};

dispatchReAdd(this.object);
}
}

export { RemoveFigureCommand };
66 changes: 57 additions & 9 deletions src/ThreeEditor/js/viewport/Viewport.ClippedViewCSG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as THREE from 'three';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import { debounce } from 'throttle-debounce';

import { SimulatorType } from '../../../types/RequestTypes';
import { CSG } from '../../CSG/CSG';
import { YaptideEditor } from '../YaptideEditor';
import { Viewport } from './Viewport';
Expand All @@ -18,6 +19,7 @@ export interface ViewportClippedView {
scene: THREE.Scene;
gui: GUI;
planeHelper: THREE.PlaneHelper;
detachSignals: () => void;
reset: () => void;
configurationToJson: () => ClippedViewConfigurationJson;
fromConfigurationJson: (config: ClippedViewConfigurationJson) => void;
Expand Down Expand Up @@ -155,6 +157,22 @@ export function ViewportClippedViewCSG<
editor.signals.sceneGraphChanged.dispatch();
}

function estimateRenderOrder(object3D: T) {
// estimate render order for rendering of slices of Geant4 geometry that overlap
// figures are nested within each other and the innermost slice should be rendered on top
// by having the highest renderOrder value
// it is sufficient to have the value equal to the depth in the hierarchy
let parent = object3D.parent;
let renderOrder = 1;

while (parent) {
parent = parent.parent;
renderOrder++;
}

return renderOrder;
}

function updateMeshIntersection(object3D: T) {
const crossSectionObject = clippedObjects.getObjectByName(object3D.uuid);

Expand All @@ -174,6 +192,12 @@ export function ViewportClippedViewCSG<
});

crossSectionMesh = CSG.toMesh(objectMesh, object3D.matrix, crossSectionMaterial) as T;
object3D.getWorldPosition(crossSectionMesh.position);

if (editor.contextManager.currentSimulator === SimulatorType.GEANT4) {
crossSectionMaterial.depthTest = false;
crossSectionMesh.renderOrder = estimateRenderOrder(object3D);
}
} else {
crossSectionMesh = new THREE.Mesh() as T;
}
Expand All @@ -183,34 +207,58 @@ export function ViewportClippedViewCSG<
crossSectionMesh.visible = object3D.visible;

clippedObjects.add(crossSectionMesh);

editor.signals.sceneGraphChanged.dispatch();
}

signalGeometryChanged.add((object3D: T) => {
updateMeshIntersection(object3D);
});
function updateMeshIntersectionIfExists(object3D: T) {
if (clippedObjects.getObjectByName(object3D.uuid) === undefined) {
// Don't update objects that do not exist
// This is needed for Geant4 which uses objectChanged signal here that also dispatches detector geometry,
// which we don't want here
return;
}

signalGeometryAdded.add((object3D: T) => {
updateMeshIntersection(object3D);
});
}

signalGeometryChanged.add(updateMeshIntersectionIfExists);
signalGeometryAdded.add(updateMeshIntersection);

signalGeometryRemoved.add((object3D: T) => {
const removeObjectFromMeshIntersection = (object3D: T) => {
const crossSectionObject = clippedObjects.getObjectByName(object3D.uuid);

if (crossSectionObject) clippedObjects.remove(crossSectionObject);
});

editor.signals.objectChanged.add((object3D: T) => {
editor.signals.sceneGraphChanged.dispatch();
};

signalGeometryRemoved.add(removeObjectFromMeshIntersection);

const updateObjectInMeshIntersection = (object3D: T) => {
const crossSectionMesh = clippedObjects.getObjectByName(object3D.uuid) as T;

if (crossSectionMesh) {
crossSectionMesh.material.color.copy(object3D.material.color);
crossSectionMesh.material.needsUpdate = true;
crossSectionMesh.visible = object3D.visible;
}
});

editor.signals.sceneGraphChanged.dispatch();
};

editor.signals.objectChanged.add(updateObjectInMeshIntersection);

this.detachSignals = () => {
signalGeometryChanged.remove(updateMeshIntersectionIfExists);
signalGeometryAdded.remove(updateMeshIntersection);
signalGeometryRemoved.remove(removeObjectFromMeshIntersection);
editor.signals.objectChanged.remove(updateObjectInMeshIntersection);
};

this.reset = () => {
clippedObjects.clear();
editor.signals.sceneGraphChanged.dispatch();
};

this.configurationToJson = (): ClippedViewConfigurationJson => {
Expand Down
66 changes: 48 additions & 18 deletions src/ThreeEditor/js/viewport/Viewport.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as THREE from 'three';
import { TransformControls } from 'three/examples/jsm/controls/TransformControls';

import { SimulatorType } from '../../../types/RequestTypes';
import {
SetPositionCommand,
SetRotationCommand,
Expand Down Expand Up @@ -116,24 +117,53 @@ export function Viewport(

let viewClipPlane = null;

if (clipPlane) {
viewClipPlane = new ViewportClippedViewCSG(
name,
editor,
this,
planeHelpers,
zoneManager.zoneContainer.children,
signals.zoneGeometryChanged,
signals.zoneAdded,
signals.zoneRemoved,
wrapperDiv.dom,
{
clipPlane,
planeHelperColor,
planePosLabel
const setupViewClipPlane = () => {
if (viewClipPlane) {
viewClipPlane.detachSignals();
viewClipPlane = null;
}

if (clipPlane) {
if (editor.contextManager.currentSimulator !== SimulatorType.GEANT4) {
viewClipPlane = new ViewportClippedViewCSG(
name,
editor,
this,
planeHelpers,
zoneManager.zoneContainer.children,
signals.zoneGeometryChanged,
signals.zoneAdded,
signals.zoneRemoved,
wrapperDiv.dom,
{
clipPlane,
planeHelperColor,
planePosLabel
}
);
} else {
viewClipPlane = new ViewportClippedViewCSG(
name,
editor,
this,
planeHelpers,
zoneManager.zoneContainer.children,
signals.objectChanged,
signals.figureAdded,
signals.figureRemoved,
wrapperDiv.dom,
{
clipPlane,
planeHelperColor,
planePosLabel
}
);
}
);
}
}
};

setupViewClipPlane();
editor.signals.simulatorChanged.add(setupViewClipPlane);

let cachedRenderer = null;

Expand All @@ -154,7 +184,7 @@ export function Viewport(

renderer.clear();

if (clipPlane) renderer.render(viewClipPlane.scene, camera);
if (clipPlane && viewClipPlane) renderer.render(viewClipPlane.scene, camera);
else {
renderer.render(detectorManager, camera);

Expand Down
1 change: 1 addition & 0 deletions src/services/StoreService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const Store = ({ children }: GenericContextProviderProps) => {
}

editor.contextManager.currentSimulator = simulator;
editor.signals.simulatorChanged.dispatch();

if (changingToOrFromGeant4) {
editor.clear();
Expand Down
6 changes: 5 additions & 1 deletion src/util/hooks/useKeyboardEditorControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { Object3D } from 'three';

import { RemoveDetectGeometryCommand } from '../../ThreeEditor/js/commands/RemoveDetectGeometryCommand';
import { RemoveDifferentialModifierCommand } from '../../ThreeEditor/js/commands/RemoveDifferentialModifierCommand';
import { RemoveFigureCommand } from '../../ThreeEditor/js/commands/RemoveFigureCommand';
import { RemoveFilterCommand } from '../../ThreeEditor/js/commands/RemoveFilterCommand';
import { RemoveObjectCommand } from '../../ThreeEditor/js/commands/RemoveObjectCommand';
import { RemoveQuantityCommand } from '../../ThreeEditor/js/commands/RemoveQuantityCommand';
import { RemoveZoneCommand } from '../../ThreeEditor/js/commands/RemoveZoneCommand';
import { SetFilterRuleCommand } from '../../ThreeEditor/js/commands/SetFilterRuleCommand';
import { YaptideEditor } from '../../ThreeEditor/js/YaptideEditor';
import { isDetector } from '../../ThreeEditor/Simulation/Detectors/Detector';
import { isBasicFigure } from '../../ThreeEditor/Simulation/Figures/BasicFigures';
import { isBeam } from '../../ThreeEditor/Simulation/Physics/Beam';
import { isCustomFilter } from '../../ThreeEditor/Simulation/Scoring/CustomFilter';
import { isOutput } from '../../ThreeEditor/Simulation/Scoring/ScoringOutput';
Expand Down Expand Up @@ -43,7 +45,9 @@ export const hasVisibleChildren = (object: Object3D) => {
};

export const getRemoveCommand = (editor: YaptideEditor, object: Object3D) => {
if (isDetector(object)) {
if (isBasicFigure(object)) {
return new RemoveFigureCommand(editor, object);
} else if (isDetector(object)) {
return new RemoveDetectGeometryCommand(editor, object);
} else if (isBooleanZone(object)) {
return new RemoveZoneCommand(editor, object);
Expand Down
Loading