-
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #351 from cogentcore/marbles
Add Cogent Marbles from kkoreilly/marbles
- Loading branch information
Showing
20 changed files
with
1,898 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Cogent Marbles | ||
|
||
Graph equations and run marbles on them. Inspired by [desmos.com](https://desmos.com). Uses [Cogent Core](https://github.com/cogentcore/core) for graphics, and [Knetic/govaluate](https://github.com/Knetic/govaluate) for evaluating equations. Formerly located at [kkoreilly/marbles](https://github.com/kkoreilly/marbles/). | ||
|
||
## Install | ||
|
||
You can run Cogent Marbles on the web at https://cogentcore.org/cogent/marbles. You can also run it locally: | ||
|
||
```sh | ||
go run cogentcore.org/cogent/marbles@main | ||
``` | ||
|
||
See the [Cogent Core installation instructions](https://www.cogentcore.org/core/setup/install) for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package main | ||
|
||
import ( | ||
"cogentcore.org/core/core" | ||
"cogentcore.org/core/events" | ||
"cogentcore.org/core/icons" | ||
"cogentcore.org/core/keymap" | ||
"cogentcore.org/core/math32" | ||
"cogentcore.org/core/styles" | ||
"cogentcore.org/core/tree" | ||
) | ||
|
||
func (gr *Graph) MakeToolbar(p *tree.Plan) { | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.Graph).SetIcon(icons.ShowChart) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.Run).SetIcon(icons.PlayArrow) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.Stop).SetIcon(icons.Stop) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.Step).SetIcon(icons.Step) | ||
}) | ||
|
||
tree.Add(p, func(w *core.Separator) {}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.AddLine).SetIcon(icons.Add) | ||
}) | ||
|
||
tree.Add(p, func(w *core.Separator) {}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.SelectNextMarble).SetText("Next marble").SetIcon(icons.ArrowForward) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.StopSelecting).SetText("Unselect").SetIcon(icons.Close) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.TrackSelectedMarble).SetText("Track").SetIcon(icons.PinDrop) | ||
}) | ||
|
||
tree.Add(p, func(w *core.Separator) {}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.OpenJSON).SetText("Open").SetIcon(icons.Open).SetKey(keymap.Open) | ||
w.Args[0].SetTag(`extension:".json"`) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.SaveLast).SetText("Save").SetIcon(icons.Save).SetKey(keymap.Save) | ||
w.Updater(func() { | ||
w.SetEnabled(gr.State.File != "") | ||
}) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.SaveJSON).SetText("Save as").SetIcon(icons.SaveAs).SetKey(keymap.SaveAs) | ||
w.Args[0].SetTag(`extension:".json"`) | ||
}) | ||
tree.Add(p, func(w *core.FuncButton) { | ||
w.SetFunc(gr.Reset).SetIcon(icons.Reset) | ||
}) | ||
} | ||
|
||
func (gr *Graph) MakeBasicElements(b *core.Body) { | ||
sp := core.NewSplits(b).SetTiles(core.TileSecondLong) | ||
sp.Styler(func(s *styles.Style) { | ||
if sp.SizeClass() == core.SizeExpanded { | ||
s.Direction = styles.Column | ||
} else { | ||
s.Direction = styles.Row | ||
} | ||
}) | ||
|
||
gr.Objects.LinesTable = core.NewTable(sp).SetSlice(&gr.Lines) | ||
gr.Objects.LinesTable.OnChange(func(e events.Event) { | ||
gr.Graph() | ||
}) | ||
|
||
gr.Objects.ParamsForm = core.NewForm(sp).SetStruct(&gr.Params) | ||
gr.Objects.ParamsForm.OnChange(func(e events.Event) { | ||
gr.Graph() | ||
}) | ||
|
||
gr.Objects.Graph = core.NewCanvas(sp).SetDraw(gr.draw) | ||
|
||
gr.Vectors.Min = math32.Vector2{X: -GraphViewBoxSize, Y: -GraphViewBoxSize} | ||
gr.Vectors.Max = math32.Vector2{X: GraphViewBoxSize, Y: GraphViewBoxSize} | ||
gr.Vectors.Size = gr.Vectors.Max.Sub(gr.Vectors.Min) | ||
var n float32 = 1.0 / float32(TheSettings.GraphInc) | ||
gr.Vectors.Inc = math32.Vector2{X: n, Y: n} | ||
|
||
statusText := core.NewText(b) | ||
statusText.Updater(func() { | ||
if gr.State.File == "" { | ||
statusText.SetText("Welcome to Cogent Marbles!") | ||
} else { | ||
statusText.SetText("<b>" + string(gr.State.File) + "</b>") | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
About = "Cogent Marbles allows you to enter equations, which are graphed, and then marbles are dropped down on the resulting lines, and bounce around in very entertaining ways!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package main | ||
|
||
import ( | ||
"cogentcore.org/core/colors" | ||
"cogentcore.org/core/math32" | ||
"cogentcore.org/core/paint" | ||
) | ||
|
||
// draw renders the graph. | ||
func (gr *Graph) draw(pc *paint.Context) { | ||
TheGraph.EvalMu.Lock() | ||
defer TheGraph.EvalMu.Unlock() | ||
gr.updateCoords() | ||
gr.drawAxes(pc) | ||
gr.drawTrackingLines(pc) | ||
gr.drawLines(pc) | ||
gr.drawMarbles(pc) | ||
} | ||
|
||
func (gr *Graph) updateCoords() { | ||
if !gr.State.Running || gr.Params.CenterX.Changes || gr.Params.CenterY.Changes { | ||
sizeFromCenter := math32.Vector2{X: GraphViewBoxSize, Y: GraphViewBoxSize} | ||
center := math32.Vector2{X: float32(gr.Params.CenterX.Eval(0, 0)), Y: float32(gr.Params.CenterY.Eval(0, 0))} | ||
gr.Vectors.Min = center.Sub(sizeFromCenter) | ||
gr.Vectors.Max = center.Add(sizeFromCenter) | ||
gr.Vectors.Size = sizeFromCenter.MulScalar(2) | ||
} | ||
} | ||
|
||
// canvasCoord converts the given graph coordinate to a normalized 0-1 canvas coordinate. | ||
func (gr *Graph) canvasCoord(v math32.Vector2) math32.Vector2 { | ||
res := math32.Vector2{} | ||
res.X = (v.X - gr.Vectors.Min.X) / (gr.Vectors.Max.X - gr.Vectors.Min.X) | ||
res.Y = (gr.Vectors.Max.Y - v.Y) / (gr.Vectors.Max.Y - gr.Vectors.Min.Y) | ||
return res | ||
} | ||
|
||
func (gr *Graph) drawAxes(pc *paint.Context) { | ||
pc.StrokeStyle.Color = colors.Scheme.OutlineVariant | ||
|
||
start := gr.canvasCoord(math32.Vec2(gr.Vectors.Min.X, 0)) | ||
end := gr.canvasCoord(math32.Vec2(gr.Vectors.Max.X, 0)) | ||
pc.MoveTo(start.X, start.Y) | ||
pc.LineTo(end.X, end.Y) | ||
pc.Stroke() | ||
|
||
start = gr.canvasCoord(math32.Vec2(0, gr.Vectors.Min.Y)) | ||
end = gr.canvasCoord(math32.Vec2(0, gr.Vectors.Max.Y)) | ||
pc.MoveTo(start.X, start.Y) | ||
pc.LineTo(end.X, end.Y) | ||
pc.Stroke() | ||
} | ||
|
||
func (gr *Graph) drawTrackingLines(pc *paint.Context) { | ||
for _, m := range gr.Marbles { | ||
if !m.TrackingInfo.Track { | ||
continue | ||
} | ||
for j, pos := range m.TrackingInfo.History { | ||
cpos := gr.canvasCoord(pos) | ||
if j == 0 { | ||
pc.MoveTo(cpos.X, cpos.Y) | ||
} else { | ||
pc.LineTo(cpos.X, cpos.Y) | ||
} | ||
} | ||
pc.StrokeStyle.Color = colors.Uniform(m.Color) | ||
pc.Stroke() | ||
} | ||
} | ||
|
||
func (gr *Graph) drawLines(pc *paint.Context) { | ||
for _, ln := range gr.Lines { | ||
// TODO: this logic doesn't work | ||
// If the line doesn't change over time then we don't need to keep graphing it while running marbles | ||
// if !ln.Changes && gr.State.Running && !gr.Params.CenterX.Changes && !gr.Params.CenterY.Changes { | ||
// continue | ||
// } | ||
ln.draw(gr, pc) | ||
} | ||
} | ||
|
||
func (ln *Line) draw(gr *Graph, pc *paint.Context) { | ||
start := true | ||
skipped := false | ||
for x := TheGraph.Vectors.Min.X; x < TheGraph.Vectors.Max.X; x += TheGraph.Vectors.Inc.X { | ||
if TheGraph.State.Error != nil { | ||
return | ||
} | ||
fx := float64(x) | ||
y := ln.Expr.Eval(fx, TheGraph.State.Time, ln.TimesHit) | ||
GraphIf := ln.GraphIf.EvalBool(fx, y, TheGraph.State.Time, ln.TimesHit) | ||
if GraphIf && TheGraph.Vectors.Min.Y < float32(y) && TheGraph.Vectors.Max.Y > float32(y) { | ||
coord := gr.canvasCoord(math32.Vec2(x, float32(y))) | ||
if start || skipped { | ||
pc.MoveTo(coord.X, coord.Y) | ||
start, skipped = false, false | ||
} else { | ||
pc.LineTo(coord.X, coord.Y) | ||
} | ||
} else { | ||
skipped = true | ||
} | ||
} | ||
pc.StrokeStyle.Color = colors.Uniform(ln.Colors.Color) | ||
pc.StrokeStyle.Width.Dp(4) | ||
pc.ToDots() | ||
pc.Stroke() | ||
} | ||
|
||
func (gr *Graph) drawMarbles(pc *paint.Context) { | ||
for i, m := range gr.Marbles { | ||
pos := gr.canvasCoord(m.Pos) | ||
if i == gr.State.SelectedMarble { | ||
pc.DrawCircle(pos.X, pos.Y, 0.02) | ||
pc.FillStyle.Color = colors.Scheme.Warn.Container | ||
pc.Fill() | ||
} | ||
pc.DrawCircle(pos.X, pos.Y, 0.005) | ||
pc.FillStyle.Color = colors.Uniform(m.Color) | ||
pc.Fill() | ||
} | ||
} |
Oops, something went wrong.