Skip to content

Commit

Permalink
feat: improve camera preview sub-panel
Browse files Browse the repository at this point in the history
  • Loading branch information
julien-moreau committed Apr 22, 2023
1 parent b6becff commit 7464606
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 87 deletions.
4 changes: 3 additions & 1 deletion src/renderer/editor/components/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,12 @@ export class Preview extends React.Component<IPreviewProps, IPreviewState> {
* Renders the component.
*/
public render(): React.ReactNode {
const activeCamera = this._editor.scene?.activeCameras?.[0] ?? this._editor.scene?.activeCamera;

const cameras = (
<Menu>
{this._editor.scene?.cameras.map((c) => (
<MenuItem key={c.id} id={c.id} text={c.name} icon={this._editor.scene?.activeCamera === c ? <Icon src="check.svg" /> : null} onClick={() => SceneSettings.SetActiveCamera(this._editor, c)} />
<MenuItem key={c.id} id={c.id} text={c.name} icon={activeCamera === c ? <Icon src="check.svg" /> : null} onClick={() => SceneSettings.SetActiveCamera(this._editor, c)} />
))}
</Menu>
);
Expand Down
161 changes: 81 additions & 80 deletions src/renderer/editor/components/preview/camera-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Nullable } from "../../../../shared/types";

import * as React from "react";
import { Button } from "@blueprintjs/core";

import { Camera, Node, Observer, RenderTargetTexture } from "babylonjs";
import { Camera, Engine, Node, Observer, Viewport } from "babylonjs";

import { Editor } from "../../editor";
import { Button } from "@blueprintjs/core";

import { SceneSettings } from "../../scene/settings";

export interface ICameraPreviewProps {
/**
Expand All @@ -27,10 +29,9 @@ export interface ICameraPreviewState {

export class CameraPreview extends React.Component<ICameraPreviewProps, ICameraPreviewState> {
private _selectedNode: Nullable<Camera> = null;
private _canvas: Nullable<HTMLCanvasElement> = null;
private _renderTarget: Nullable<RenderTargetTexture> = null;
private _viewMatrixChangeObserver: Nullable<Observer<Camera>> = null;
private _projectionMatrixChangeObserver: Nullable<Observer<Camera>> = null;
private _resizeObserver: Nullable<Observer<Engine>> = null;

private _divElement: Nullable<HTMLDivElement> = null;

/**
* Constructor.
Expand Down Expand Up @@ -73,15 +74,32 @@ export class CameraPreview extends React.Component<ICameraPreviewProps, ICameraP
right: "10px",
bottom: "10px",
position: "absolute",
pointerEvents: "none",
background: "#00000099",
backdropFilter: "blur(50px)",
display: this.state.visible ? "" : "none",
width: this.state.doubleSize ? "800px" : "400px",
height: this.state.doubleSize ? "400px" : "200px",
}}
ref={(r) => this._divElement = r}
onMouseEnter={() => {
SceneSettings.Camera?.detachControl();

this.props.editor.engine!.inputElement = this._divElement;

if (this._selectedNode) {
this.props.editor.scene!.cameraToUseForPointers = this._selectedNode;
SceneSettings.AttachControl(this.props.editor, this._selectedNode);
}
}}
onMouseLeave={() => {
this._selectedNode?.detachControl();

this.props.editor.engine!.inputElement = null;

if (SceneSettings.Camera) {
this.props.editor.scene!.cameraToUseForPointers = SceneSettings.Camera;
SceneSettings.AttachControl(this.props.editor, SceneSettings.Camera);
}
}}
>
<canvas ref={(r) => this._canvas = r} style={{ width: "100%", height: "100%", transform: "scale(1, -1)", objectFit: "contain", pointerEvents: "none" }} />
</div>
</>
);
Expand All @@ -92,114 +110,97 @@ export class CameraPreview extends React.Component<ICameraPreviewProps, ICameraP
* @param doubled defines wether or not the size of the preview should be doubled.
*/
public setDoubleSized(doubled: boolean): void {
this.setState({ doubleSize: doubled });
this.setState({ doubleSize: doubled }, () => {
if (this._selectedNode) {
const node = this._selectedNode;

if (this._selectedNode) {
const node = this._selectedNode;
this._clear();
this.setSelectedNode(node);
}
});

this._clear();
this.setSelectedNode(node);
}
}

/**
* Sets the selected in the editor. In case of a camera, create the preview.
* @param node defines the reference to the selected node in the editor.
*/
public setSelectedNode(node: Node): void {
if (!(node instanceof Camera)) {
if (!(node instanceof Camera) || node === SceneSettings.Camera) {
return;
}

this.setState({ visible: true });

this._selectedNode = node;

this._renderTarget = new RenderTargetTexture("camera_preview_rtt", this.state.doubleSize ? { width: 800, height: 400 } : { width: 400, height: 200 }, this.props.editor.scene!, true, true);
this._renderTarget.refreshRate = 0;
this._renderTarget.activeCamera = node;
this._renderTarget.useCameraPostProcesses = false;
this._renderTarget.renderList = this.props.editor.scene!.meshes.slice();
const scene = this.props.editor.scene!;
const engine = this.props.editor.engine!;

this.props.editor.scene!.customRenderTargets.push(this._renderTarget);
scene.activeCameras = [];
scene.activeCameras.push(SceneSettings.Camera!);
scene.activeCameras.push(node);

const c = this._canvas?.getContext("2d");
if (!c) {
return;
}
scene.activeCamera = SceneSettings.Camera;
scene.cameraToUseForPointers = SceneSettings.Camera;

let computing = false;
let firstRender = true;
let buffer = new Uint8Array();
SceneSettings.ResetPipelines(this.props.editor);

this._viewMatrixChangeObserver = node.onViewMatrixChangedObservable.add(() => {
this._renderTarget!.refreshRate = 0;
});
this._updatePreviewCameraViewport();

this._projectionMatrixChangeObserver = node.onProjectionMatrixChangedObservable.add(() => {
this._renderTarget!.refreshRate = 0;
this._resizeObserver = engine.onResizeObservable.add(() => {
this._updatePreviewCameraViewport();
});
}

this._renderTarget.onAfterRenderObservable.add(async () => {
if (computing) {
return;
}

computing = true;

const renderWidth = this.props.editor.engine!.getRenderWidth();
const renderHeight = this.props.editor.engine!.getRenderHeight();

const { width, height } = this._renderTarget!.getSize();

if (width !== this.props.editor.engine!.getRenderWidth() || height !== this.props.editor.engine!.getRenderHeight()) {
this._renderTarget!.resize({ width: renderWidth, height: renderHeight });
}
/**
* Updates the viewport of the camera used in the preview sub-panel.
*/
private _updatePreviewCameraViewport(): void {
if (!this._selectedNode) {
return;
}

if (c.canvas.width !== width || c.canvas.height !== height) {
c.canvas.width = width;
c.canvas.height = height;
}
const engine = this.props.editor.engine!;
const canvas = engine.getRenderingCanvas();

const count = width * height * 4;
if (buffer.length !== count) {
buffer = new Uint8Array(count);
}
if (!canvas) {
return;
}

try {
const p = await this._renderTarget!.readPixels(0, 0, buffer, true, true) as Uint8Array;
const a = new ImageData(new Uint8ClampedArray(p), width, height);
const offsetX = 10 / canvas.width;
const offsetY = 10 / canvas.height;

c.scale(1, -1);
c.putImageData(a, 0, 0);
} catch (e) {
// Catch silently.
}
const x = (canvas.width - (this.state.doubleSize ? 800 : 400)) / canvas.width;

computing = false;
const width = (this.state.doubleSize ? 800 : 400) / canvas.width;
const height = (this.state.doubleSize ? 400 : 200) / canvas.height;

if (firstRender) {
firstRender = false;
this._renderTarget!.refreshRate = 0;
}
});
this._selectedNode.viewport = new Viewport(
x - offsetX,
offsetY,
width,
height,
);
}

/**
* Clears all the preview allocated resources.
*/
private _clear(): void {
if (this._renderTarget) {
this._renderTarget?.dispose();
this._renderTarget = null;
}
const scene = this.props.editor.scene!;
const engine = this.props.editor.engine!;

if (this._selectedNode) {
this._selectedNode.onViewMatrixChangedObservable.remove(this._viewMatrixChangeObserver);
this._selectedNode.onProjectionMatrixChangedObservable.remove(this._projectionMatrixChangeObserver);
scene.activeCameras = [];
scene.activeCamera = SceneSettings.Camera;

this._selectedNode.viewport = new Viewport(0, 0, 1, 1);
}

this._viewMatrixChangeObserver = null;
this._projectionMatrixChangeObserver = null;
if (this._resizeObserver) {
engine.onResizeObservable.remove(this._resizeObserver);
this._resizeObserver = null;
}

this._selectedNode = null;
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/editor/scene/picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ export class ScenePicker {
public getObjectUnderPointer(fastCheck: boolean = false): Nullable<Node | SubMesh> {
// Icons
let scene = this.icons._layer.utilityLayerScene;
scene.cameraToUseForPointers = this._editor.scene!.cameraToUseForPointers;

let pick = scene.pick(scene.pointerX, scene.pointerY, undefined, false);

if (pick?.pickedMesh) { return pick.pickedMesh; }

// Scene
scene = this._editor.scene!;
pick = scene.pick(scene.pointerX, scene.pointerY, undefined, fastCheck);
pick = scene.pick(scene.pointerX, scene.pointerY, undefined, fastCheck, scene.cameraToUseForPointers);

if (!pick) { return null; }
if (pick.pickedMesh?.metadata?.isLocked) { return null; }
Expand Down
18 changes: 13 additions & 5 deletions src/renderer/editor/scene/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class SceneSettings {
this.Camera?.dispose();

const camera = new EditorCamera("Editor Camera", this.Camera?.position ?? Vector3.Zero(), editor.scene!);
camera.setTarget(this.Camera?.target ?? Vector3.Zero())
camera.setTarget(this.Camera?.target ?? Vector3.Zero());
camera.attachControl(editor.scene!.getEngine().getRenderingCanvas()!, true);
camera.inertia = 0.5;
camera.angularSensibility = 500;
Expand Down Expand Up @@ -179,7 +179,11 @@ export class SceneSettings {
public static GetSSAORenderingPipeline(editor: Editor): SSAO2RenderingPipeline {
if (this.SSAOPipeline) { return this.SSAOPipeline; }

const ssao = new SSAO2RenderingPipeline("ssao", editor.scene!, { ssaoRatio: 0.5, blurRatio: 0.5 }, this._SSAOPipelineEnabled ? [editor.scene!.activeCamera!] : [], false);
const cameras = editor.scene!.activeCameras?.length
? editor.scene!.activeCameras!
: [SceneSettings.Camera!];

const ssao = new SSAO2RenderingPipeline("ssao", editor.scene!, { ssaoRatio: 0.5, blurRatio: 0.5 }, this._SSAOPipelineEnabled ? cameras : [], false);
ssao.radius = 3.5;
ssao.totalStrength = 1.3;
ssao.expensiveBlur = true;
Expand Down Expand Up @@ -215,7 +219,11 @@ export class SceneSettings {
public static GetDefaultRenderingPipeline(editor: Editor): DefaultRenderingPipeline {
if (this.DefaultPipeline) { return this.DefaultPipeline; }

const pipeline = new DefaultRenderingPipeline("default", true, editor.scene!, this._DefaultPipelineEnabled ? [editor.scene!.activeCamera!] : [], true);
const cameras = editor.scene!.activeCameras?.length
? editor.scene!.activeCameras!
: [SceneSettings.Camera!];

const pipeline = new DefaultRenderingPipeline("default", true, editor.scene!, this._DefaultPipelineEnabled ? cameras : [], true);
// const curve = new ColorCurves();
// curve.globalHue = 200;
// curve.globalDensity = 80;
Expand Down Expand Up @@ -412,7 +420,7 @@ export class SceneSettings {
public static GetMotionBlurPostProcess(editor: Editor): MotionBlurPostProcess {
if (this.MotionBlurPostProcess) { return this.MotionBlurPostProcess; }

this.MotionBlurPostProcess = new MotionBlurPostProcess("motionBlur", editor.scene!, 1.0, editor.scene!.activeCamera, undefined, undefined, undefined, undefined, undefined, false);
this.MotionBlurPostProcess = new MotionBlurPostProcess("motionBlur", editor.scene!, 1.0, this.Camera, undefined, undefined, undefined, undefined, undefined, false);
return this.MotionBlurPostProcess;
}

Expand Down Expand Up @@ -441,7 +449,7 @@ export class SceneSettings {
public static GetScreenSpaceReflectionsPostProcess(editor: Editor): ScreenSpaceReflectionPostProcess {
if (this.ScreenSpaceReflectionsPostProcess) { return this.ScreenSpaceReflectionsPostProcess; }

this.ScreenSpaceReflectionsPostProcess = new ScreenSpaceReflectionPostProcess("ssr", editor.scene!, 1.0, editor.scene!.activeCamera!, undefined, undefined, undefined, undefined, undefined, false);
this.ScreenSpaceReflectionsPostProcess = new ScreenSpaceReflectionPostProcess("ssr", editor.scene!, 1.0, this.Camera, undefined, undefined, undefined, undefined, undefined, false);
return this.ScreenSpaceReflectionsPostProcess;
}

Expand Down

0 comments on commit 7464606

Please sign in to comment.