2
0
mirror of https://github.com/raylib-cs/raylib-cs synced 2025-04-05 11:19:39 -04:00
raylib-cs/Examples/Textures/TexturedCurve.cs

285 lines
9.2 KiB
C#

using System;
using System.Numerics;
using static Raylib_cs.Raylib;
namespace Examples.Textures;
public unsafe class TexturedCurve
{
public class CurvePoint
{
public Vector2 value;
public float X => value.X;
public float Y => value.Y;
public static implicit operator CurvePoint(Vector2 v) => new CurvePoint { value = v };
public static implicit operator Vector2(CurvePoint v) => v.value;
}
static Texture2D texRoad;
static bool showCurve = false;
static float curveWidth = 50;
static int curveSegments = 24;
static CurvePoint curveStartPosition;
static CurvePoint curveStartPositionTangent;
static CurvePoint curveEndPosition;
static CurvePoint curveEndPositionTangent;
static CurvePoint curveSelectedPoint;
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
SetConfigFlags(ConfigFlags.FLAG_VSYNC_HINT | ConfigFlags.FLAG_MSAA_4X_HINT);
InitWindow(screenWidth, screenHeight, "raylib [textures] examples - textured curve");
// Load the road texture
texRoad = LoadTexture("resources/road.png");
SetTextureFilter(texRoad, TextureFilter.TEXTURE_FILTER_BILINEAR);
// Setup the curve
curveStartPosition = new Vector2(80, 100);
curveStartPositionTangent = new Vector2(100, 300);
curveEndPosition = new Vector2(700, 350);
curveEndPositionTangent = new Vector2(600, 100);
SetTargetFPS(60);
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose())
{
// Update
//----------------------------------------------------------------------------------
UpdateCurve();
UpdateOptions();
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(Color.RAYWHITE);
DrawTexturedCurve();
DrawCurve();
DrawText("Drag points to move curve, press SPACE to show/hide base curve", 10, 10, 10, Color.DARKGRAY);
DrawText($"Curve width: {curveWidth} (Use + and - to adjust)", 10, 30, 10, Color.DARKGRAY);
DrawText($"Curve segments: {curveSegments} (Use LEFT and RIGHT to adjust)", 10, 50, 10, Color.DARKGRAY);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
UnloadTexture(texRoad);
CloseWindow();
//--------------------------------------------------------------------------------------
return 0;
}
static void DrawCurve()
{
if (showCurve)
{
DrawLineBezierCubic(
curveStartPosition,
curveEndPosition,
curveStartPositionTangent,
curveEndPositionTangent,
2,
Color.BLUE
);
}
// Draw the various control points and highlight where the mouse is
DrawLineV(curveStartPosition, curveStartPositionTangent, Color.SKYBLUE);
DrawLineV(curveEndPosition, curveEndPositionTangent, Color.PURPLE);
Vector2 mouse = GetMousePosition();
if (CheckCollisionPointCircle(mouse, curveStartPosition, 6))
{
DrawCircleV(curveStartPosition, 7, Color.YELLOW);
}
DrawCircleV(curveStartPosition, 5, Color.RED);
if (CheckCollisionPointCircle(mouse, curveStartPositionTangent, 6))
{
DrawCircleV(curveStartPositionTangent, 7, Color.YELLOW);
}
DrawCircleV(curveStartPositionTangent, 5, Color.MAROON);
if (CheckCollisionPointCircle(mouse, curveEndPosition, 6))
{
DrawCircleV(curveEndPosition, 7, Color.YELLOW);
}
DrawCircleV(curveEndPosition, 5, Color.GREEN);
if (CheckCollisionPointCircle(mouse, curveEndPositionTangent, 6))
{
DrawCircleV(curveEndPositionTangent, 7, Color.YELLOW);
}
DrawCircleV(curveEndPositionTangent, 5, Color.DARKGREEN);
}
static void UpdateCurve()
{
// If the mouse is not down, we are not editing the curve so clear the selection
if (!IsMouseButtonDown(MouseButton.MOUSE_LEFT_BUTTON))
{
return;
}
// If a point was selected, move it
if (curveSelectedPoint != null)
{
curveSelectedPoint.value += GetMouseDelta();
}
// The mouse is down, and nothing was selected, so see if anything was picked
Vector2 mouse = GetMousePosition();
if (CheckCollisionPointCircle(mouse, curveStartPosition, 6))
{
curveSelectedPoint = curveStartPosition;
}
else if (CheckCollisionPointCircle(mouse, curveStartPositionTangent, 6))
{
curveSelectedPoint = curveStartPositionTangent;
}
else if (CheckCollisionPointCircle(mouse, curveEndPosition, 6))
{
curveSelectedPoint = curveEndPosition;
}
else if (CheckCollisionPointCircle(mouse, curveEndPositionTangent, 6))
{
curveSelectedPoint = curveEndPositionTangent;
}
}
static void DrawTexturedCurve()
{
float step = 1.0f / curveSegments;
Vector2 previous = curveStartPosition;
Vector2 previousTangent = Vector2.Zero;
float previousV = 0;
// We can't compute a tangent for the first point, so we need to reuse the tangent from the first segment
bool tangentSet = false;
Vector2 current = Vector2.Zero;
float t = 0.0f;
for (int i = 1; i <= curveSegments; i++)
{
// Segment the curve
t = step * i;
float a = MathF.Pow(1 - t, 3);
float b = 3 * MathF.Pow(1 - t, 2) * t;
float c = 3 * (1 - t) * MathF.Pow(t, 2);
float d = MathF.Pow(t, 3);
// Compute the endpoint for this segment
current.Y = a * curveStartPosition.Y + b * curveStartPositionTangent.Y;
current.Y += c * curveEndPositionTangent.Y + d * curveEndPosition.Y;
current.X = a * curveStartPosition.X + b * curveStartPositionTangent.X;
current.X += c * curveEndPositionTangent.X + d * curveEndPosition.X;
// Vector from previous to current
Vector2 delta = new(current.X - previous.X, current.Y - previous.Y);
// The right hand normal to the delta vector
Vector2 normal = Vector2.Normalize(new Vector2(-delta.Y, delta.X));
// The v teXture coordinate of the segment (add up the length of all the segments so far)
float v = previousV + delta.Length();
// Make sure the start point has a normal
if (!tangentSet)
{
previousTangent = normal;
tangentSet = true;
}
// EXtend out the normals from the previous and current points to get the quad for this segment
Vector2 prevPosNormal = previous + (previousTangent * curveWidth);
Vector2 prevNegNormal = previous + (previousTangent * -curveWidth);
Vector2 currentPosNormal = current + (normal * curveWidth);
Vector2 currentNegNormal = current + (normal * -curveWidth);
// Draw the segment as a quad
Rlgl.SetTexture(texRoad.Id);
Rlgl.Begin(DrawMode.QUADS);
Rlgl.Color4ub(255, 255, 255, 255);
Rlgl.Normal3f(0.0f, 0.0f, 1.0f);
Rlgl.TexCoord2f(0, previousV);
Rlgl.Vertex2f(prevNegNormal.X, prevNegNormal.Y);
Rlgl.TexCoord2f(1, previousV);
Rlgl.Vertex2f(prevPosNormal.X, prevPosNormal.Y);
Rlgl.TexCoord2f(1, v);
Rlgl.Vertex2f(currentPosNormal.X, currentPosNormal.Y);
Rlgl.TexCoord2f(0, v);
Rlgl.Vertex2f(currentNegNormal.X, currentNegNormal.Y);
Rlgl.End();
// The current step is the start of the neXt step
previous = current;
previousTangent = normal;
previousV = v;
}
}
static void UpdateOptions()
{
if (IsKeyPressed(KeyboardKey.KEY_SPACE))
{
showCurve = !showCurve;
}
// Update with
if (IsKeyPressed(KeyboardKey.KEY_EQUAL))
{
curveWidth += 2;
}
if (IsKeyPressed(KeyboardKey.KEY_MINUS))
{
curveWidth -= 2;
}
if (curveWidth < 2)
{
curveWidth = 2;
}
// Update segments
if (IsKeyPressed(KeyboardKey.KEY_LEFT))
{
curveSegments -= 2;
}
if (IsKeyPressed(KeyboardKey.KEY_RIGHT))
{
curveSegments += 2;
}
if (curveSegments < 2)
{
curveSegments = 2;
}
}
}