Skip to content
Open
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
3 changes: 3 additions & 0 deletions TombEditor/Controls/Panel3D/Panel3D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ protected override void Dispose(bool disposing)
_rasterizerWireframe?.Dispose();
_objectHeightLineVertexBuffer?.Dispose();
_flybyPathVertexBuffer?.Dispose();
_flybyPyramidSolidVertexBuffer?.Dispose();
_flybyPyramidAccentVertexBuffer?.Dispose();
_flybyPyramidWireVertexBuffer?.Dispose();
_gizmo?.Dispose();
_sphere?.Dispose();
_cone?.Dispose();
Expand Down
130 changes: 61 additions & 69 deletions TombEditor/Controls/Panel3D/Panel3DDraw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1349,94 +1349,86 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis
if (_editor.CameraPreviewMode != CameraPreviewType.None)
return;

// Draw extra flyby cones (hidden during flyby preview)
// Draw extra flyby camera pyramids (hidden during flyby preview).
DrawFlybyCameraPyramids(effect, roomsWhoseObjectsToDraw);
}

_legacyDevice.SetVertexBuffer(_cone.VertexBuffer);
_legacyDevice.SetVertexInputLayout(_cone.InputLayout);
_legacyDevice.SetIndexBuffer(_cone.IndexBuffer, _cone.IsIndex32Bits);
private void DrawFlybyCameraPyramids(Effect effect, Room[] roomsWhoseObjectsToDraw)
{
_legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone);
_legacyDevice.SetBlendState(_legacyDevice.BlendStates.AlphaBlend);

bool wireframe = false;
foreach (Room room in roomsWhoseObjectsToDraw)
foreach (var instance in room.Objects.OfType<FlybyCameraInstance>())
{
var color = MathC.GetRandomColorByIndex(instance.Sequence, 32, 0.7f);
Matrix4x4 model;

if (_highlightedObjects.Contains(instance))
color = _editor.Configuration.UI_ColorScheme.ColorSelection;

for (int pass = 0; pass < 2; pass++)
{
if (_editor.SelectedObject == instance)
{
float coneAngle = (float)Math.Atan2(512, 1024);
float cutoffScaleH = 1;
float cutoffScaleW = instance.Fov * (float)(Math.PI / 360) / coneAngle * cutoffScaleH;
float distance = Vector3.Distance(instance.WorldPosition, Camera.GetPosition());
if (distance < (_coneRadius * 0.5f))
color.W *= distance / (_coneRadius * 0.5f);

if (pass == 0)
{
// Ordinary cone
model = Matrix4x4.CreateScale(cutoffScaleW, cutoffScaleW, cutoffScaleH) * instance.ObjectMatrix;
}
else
{
// Roll pointer
var step = 1 / _coneRadius;
var scale = _littleCubeRadius * 2;
var pScale = _littleCubeRadius / 5;
var vOffset = -cutoffScaleW / 2 * _coneRadius - scale;
var hOffset = cutoffScaleH * _coneRadius;

model = Matrix4x4.CreateScale(step * pScale, step * pScale, step * scale) *
Matrix4x4.CreateTranslation(new Vector3(0, hOffset, vOffset)) *
Matrix4x4.CreateRotationX((float)(Math.PI / 2)) *
instance.ObjectMatrix;
}
if (color.W <= 0.0f)
continue;

if (!wireframe)
{
_legacyDevice.SetRasterizerState(_rasterizerWireframe);
wireframe = true;
}
}
else
{
// Don't do second pass for non-selected flybys
if (pass == 1)
break;
bool selected = _editor.SelectedObject == instance;
float pyramidLength = selected ? _coneRadius * _flybyPyramidSelectedLengthScale : _flybyPyramidInactiveLength;
float pyramidHalfHeight = selected ? GetFlybyPyramidDefaultHalfSize(pyramidLength) : _flybyPyramidInactiveBaseHeight * 0.5f;
float pyramidHalfWidth = GetFlybyPyramidHalfWidth(instance.Fov, pyramidLength, pyramidHalfHeight, selected);
var scaleMatrix = Matrix4x4.CreateScale(pyramidHalfWidth, pyramidHalfHeight, pyramidLength);
var transform = selected ? instance.RotationPositionMatrix : BuildFlybyMarkerMatrix(instance, pyramidLength);
var model = scaleMatrix * transform;

if (!selected)
DrawFlybyPyramidBuffer(effect, _flybyPyramidSolidVertexBuffer, PrimitiveType.TriangleList, model, color);

DrawFlybyPyramidBuffer(effect,
selected ? _flybyPyramidWireVertexBuffer : _flybyPyramidAccentVertexBuffer,
PrimitiveType.LineList,
model,
color);
}

// Push unselected cone further away in sprite mode for neatness
model = _editor.Configuration.Rendering3D_UseSpritesForServiceObjects
? Matrix4x4.CreateTranslation(new Vector3(0, 0, -_coneRadius * 0.5f))
: Matrix4x4.Identity;
_legacyDevice.SetBlendState(_legacyDevice.BlendStates.Opaque);
_legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullBack);
}

