Skip to content

Commit 04f6614

Browse files
committed
feat: add terrain painter
1 parent e1e684a commit 04f6614

29 files changed

+912
-97
lines changed

css/editor.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ html, body {
3939
padding: 0;
4040
}
4141

42+
span, img {
43+
outline: none !important;
44+
}
45+
4246
/* SCROLLBARS */
4347

4448
::-webkit-scrollbar {
@@ -129,6 +133,10 @@ html, body {
129133
background-color: #222222 !important;
130134
}
131135

136+
.bp3-dark .bp3-button {
137+
outline: none;
138+
}
139+
132140
.bp3-button.bp3-intent-danger {
133141
background-color: #c23030 !important;
134142
}

src/renderer/editor/components/assets-browser/files/handlers/mesh.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,16 @@ export class MeshItemHandler extends AssetsBrowserItemHandler {
271271
*/
272272
private _updateInstantiatedGeometryReferences(mesh: Mesh, withSkeleton: boolean): void {
273273
const metadata = Tools.GetMeshMetadata(mesh);
274+
const matrices = mesh.thinInstanceGetWorldMatrices();
274275

275276
metadata._waitingUpdatedReferences?.geometry?.geometry?.applyToMesh(mesh);
276277

278+
if (matrices.length) {
279+
const array = new Float32Array(matrices.length * 16);
280+
matrices.forEach((m, i) => m.copyToArray(array, i * 16));
281+
mesh.thinInstanceSetBuffer("matrix", array, 16, true);
282+
}
283+
277284
if (withSkeleton) {
278285
mesh.skeleton = metadata._waitingUpdatedReferences?.geometry?.skeleton ?? null;
279286
}

src/renderer/editor/components/console.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Nullable } from "../../../shared/types";
22

33
import * as React from "react";
4-
import { Classes, ButtonGroup, Button } from "@blueprintjs/core";
4+
import { Classes, ButtonGroup, Button, Pre } from "@blueprintjs/core";
55

66
import { Logger, Observable } from "babylonjs";
77

@@ -114,7 +114,6 @@ export class Console extends React.Component<IConsoleProps, IConsoleState> {
114114
/**
115115
* Logs the given message as info.
116116
* @param message defines the message to log as info.
117-
* @param ref defines the optional callback on the
118117
*/
119118
public logInfo(message: string): Promise<ConsoleLog> {
120119
return this._addLog({ type: ConsoleLogType.Info, message });
@@ -132,8 +131,16 @@ export class Console extends React.Component<IConsoleProps, IConsoleState> {
132131
* Logs the given message as error.
133132
* @param message the message to log as error.
134133
*/
135-
public logError(message: string): Promise<ConsoleLog> {
136-
return this._addLog({ type: ConsoleLogType.Error, message });
134+
public async logError(message: string): Promise<ConsoleLog> {
135+
const log = await this._addLog({ type: ConsoleLogType.Error, message });
136+
137+
log.setBody(
138+
<Pre style={{ outlineColor: "red", outlineWidth: "1px", outlineStyle: "double" }}>
139+
{message}
140+
</Pre>
141+
);
142+
143+
return log;
137144
}
138145

139146
/**

src/renderer/editor/components/graph.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,19 @@ export class Graph extends React.Component<IGraphProps, IGraphState> {
8585
public render(): React.ReactNode {
8686
return (
8787
<>
88-
<InputGroup
89-
type="search"
90-
placeholder="Search..."
91-
className={Classes.FILL}
92-
style={{ marginTop: "5px", marginBottom: "5px" }}
93-
onChange={(e) => this._handleSearchChange(e.target.value)}
94-
leftIcon={<BPIcon icon="search" style={{ margin: "12px" }} />}
95-
></InputGroup>
88+
<div style={{ width: "100%", overflow: "hidden" }}>
89+
<InputGroup
90+
type="search"
91+
placeholder="Search..."
92+
className={Classes.FILL}
93+
style={{ marginTop: "5px", marginBottom: "5px" }}
94+
onChange={(e) => this._handleSearchChange(e.target.value)}
95+
leftIcon={<BPIcon icon="search" style={{ margin: "12px" }} />}
96+
></InputGroup>
97+
</div>
9698
<div
9799
style={{
100+
overflow: "auto",
98101
height: "calc(100% - 40px)",
99102
}}
100103
onDrop={(ev) => {

src/renderer/editor/components/graph/context-menu/clone.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Nullable } from "../../../../../shared/types";
33
import * as React from "react";
44
import { MenuItem } from "@blueprintjs/core";
55

6-
import { Node } from "babylonjs";
6+
import { Geometry, Node, VertexData } from "babylonjs";
77

88
import { Editor } from "../../../editor";
99

@@ -55,6 +55,21 @@ function onClick(editor: Editor, object: Node): void {
5555
}
5656

5757
clone.physicsImpostor?.sleep();
58+
59+
debugger;
60+
61+
if (isAbstractMesh(object)) {
62+
object.geometry?.releaseForMesh(clone);
63+
64+
// Copy geometry
65+
const serializedGeometry = object.geometry?.serializeVerticeData();
66+
if (serializedGeometry) {
67+
const geometry = new Geometry(Tools.RandomId(), object.getScene(), undefined, false);
68+
VertexData.ImportVertexData(serializedGeometry, geometry);
69+
70+
geometry?.applyToMesh(clone);
71+
}
72+
}
5873
}
5974

6075
// Metadata

src/renderer/editor/components/graph/label.tsx

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Nullable } from "../../../../shared/types";
22

33
import * as React from "react";
4-
import { Tooltip } from "@blueprintjs/core";
4+
import { Tooltip, Icon as BPIcon, Button } from "@blueprintjs/core";
55

66
import { TransformNode } from "babylonjs";
77

@@ -15,7 +15,7 @@ import { undoRedo } from "../../tools/undo-redo";
1515
import { IDragAndDroppedAssetComponentItem } from "../../assets/abstract-assets";
1616

1717
import { moveNodes } from "./tools/move";
18-
import { getNodeId, isDraggable, isNode } from "./tools/tools";
18+
import { getNodeId, isDraggable, isLight, isMesh, isNode } from "./tools/tools";
1919

2020
export interface IGraphLabelProps {
2121
/**
@@ -85,7 +85,7 @@ export class GraphLabel extends React.Component<IGraphLabelProps, IGraphLabelSta
8585
<Tooltip
8686
usePortal
8787
position="top"
88-
content={<span>{Tools.GetConstructorName(this.props.object)}</span>}
88+
content={this._getTooltipContent()}
8989
>
9090
<span
9191
onDragOver={(e) => this._handleDragEnter(e)}
@@ -101,12 +101,88 @@ export class GraphLabel extends React.Component<IGraphLabelProps, IGraphLabelSta
101101
}}
102102
>
103103
{this.props.object.name}
104+
{this._getThinInstanceBadge()}
104105
</span>
105106
</Tooltip>
106107
</div>
107108
);
108109
}
109110

111+
/**
112+
* In case of a mesh that has thin instances, draw a small icon.
113+
*/
114+
private _getThinInstanceBadge(): React.ReactNode {
115+
if (!isMesh(this.props.object) || !this.props.object.thinInstanceCount) {
116+
return undefined;
117+
}
118+
119+
return (
120+
<>
121+
<Button
122+
small
123+
icon={<BPIcon icon="tree" color={this.props.object.thinInstanceCount > 1 ? "white" : "grey"} />}
124+
style={{
125+
float: "right",
126+
marginTop: "4px",
127+
cursor: "pointer",
128+
marginLeft: "10px",
129+
borderRadius: "45px",
130+
}}
131+
onClick={() => {
132+
const object = this.props.object;
133+
if (!isMesh(object)) {
134+
return;
135+
}
136+
137+
object.metadata ??= {};
138+
139+
if (object.thinInstanceCount > 1) {
140+
this.props.object.metadata ??= {};
141+
this.props.object.metadata.thinInstanceCount = object.thinInstanceCount;
142+
object.thinInstanceCount = 1;
143+
} else if (this.props.object.metadata.thinInstanceCount) {
144+
object.thinInstanceCount = this.props.object.metadata.thinInstanceCount;
145+
}
146+
}}
147+
/>
148+
<span style={{ float: "right", color: "darkgrey" }}>
149+
({this.props.object.metadata?.thinInstanceCount ?? this.props.object.thinInstanceCount})
150+
</span>
151+
</>
152+
);
153+
}
154+
155+
/**
156+
* Returns the final content of the tooltip.
157+
*/
158+
private _getTooltipContent(): JSX.Element {
159+
const infos: React.ReactNode[] = [
160+
<>
161+
{Tools.GetConstructorName(this.props.object)}
162+
</>
163+
];
164+
165+
if (isMesh(this.props.object) && this.props.object.thinInstanceCount) {
166+
infos.push(
167+
<>
168+
Thin Instance Count: {this.props.object.thinInstanceCount}
169+
</>
170+
);
171+
}
172+
173+
if (isLight(this.props.object) && this.props.object.getShadowGenerator()) {
174+
infos.push(
175+
<>
176+
Has Shadows: {Tools.GetConstructorName(this.props.object.getShadowGenerator())}
177+
</>
178+
);
179+
}
180+
181+
return (
182+
<span>{infos.map((i) => <>{i}<br /></>)}</span>
183+
);
184+
}
185+
110186
/**
111187
* Called on the user drops an element on this label.
112188
*/

src/renderer/editor/components/graph/reference-updater.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export class GraphReferenceUpdater {
3535
<MenuItem text="Update Material" disabled={(metadata._waitingUpdatedReferences?.material ?? null) === null} onClick={() => this._updateMaterial()} />
3636
<MenuDivider />
3737
<MenuItem text="Update All" onClick={() => this._updateAll()} />
38+
<MenuDivider />
39+
<MenuItem text="Done" intent="success" onClick={() => this._handleDoneClicked()} />
3840
</Menu>
3941
), {
4042
top: ev.clientY,
@@ -69,4 +71,15 @@ export class GraphReferenceUpdater {
6971

7072
this._editor.graph.refresh();
7173
}
74+
75+
/**
76+
* Called on the user clicks on the done button.
77+
*/
78+
private _handleDoneClicked(): void {
79+
if (this._mesh.metadata?._waitingUpdatedReferences) {
80+
delete this._mesh.metadata._waitingUpdatedReferences;
81+
}
82+
83+
this._editor.graph.refresh();
84+
}
7285
}

src/renderer/editor/components/inspectors/cameras/arc-rotate-camera-inspector.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export class ArcRotateCameraInspector extends CameraInspector<ArcRotateCamera, I
3434
<InspectorVector3 object={this.selectedObject} property="panningAxis" label="Panning Axis" step={0.01} />
3535
</InspectorSection>
3636

37+
{this.getCameraInspector()}
38+
3739
<InspectorSection title="Arc Rotate Camera">
3840
<InspectorBoolean object={this.selectedObject} property="noRotationConstraint" label="No Rotation Constraint" />
3941
<InspectorNumber object={this.selectedObject} property="wheelPrecision" label="Wheel Precision" min={0} step={0.01} />

src/renderer/editor/components/inspectors/cameras/camera-inspector.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class CameraInspector<T extends Camera, S extends INodeInspectorState> ex
1414
* Constructor.
1515
* @param props defines the component's props.
1616
*/
17-
public constructor(props: IObjectInspectorProps) {
17+
public constructor(props: IObjectInspectorProps) {
1818
super(props);
1919
}
2020

@@ -25,16 +25,23 @@ export class CameraInspector<T extends Camera, S extends INodeInspectorState> ex
2525
return (
2626
<>
2727
{super.renderContent()}
28-
29-
<InspectorSection title="Camera">
30-
<InspectorNumber object={this.selectedObject} property="fov" label="FOV" step={0.01} />
31-
<InspectorNumber object={this.selectedObject} property="minZ" label="Min Z" step={0.01} />
32-
<InspectorNumber object={this.selectedObject} property="maxZ" label="Max Z" step={0.01} />
33-
<InspectorNumber object={this.selectedObject} property="inertia" label="Inertia" step={0.01} />
34-
</InspectorSection>
3528
</>
3629
);
3730
}
31+
32+
/**
33+
* Returns the inspector used to edit the camera's properties.
34+
*/
35+
protected getCameraInspector(): React.ReactNode {
36+
return (
37+
<InspectorSection title="Camera">
38+
<InspectorNumber object={this.selectedObject} property="fov" label="FOV" step={0.01} />
39+
<InspectorNumber object={this.selectedObject} property="minZ" label="Min Z" step={0.01} />
40+
<InspectorNumber object={this.selectedObject} property="maxZ" label="Max Z" step={0.01} />
41+
<InspectorNumber object={this.selectedObject} property="inertia" label="Inertia" step={0.01} />
42+
</InspectorSection>
43+
);
44+
}
3845
}
3946

4047
Inspector.RegisterObjectInspector({

src/renderer/editor/components/inspectors/cameras/free-camera-inspector.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,19 @@ export class FreeCameraInspector extends CameraInspector<FreeCamera | UniversalC
2525
<>
2626
{super.renderContent()}
2727

28+
<InspectorSection title="Transforms">
29+
<InspectorVector3 object={this.selectedObject} property="position" label="Position" step={0.01} />
30+
<InspectorVector3 object={this.selectedObject} property="rotation" label="Rotation" step={0.01} />
31+
</InspectorSection>
32+
33+
{this.getCameraInspector()}
34+
2835
<InspectorSection title="Free Camera">
2936
<InspectorNumber object={this.selectedObject} property="speed" label="Speed" min={0} step={0.01} />
3037
<InspectorNumber object={this.selectedObject} property="angularSensibility" label="Angular Sensibility" min={0} step={0.01} />
3138
<InspectorBoolean object={this.selectedObject} property="noRotationConstraint" label="No Rotation Constraint" />
3239
</InspectorSection>
3340

34-
<InspectorSection title="Transforms">
35-
<InspectorVector3 object={this.selectedObject} property="position" label="Position" step={0.01} />
36-
<InspectorVector3 object={this.selectedObject} property="rotation" label="Rotation" step={0.01} />
37-
</InspectorSection>
38-
3941
<InspectorSection title="Collisions">
4042
<InspectorBoolean object={this.selectedObject} property="checkCollisions" label="Check Collisions" />
4143
<InspectorBoolean object={this.selectedObject} property="applyGravity" label="Apply Gravity" />
@@ -70,7 +72,7 @@ export class FreeCameraInspector extends CameraInspector<FreeCamera | UniversalC
7072
undoRedo.push({
7173
description: `Changed key for camera "${property}" from "${value}" to "${c}"`,
7274
common: () => this.forceUpdate(),
73-
undo:() => this.selectedObject[property] = [value],
75+
undo: () => this.selectedObject[property] = [value],
7476
redo: () => this.selectedObject[property] = [c],
7577
});
7678
}} />

src/renderer/editor/components/inspectors/cameras/target-camera-inspector.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class TargetCameraInspector extends CameraInspector<TargetCamera, INodeIn
2525
<InspectorVector3 object={this.selectedObject} property="position" label="Position" step={0.01} />
2626
</InspectorSection>
2727

28+
{this.getCameraInspector()}
2829
{this.getAnimationRangeInspector()}
2930
</>
3031
);

src/renderer/editor/components/inspectors/lights/directional-light-inspector.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export class DirectionalLightInspector extends LightInspector<DirectionalLight,
2525
<InspectorVector3 object={this.selectedObject} property="direction" label="Direction" step={0.01} />
2626
</InspectorSection>
2727

28+
{this.getColorsInspector()}
29+
{this.getLightInspector()}
30+
2831
{this.getAnimationRangeInspector()}
2932
{this.getExcludedMeshesInspector()}
3033
</>

src/renderer/editor/components/inspectors/lights/hemispheric-light-inspector.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export class HemisphericLightInspector extends LightInspector<HemisphericLight,
2929
<InspectorColorPicker object={this.selectedObject} property="groundColor" label="Hex" />
3030
</InspectorSection>
3131

32+
{this.getColorsInspector()}
33+
{this.getLightInspector()}
34+
3235
{this.getAnimationRangeInspector()}
3336
{this.getExcludedMeshesInspector()}
3437
</>

0 commit comments

Comments
 (0)