You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
6.3 KiB
227 lines
6.3 KiB
2 months ago
|
[Tool]
|
||
|
public partial class Terrain
|
||
|
: StaticBody3D
|
||
|
{
|
||
|
[Export] public Vector2I Size { get; set; } = new(64, 64);
|
||
|
[Export] public float TileSize { get; set; } = 2.0f;
|
||
|
|
||
|
[Export] public Godot.Collections.Array<Texture2D> Textures { get; set; }
|
||
|
|
||
|
public bool IsEditing { get; } = Engine.IsEditorHint();
|
||
|
|
||
|
Vector2I? _gridHover = null;
|
||
|
Vector2I? _gridSelectionStart = null;
|
||
|
Vector2I? _gridSelectionEnd = null;
|
||
|
bool _isSelecting = false;
|
||
|
|
||
|
Material _editToolMaterial;
|
||
|
Material _terrainMaterial;
|
||
|
public override void _Ready()
|
||
|
{
|
||
|
_editToolMaterial = new StandardMaterial3D {
|
||
|
VertexColorUseAsAlbedo = true,
|
||
|
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
|
||
|
DepthDrawMode = BaseMaterial3D.DepthDrawModeEnum.Disabled,
|
||
|
NoDepthTest = true,
|
||
|
};
|
||
|
|
||
|
_terrainMaterial = new StandardMaterial3D {
|
||
|
AlbedoTexture = Textures?.FirstOrDefault(),
|
||
|
};
|
||
|
|
||
|
UpdateMeshAndShape();
|
||
|
}
|
||
|
|
||
|
|
||
|
Vector2I ToGridPos(Vector3 localPos)
|
||
|
=> new(RoundToInt(localPos.X / TileSize + Size.X / 2.0f),
|
||
|
RoundToInt(localPos.Z / TileSize + Size.Y / 2.0f));
|
||
|
|
||
|
float GetCornerHeight(Vector2I gridPos, Corner corner)
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
GridCorners GetGridCorners(Vector2I gridPos)
|
||
|
{
|
||
|
var halfSize = TileSize / 2;
|
||
|
var vx = (gridPos.X - Size.X / 2.0f) * TileSize;
|
||
|
var vz = (gridPos.Y - Size.Y / 2.0f) * TileSize;
|
||
|
|
||
|
return new() {
|
||
|
TopLeft = new(vx - halfSize, GetCornerHeight(gridPos, Corner.TopLeft ), vz - halfSize),
|
||
|
TopRight = new(vx + halfSize, GetCornerHeight(gridPos, Corner.TopRight ), vz - halfSize),
|
||
|
BottomLeft = new(vx - halfSize, GetCornerHeight(gridPos, Corner.BottomLeft ), vz + halfSize),
|
||
|
BottomRight = new(vx + halfSize, GetCornerHeight(gridPos, Corner.BottomRight), vz + halfSize),
|
||
|
};
|
||
|
}
|
||
|
|
||
|
|
||
|
void UpdateMeshAndShape()
|
||
|
{
|
||
|
var mesh = GetOrCreateMesh("MeshInstance");
|
||
|
var shape = GetOrCreateShape("CollisionShape");
|
||
|
|
||
|
mesh.ClearSurfaces();
|
||
|
mesh.SurfaceBegin(Mesh.PrimitiveType.Triangles);
|
||
|
var points = new List<Vector3>();
|
||
|
|
||
|
void AddPoint(Vector3 pos, Vector2 uv) {
|
||
|
mesh.SurfaceSetUV(uv);
|
||
|
mesh.SurfaceAddVertex(pos);
|
||
|
points.Add(pos);
|
||
|
}
|
||
|
|
||
|
void AddTriangle(Vector3 v1, Vector2 uv1,
|
||
|
Vector3 v2, Vector2 uv2,
|
||
|
Vector3 v3, Vector2 uv3) {
|
||
|
var dir = (v3 - v1).Cross(v2 - v1);
|
||
|
mesh.SurfaceSetNormal(dir.Normalized());
|
||
|
AddPoint(v1, uv1);
|
||
|
AddPoint(v2, uv2);
|
||
|
AddPoint(v3, uv3);
|
||
|
}
|
||
|
|
||
|
for (var x = 0; x < Size.X; x++) {
|
||
|
for (var z = 0; z < Size.Y; z++) {
|
||
|
var corners = GetGridCorners(new(x, z));
|
||
|
AddTriangle(corners.TopLeft , new(0.0f, 0.0f),
|
||
|
corners.TopRight , new(1.0f, 0.0f),
|
||
|
corners.BottomLeft , new(0.0f, 1.0f));
|
||
|
AddTriangle(corners.TopRight , new(1.0f, 0.0f),
|
||
|
corners.BottomRight, new(1.0f, 1.0f),
|
||
|
corners.BottomLeft , new(0.0f, 1.0f));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mesh.SurfaceEnd();
|
||
|
mesh.SurfaceSetMaterial(0, _terrainMaterial);
|
||
|
|
||
|
shape.Data = [.. points];
|
||
|
}
|
||
|
|
||
|
|
||
|
public override void _MouseEnter()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public override void _MouseExit()
|
||
|
{
|
||
|
_gridHover = null;
|
||
|
}
|
||
|
|
||
|
public override void _InputEvent(Camera3D camera, InputEvent @event, Vector3 position, Vector3 normal, int shapeIdx)
|
||
|
{
|
||
|
if (!IsEditing) return;
|
||
|
|
||
|
var localPos = ToLocal(position);
|
||
|
var gridPos = ToGridPos(localPos);
|
||
|
|
||
|
if (@event is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: var pressed }) {
|
||
|
if (pressed) _gridSelectionStart = _gridSelectionEnd = gridPos;
|
||
|
_isSelecting = pressed;
|
||
|
camera.GetViewport().SetInputAsHandled();
|
||
|
}
|
||
|
|
||
|
if (@event is InputEventMouseMotion) {
|
||
|
_gridHover = gridPos;
|
||
|
if (_isSelecting) _gridSelectionEnd = gridPos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void _Process(double delta)
|
||
|
{
|
||
|
if (!IsEditing) _gridHover = _gridSelectionStart = _gridSelectionEnd = null;
|
||
|
|
||
|
if ((_gridHover != null) || (_gridSelectionStart != null)) {
|
||
|
var mesh = GetOrCreateMesh("EditToolMesh");
|
||
|
mesh.ClearSurfaces();
|
||
|
mesh.SurfaceBegin(Mesh.PrimitiveType.Lines);
|
||
|
|
||
|
|
||
|
void AddLine(Vector3 start, Vector3 end) {
|
||
|
mesh.SurfaceAddVertex(start);
|
||
|
mesh.SurfaceAddVertex(end);
|
||
|
}
|
||
|
|
||
|
void AddQuad(Vector3 topLeft, Vector3 topRight,
|
||
|
Vector3 bottomLeft, Vector3 bottomRight) {
|
||
|
AddLine(topLeft , topRight );
|
||
|
AddLine(topRight , bottomRight);
|
||
|
AddLine(bottomRight, bottomLeft );
|
||
|
AddLine(bottomLeft , topLeft );
|
||
|
}
|
||
|
|
||
|
if (_gridHover is Vector2I hover) {
|
||
|
var corners = GetGridCorners(hover);
|
||
|
var margin = 0.1f;
|
||
|
mesh.SurfaceSetColor(Colors.Black);
|
||
|
AddQuad(
|
||
|
corners.TopLeft + new Vector3(-margin, 0, -margin),
|
||
|
corners.TopRight + new Vector3(+margin, 0, -margin),
|
||
|
corners.BottomLeft + new Vector3(-margin, 0, +margin),
|
||
|
corners.BottomRight + new Vector3(+margin, 0, +margin)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if (_gridSelectionStart is Vector2I start) {
|
||
|
var end = _gridSelectionEnd ?? start;
|
||
|
(start, end) = (new(Min(start.X, end.X), Min(start.Y, end.Y)),
|
||
|
new(Max(start.X, end.X), Max(start.Y, end.Y)));
|
||
|
mesh.SurfaceSetColor(Colors.Blue);
|
||
|
for (var x = start.X; x <= end.X; x++)
|
||
|
for (var y = start.Y; y <= end.Y; y++) {
|
||
|
var corners = GetGridCorners(new(x, y));
|
||
|
AddQuad(corners.TopLeft, corners.TopRight, corners.BottomLeft, corners.BottomRight);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mesh.SurfaceEnd();
|
||
|
mesh.SurfaceSetMaterial(0, _editToolMaterial);
|
||
|
} else {
|
||
|
var meshInstance = (MeshInstance3D)GetNodeOrNull("EditToolMesh");
|
||
|
var mesh = (ImmediateMesh)meshInstance?.Mesh;
|
||
|
mesh?.ClearSurfaces();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
ImmediateMesh GetOrCreateMesh(string name)
|
||
|
{
|
||
|
var meshInstance = (MeshInstance3D)GetNodeOrNull(name);
|
||
|
if (meshInstance == null) {
|
||
|
meshInstance = new() { Name = name, Mesh = new ImmediateMesh() };
|
||
|
AddChild(meshInstance);
|
||
|
meshInstance.Owner = this;
|
||
|
}
|
||
|
return (ImmediateMesh)meshInstance.Mesh;
|
||
|
}
|
||
|
|
||
|
ConcavePolygonShape3D GetOrCreateShape(string name)
|
||
|
{
|
||
|
var collisionShape = (CollisionShape3D)GetNodeOrNull(name);
|
||
|
if (collisionShape == null) {
|
||
|
collisionShape = new() { Name = name, Shape = new ConcavePolygonShape3D() };
|
||
|
AddChild(collisionShape);
|
||
|
collisionShape.Owner = this;
|
||
|
}
|
||
|
return (ConcavePolygonShape3D)collisionShape.Shape;
|
||
|
}
|
||
|
|
||
|
enum Corner
|
||
|
{
|
||
|
TopLeft,
|
||
|
TopRight,
|
||
|
BottomLeft,
|
||
|
BottomRight,
|
||
|
}
|
||
|
|
||
|
readonly struct GridCorners
|
||
|
{
|
||
|
public Vector3 TopLeft { get; init; }
|
||
|
public Vector3 TopRight { get; init; }
|
||
|
public Vector3 BottomLeft { get; init; }
|
||
|
public Vector3 BottomRight { get; init; }
|
||
|
}
|
||
|
}
|