model *= Matrix4x4.CreateTranslation(new Vector3(0, 0, -_coneRadius * 1.2f)) *
Matrix4x4.CreateRotationY((float)Math.PI) *
Matrix4x4.CreateScale(1 / _coneRadius * _littleCubeRadius * 2.0f) *
instance.ObjectMatrix;
private void DrawFlybyPyramidBuffer(Effect effect, Buffer<SolidVertex> buffer, PrimitiveType primitiveType, Matrix4x4 model, Vector4 color)
{
_legacyDevice.SetVertexBuffer(buffer);
_legacyDevice.SetVertexInputLayout(VertexInputLayout.FromBuffer(0, buffer));
effect.Parameters["ModelViewProjection"].SetValue((model * _viewProjection).ToSharpDX());
effect.Parameters["Color"].SetValue(color);
effect.CurrentTechnique.Passes[0].Apply();
_legacyDevice.Draw(primitiveType, buffer.ElementCount);
}

if (wireframe)
{
_legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone);
wireframe = false;
}
}
private Matrix4x4 BuildFlybyMarkerMatrix(FlybyCameraInstance instance, float pyramidLength)
{
var model = _editor.Configuration.Rendering3D_UseSpritesForServiceObjects
? Matrix4x4.CreateTranslation(new Vector3(0.0f, 0.0f, -pyramidLength * 0.5f))
: Matrix4x4.Identity;

return model *
Matrix4x4.CreateTranslation(new Vector3(0.0f, 0.0f, -pyramidLength * 1.2f)) *
Matrix4x4.CreateRotationY((float)Math.PI) *
instance.RotationPositionMatrix;
}

// Apply distance-based fade for nearby flyby cameras.
float distance = Vector3.Distance(instance.WorldPosition, Camera.GetPosition());
if (distance < (_coneRadius * 0.5f))
color.W *= distance / (_coneRadius * 0.5f);
private static float GetFlybyPyramidDefaultHalfSize(float pyramidLength)
{
return MathF.Tan(_flybyPyramidReferenceFov * (float)(Math.PI / 360.0f)) * pyramidLength * _flybyPyramidSelectedBaseScale;
}

effect.Parameters["ModelViewProjection"].SetValue((model * _viewProjection).ToSharpDX());
effect.Parameters["Color"].SetValue(color);
effect.CurrentTechnique.Passes[0].Apply();
_legacyDevice.DrawIndexed(PrimitiveType.TriangleList, _cone.IndexBuffer.ElementCount);
}
}
private static float GetFlybyPyramidHalfWidth(float fov, float pyramidLength, float pyramidHalfHeight, bool selected)
{
float clampedHalfAngle = Math.Clamp(fov, 1.0f, 179.0f) * (float)(Math.PI / 360.0f);

_legacyDevice.SetBlendState(_legacyDevice.BlendStates.Opaque);
if (selected)
return MathF.Tan(clampedHalfAngle) * pyramidLength * _flybyPyramidSelectedBaseScale;

return pyramidHalfHeight;
}

