Skip to content

Commit

Permalink
Merge pull request #351 from cogentcore/marbles
Browse files Browse the repository at this point in the history
Add Cogent Marbles from kkoreilly/marbles
  • Loading branch information
rcoreilly authored Sep 7, 2024
2 parents 9ab53a8 + 7feb30b commit 751b696
Show file tree
Hide file tree
Showing 20 changed files with 1,898 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
- name: Build Canvas
run: core build web -dir canvas/cmd/canvas -o static/canvas

- name: Build Marbles
run: core build web -dir marbles -o static/marbles

- name: Setup Pages
id: pages
uses: actions/configure-pages@v3
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22

require (
cogentcore.org/core v0.3.3-0.20240902213628-48df10901467
github.com/Knetic/govaluate v3.0.0+incompatible
github.com/aandrew-me/tgpt/v2 v2.7.2
github.com/alecthomas/chroma/v2 v2.13.0
github.com/bogdanfinn/fhttp v0.5.27
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ cogentcore.org/core v0.3.3-0.20240902213628-48df10901467/go.mod h1:dg3uRsPcd8S1Z
github.com/Bios-Marcel/wastebasket v0.0.4-0.20240213135800-f26f1ae0a7c4 h1:6lx9xzJAhdjq0LvVfbITeC3IH9Fzvo1aBahyPu2FuG8=
github.com/Bios-Marcel/wastebasket v0.0.4-0.20240213135800-f26f1ae0a7c4/go.mod h1:FChzXi1izqzdPb6BiNZmcZLGyTYiT61iGx9Rxx9GNeI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE=
github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8=
github.com/aandrew-me/tgpt/v2 v2.7.2 h1:xhcglzpC+A16t40GvEPAwPATFeRY5h4lYpabN6DBK9k=
Expand Down
13 changes: 13 additions & 0 deletions marbles/README.md
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.
99 changes: 99 additions & 0 deletions marbles/app.go
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>")
}
})
}
1 change: 1 addition & 0 deletions marbles/core.toml
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!"
123 changes: 123 additions & 0 deletions marbles/draw.go
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()
}
}
Loading

0 comments on commit 751b696

Please sign in to comment.