-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvectors7.nim
More file actions
189 lines (159 loc) · 7.6 KB
/
vectors7.nim
File metadata and controls
189 lines (159 loc) · 7.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# ****************************************************************************************
#
# raylib [vectors] lesson 7 - Graphing Quadratic Functions
#
# This lesson demonstrates:
# - The difference between a quadratic FUNCTION and a quadratic EQUATION.
# - How to plot a mathematical function on the screen.
# - Mapping coordinates from a "graph space" to "screen space".
# - Calculating and visualizing the roots (solutions) of a quadratic equation.
#
# ****************************************************************************************
import raylib
import raymath
import math
import strformat
const
screenWidth = 800
screenHeight = 600 # A bit taller for the graph.
# --- Graphing Helper Types and Procs ---
type
GraphSpace = object
origin: Vector2 # Screen coordinates of the graph's (0,0) point
scale: Vector2 # Pixels per unit for x and y
# Function to convert a point from graph space to screen space
proc toScreenSpace(p: Vector2, graph: GraphSpace): Vector2 =
# X: Start at the origin's screen X and add the scaled graph X.
result.x = graph.origin.x + p.x * graph.scale.x
# Y: Start at the origin's screen Y and SUBTRACT the scaled graph Y.
# This is because in math, positive Y goes up, but in screen coordinates, positive Y goes down.
result.y = graph.origin.y - p.y * graph.scale.y
# The quadratic function itself: y = ax² + bx + c
proc quadraticFunc(x: float32, a, b, c: float32): float32 =
return a * x * x + b * x + c
proc main =
initWindow(screenWidth, screenHeight, "raylib [vectors] lesson 7 - Quadratic Functions")
setTargetFPS(60)
# LESSON 1: DEFINING THE GRAPH SPACE
# We need to map our mathematical coordinates to screen coordinates.
let graph = GraphSpace(
origin: Vector2(x: screenWidth / 2.0, y: screenHeight / 2.0 + 50),
scale: Vector2(x: 30.0, y: 30.0) # 30 pixels per 1 unit
)
# LESSON 2: PRE-DEFINED FUNCTIONS
# We'll create a list of functions to cycle through. Each is a tuple of (a, b, c).
let functions = [
(a: 0.5, b: -1.0, c: -4.0), # Two real roots
(a: 1.0, b: -6.0, c: 9.0), # One real root
(a: 1.0, b: 2.0, c: 5.0), # No real roots
(a: -0.5, b: 1.0, c: 6.0) # Downward-facing parabola
]
var currentFuncIndex = 0
# Main game loop
# --------------------------------------------------------------------------------------
while not windowShouldClose():
# Update
# ----------------------------------------------------------------------------------
if isKeyPressed(Space):
# Use the modulo operator to cycle through the list of functions, wrapping
# around to the beginning when we reach the end.
currentFuncIndex = (currentFuncIndex + 1) mod functions.len
# LESSON 3: THE CURRENT FUNCTION AND EQUATION
# Get the current coefficients and calculate the roots on every frame.
let (a, b, c) = functions[currentFuncIndex]
# This is the implementation of the quadratic formula to find the roots:
# x = [-b ± sqrt(b² - 4ac)] / 2a
# The "discriminant" is the part under the square root. Its value tells us
# how many real roots the equation has.
let discriminant = b*b - 4*a*c
var roots: seq[float32] = @[]
if discriminant > 0:
let sqrtDiscriminant = sqrt(discriminant)
roots.add((-b + sqrtDiscriminant) / (2*a))
roots.add((-b - sqrtDiscriminant) / (2*a))
elif discriminant == 0:
roots.add(-b / (2*a))
# Draw
# ------------------------------------------------------------------------------------
beginDrawing()
clearBackground(RayWhite)
# --- Draw Graph Axes and Grid ---
let xAxisStart = toScreenSpace(Vector2(x: -20, y: 0), graph)
let xAxisEnd = toScreenSpace(Vector2(x: 20, y: 0), graph)
drawLine(xAxisStart, xAxisEnd, 2.0, LightGray)
let yAxisStart = toScreenSpace(Vector2(x: 0, y: -20), graph)
let yAxisEnd = toScreenSpace(Vector2(x: 0, y: 20), graph)
drawLine(yAxisStart, yAxisEnd, 2.0, LightGray)
# --- Draw the Parabola ---
# We draw the function by calculating many points and connecting them with lines.
let steps = 200 # More steps = smoother curve
let graphWidth = screenWidth.float / graph.scale.x
for i in 0 ..< steps:
# Calculate the start point (p1) of a tiny line segment.
# We map the loop counter `i` to an x-coordinate in our graph's space.
let x1_graph = -graphWidth/2 + (i.float / steps.float) * graphWidth
let y1_graph = quadraticFunc(x1_graph, a, b, c)
let p1_screen = toScreenSpace(Vector2(x: x1_graph, y: y1_graph), graph)
# Calculate the end point (p2) of the segment for the next step.
let x2_graph = -graphWidth/2 + ((i+1).float / steps.float) * graphWidth
let y2_graph = quadraticFunc(x2_graph, a, b, c)
let p2_screen = toScreenSpace(Vector2(x: x2_graph, y: y2_graph), graph)
drawLine(p1_screen, p2_screen, 2.0, Maroon)
# --- Draw the Roots ---
# The roots are the solutions to the equation, where y=0.
for root in roots:
let rootPosGraph = Vector2(x: root, y: 0)
let rootPosScreen = toScreenSpace(rootPosGraph, graph)
drawCircle(rootPosScreen, 8.0, colorAlpha(Blue, 0.7))
drawCircleLines(rootPosScreen, 8.0, Blue)
# Draw the numerical value of the root on the graph.
let rootValueText = fmt"{root:.2f}"
# Measure the text so we can center it under the circle.
let textWidth = measureText(rootValueText, 15)
let textPos = Vector2(x: rootPosScreen.x - textWidth / 2, y: rootPosScreen.y + 12)
drawText(rootValueText, textPos.x.int32, textPos.y.int32, 15, Blue)
# --- Draw Explanations ---
drawText("Quadratic Visualizer", 20, 20, 40, DarkGray)
let funcText = fmt"Function: y = {a}x² + {b}x + {c}"
drawText(funcText, 20, 80, 20, Black)
let eqText = fmt"Equation: {a}x² + {b}x + {c} = 0"
drawText(eqText, 20, 110, 20, Black)
drawText("The function (red curve) describes the relationship between x and y.",
20, 150, 20, Gray)
drawText("Press [Space] to cycle through different functions.",
20, screenHeight - 40, 20, LightGray)
drawText("The equation's solutions (blue circles) are the 'roots' -",
20, 180, 20, Gray)
drawText("the x-values where y is 0.", 20, 200, 20, Gray)
var rootText = "Roots: "
var discriminantText = fmt"Discriminant (b² - 4ac) = {discriminant:.2f}"
var discriminantMeaning: string
if roots.len == 0:
rootText &= "None (parabola does not cross the x-axis)"
discriminantMeaning = "Discriminant is negative: No real roots"
elif roots.len == 1:
rootText &= fmt"{roots[0]:.2f}"
discriminantMeaning = "Discriminant is zero: One real root"
else:
for i, root in roots:
rootText &= fmt"{root:.2f}"
if i < roots.len - 1: rootText &= ", "
discriminantMeaning = "Discriminant is positive: Two real roots"
drawText(rootText, 20, 240, 20, Blue)
drawText(discriminantText, 20, 270, 20, DarkGreen)
drawText(discriminantMeaning, 20, 290, 20, DarkGreen)
endDrawing()
# ------------------------------------------------------------------------------------
# De-Initialization
# --------------------------------------------------------------------------------------
closeWindow()
# --------------------------------------------------------------------------------------
main()
#[
This lesson shows the clear difference:
- The FUNCTION `y = ax² + bx + c` is the entire red curve.
It's a "map" of all possible points.
- The EQUATION `ax² + bx + c = 0` asks a specific question:
"Where does that curve cross the line y=0 (the x-axis)?".
- The blue circles are the answers to that question.
]#