private void DrawOrQueueServiceObject(ISpatial instance, GeometricPrimitive primitive, Vector4 color, Effect effect, List<Sprite> sprites)
Expand Down
123 changes: 121 additions & 2 deletions TombEditor/Controls/Panel3D/Panel3DInit.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using SharpDX.Toolkit.Graphics;
using System.Collections.Generic;
using System.Numerics;
using TombLib.Graphics.Primitives;
using TombLib.Graphics;
Expand All @@ -12,6 +13,26 @@ namespace TombEditor.Controls.Panel3D
{
public partial class Panel3D
{
private Buffer<SolidVertex> _flybyPyramidSolidVertexBuffer;
private Buffer<SolidVertex> _flybyPyramidAccentVertexBuffer;
private Buffer<SolidVertex> _flybyPyramidWireVertexBuffer;

private const float _flybyPyramidReferenceFov = 80.0f;
private const float _flybyPyramidSelectedLengthScale = 1.5f;
private const float _flybyPyramidSelectedBaseScale = 0.5f;
private const float _flybyPyramidInactiveLength = 300.0f;
private const float _flybyPyramidInactiveBaseHeight = 200.0f;
private const float _flybyPyramidNormalizedHalfWidth = 1.0f;
private const float _flybyPyramidNormalizedHalfHeight = 1.0f;
private const float _flybyPyramidNormalizedLength = 1.0f;
private const int _flybyPyramidPerimeterSegments = 5;
private const int _flybyPyramidStripeLineCount = 8;
private const float _flybyPyramidStripeStart = 0.95f;
private const float _flybyPyramidStripeEnd = 1.0f;
private static readonly SolidVertex[] _flybyPyramidSolidVertices = BuildFlybyPyramidSolidVertices();
private static readonly SolidVertex[] _flybyPyramidAccentVertices = BuildFlybyPyramidLineVertices(false);
private static readonly SolidVertex[] _flybyPyramidWireVertices = BuildFlybyPyramidLineVertices(true);

public override void InitializeRendering(RenderingDevice device, bool antialias, ObjectRenderingQuality objectQuality)
{
base.InitializeRendering(device, antialias, objectQuality);
Expand Down Expand Up @@ -42,8 +63,8 @@ public override void InitializeRendering(RenderingDevice device, bool antialias,
int atlasSize = objectQuality switch
{
ObjectRenderingQuality.High => 4096,
ObjectRenderingQuality.Medium => 1024,
_ => 512
ObjectRenderingQuality.Medium => 1024,
_ => 512
};

int maxAllocationSize = objectQuality switch
Expand All @@ -57,6 +78,9 @@ public override void InitializeRendering(RenderingDevice device, bool antialias,
// Initialize vertex buffers
_ghostBlockVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New<SolidVertex>(_legacyDevice, 84);
_boxVertexBuffer = new BoundingBox(new Vector3(-_littleCubeRadius), new Vector3(_littleCubeRadius)).GetVertexBuffer(_legacyDevice);
_flybyPyramidSolidVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, _flybyPyramidSolidVertices, SharpDX.Direct3D11.ResourceUsage.Dynamic);
_flybyPyramidAccentVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, _flybyPyramidAccentVertices, SharpDX.Direct3D11.ResourceUsage.Dynamic);
_flybyPyramidWireVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, _flybyPyramidWireVertices, SharpDX.Direct3D11.ResourceUsage.Dynamic);

// Maybe I could use this as bounding box, scaling it properly before drawing
_linesCube = GeometricPrimitive.LinesCube.New(_legacyDevice, 128, 128, 128);
Expand Down Expand Up @@ -130,5 +154,100 @@ RenderingDrawingRoom CacheRoom(Room room)
SectorTextureGet = sectorTextures.Get
});
}

private static SolidVertex[] BuildFlybyPyramidSolidVertices()
{
var vertices = new List<SolidVertex>();
var apex = Vector3.Zero;
var baseCorners = GetFlybyPyramidBaseCorners();

for (int i = 0; i < baseCorners.Length; i++)
AddFlybyPyramidTriangle(vertices, apex, baseCorners[i], baseCorners[(i + 1) % baseCorners.Length]);

AddFlybyPyramidTriangle(vertices, baseCorners[0], baseCorners[1], baseCorners[2]);
AddFlybyPyramidTriangle(vertices, baseCorners[0], baseCorners[2], baseCorners[3]);
return vertices.ToArray();
}

private static SolidVertex[] BuildFlybyPyramidLineVertices(bool includeOutline)
{
var vertices = new List<SolidVertex>();
var perimeterPoints = BuildFlybyPyramidPerimeterPoints();

foreach (var point in perimeterPoints)
AddFlybyPyramidLine(vertices, Vector3.Zero, point);

if (includeOutline)
for (int i = 0; i < perimeterPoints.Count; i++)
AddFlybyPyramidLine(vertices, perimeterPoints[i], perimeterPoints[(i + 1) % perimeterPoints.Count]);

AddFlybyPyramidTopStripe(vertices);
return vertices.ToArray();
}

private static Vector3[] GetFlybyPyramidBaseCorners()
{
return new[]
{
CreateFlybyPyramidBasePoint(-1.0f, 1.0f),
CreateFlybyPyramidBasePoint(1.0f, 1.0f),
CreateFlybyPyramidBasePoint(1.0f, -1.0f),
CreateFlybyPyramidBasePoint(-1.0f, -1.0f)
};
}

private static List<Vector3> BuildFlybyPyramidPerimeterPoints()
{
var points = new List<Vector3>(_flybyPyramidPerimeterSegments * 4);

AddFlybyPyramidEdgePoints(points, new Vector2(-1.0f, 1.0f), new Vector2(1.0f, 1.0f));
AddFlybyPyramidEdgePoints(points, new Vector2(1.0f, 1.0f), new Vector2(1.0f, -1.0f));
AddFlybyPyramidEdgePoints(points, new Vector2(1.0f, -1.0f), new Vector2(-1.0f, -1.0f));
AddFlybyPyramidEdgePoints(points, new Vector2(-1.0f, -1.0f), new Vector2(-1.0f, 1.0f));

return points;
}

private static void AddFlybyPyramidEdgePoints(List<Vector3> points, Vector2 start, Vector2 end)
{
for (int i = 0; i < _flybyPyramidPerimeterSegments; i++)
{
float interpolation = i / (float)_flybyPyramidPerimeterSegments;
var point = Vector2.Lerp(start, end, interpolation);
points.Add(CreateFlybyPyramidBasePoint(point.X, point.Y));
}
}

private static void AddFlybyPyramidTopStripe(List<SolidVertex> vertices)
{
int segmentCount = Math.Max(_flybyPyramidStripeLineCount - 1, 1);

for (int i = 0; i < _flybyPyramidStripeLineCount; i++)
{
float interpolation = i / (float)segmentCount;
float depth = _flybyPyramidStripeStart + ((_flybyPyramidStripeEnd - _flybyPyramidStripeStart) * interpolation);
AddFlybyPyramidLine(vertices,
new Vector3(-_flybyPyramidNormalizedHalfWidth * depth, _flybyPyramidNormalizedHalfHeight * depth, _flybyPyramidNormalizedLength * depth),
new Vector3(_flybyPyramidNormalizedHalfWidth * depth, _flybyPyramidNormalizedHalfHeight * depth, _flybyPyramidNormalizedLength * depth));
}
}

private static Vector3 CreateFlybyPyramidBasePoint(float x, float y)
{
return new Vector3(x * _flybyPyramidNormalizedHalfWidth, y * _flybyPyramidNormalizedHalfHeight, _flybyPyramidNormalizedLength);
}

private static void AddFlybyPyramidTriangle(List<SolidVertex> vertices, Vector3 p0, Vector3 p1, Vector3 p2)
{
vertices.Add(new SolidVertex(p0));
vertices.Add(new SolidVertex(p1));
vertices.Add(new SolidVertex(p2));
}

private static void AddFlybyPyramidLine(List<SolidVertex> vertices, Vector3 start, Vector3 end)
{
vertices.Add(new SolidVertex(start));
vertices.Add(new SolidVertex(end));
}
}
}