Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize walking of L-path vertices #300

Merged
merged 5 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
33 changes: 29 additions & 4 deletions src/common/Graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,27 @@ export const mix = (v1, v2, s) => {
return new Victor(result[0], result[1])
}

export const buildGraph = (nodes) => {
const graph = new Graph()

for (let i = 0; i < nodes.length - 1; i++) {
const node1 = nodes[i]
const node2 = nodes[i + 1]
graph.addNode(node1)
graph.addNode(node2)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it matter that you're adding both nodes each time? You would have to handle the edge case if you didn't do it this way, but it would save almost half the work on large lists of nodes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it matter that you're adding both nodes each time? You would have to handle the edge case if you didn't do it this way, but it would save almost half the work on large lists of nodes.

You're right. addNode handles it, but that's sloppy and I'll fix.

graph.addEdge(node1, node2)
}

return graph
}

export const edgeKey = (node1, node2) => {
const node1Key = node1.toString()
const node2Key = node2.toString()

return [node1Key, node2Key].sort().toString()
}

// note: requires string-based nodes to work properly
export default class Graph {
constructor() {
Expand Down Expand Up @@ -42,13 +63,13 @@ export default class Graph {
addEdge(node1, node2, weight = 1) {
let node1Key = node1.toString()
let node2Key = node2.toString()
let edgeKey = [node1Key, node2Key].sort().toString()
let edge12Key = edgeKey(node1, node2)

if (!this.edgeKeys.has(edgeKey)) {
if (!this.edgeKeys.has(edge12Key)) {
this.adjacencyList[node1Key].push({ node: node2, weight })
this.adjacencyList[node2Key].push({ node: node1, weight })
this.edgeKeys.add(edgeKey)
this.edgeMap[edgeKey] = [node1.toString(), node2.toString()]
this.edgeKeys.add(edge12Key)
this.edgeMap[edge12Key] = [node1.toString(), node2.toString()]
this.clearCachedPaths()
}
}
Expand All @@ -62,6 +83,10 @@ export default class Graph {
return this.adjacencyList[node.toString()].map((hash) => hash.node)
}

getNode(node) {
return this.nodeMap[node.toString()]
}

dijkstraShortestPath(startNode, endNode) {
let shortest = this.getCachedShortestPath(startNode, endNode)

Expand Down
11 changes: 7 additions & 4 deletions src/features/export/Exporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default class Exporter {
this.startComments()
this.line("BEGIN PRE")
this.endComments()
this.line(this.pre, this.pre !== "")
this.line(this.pre, this.pre !== "", false)
this.startComments()
this.line("END PRE")
this.endComments()
Expand All @@ -91,7 +91,7 @@ export default class Exporter {
this.startComments()
this.line("BEGIN POST")
this.endComments()
this.line(this.post, this.post !== "")
this.line(this.post, this.post !== "", false)
this.startComments()
this.line("END POST")
this.endComments()
Expand All @@ -115,7 +115,7 @@ export default class Exporter {
this.vertices = vertices
}

line(content = "", add = true) {
line(content = "", add = true, sanitize = true) {
if (add) {
let padding = ""
if (this.commenting) {
Expand All @@ -124,7 +124,10 @@ export default class Exporter {
padding += " "
}
}
this.lines.push(padding + this.sanitizeValue(content))

const preparedContent = sanitize ? this.sanitizeValue(content) : content

this.lines.push(padding + preparedContent)
}
}

Expand Down
59 changes: 56 additions & 3 deletions src/features/shapes/lsystem/LSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
onMaxIterations,
} from "@/common/lindenmayer"
import { subtypes } from "./subtypes"
import { resizeVertices } from "@/common/geometry"
import { resizeVertices, cloneVertices } from "@/common/geometry"
import { buildGraph, edgeKey } from "@/common/Graph"

const options = {
subtype: {
Expand Down Expand Up @@ -63,13 +64,65 @@ export default class LSystem extends Shape {
config.angle = Math.PI / 2
}

let curve = lsystemPath(lsystem(config), config)
const curve = lsystemPath(lsystem(config), config)
const scale = 18.0 // to normalize starting size
const path =
config.shortestPath >= iterations ? this.shortestPath(curve) : curve

return resizeVertices(curve, scale, scale)
return resizeVertices(path, scale, scale)
}

getOptions() {
return options
}

shortestPath(nodes) {
const graph = buildGraph(nodes)
const path = []
const visited = {}

for (let i = 0; i < nodes.length - 1; i++) {
const node1 = nodes[i]
const node2 = nodes[i + 1]
let node1Key = node1.toString()
let edge12Key = edgeKey(node1, node2)

if (visited[edge12Key]) {
const unvisitedNode = this.nearestUnvisitedNode(
i + 1,
nodes,
visited,
graph,
)

if (unvisitedNode != null) {
const shortestSubPath = graph.dijkstraShortestPath(
node1Key,
unvisitedNode.toString(),
)

path.push(...cloneVertices(shortestSubPath.slice(1)))
i = nodes.indexOf(unvisitedNode) - 1
}
} else {
path.push(node2)
visited[edge12Key] = true
}
}

return path
}

nearestUnvisitedNode(nodeIndex, nodes, visited, graph) {
for (let i = nodeIndex; i < nodes.length - 1; i++) {
const node1 = nodes[i]
const node2 = nodes[i + 1]

if (!visited[edgeKey(node1, node2)]) {
return node2
}
}

return null // all nodes visited
}
}
10 changes: 7 additions & 3 deletions src/features/shapes/lsystem/subtypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export const subtypes = {
rules: {
F: "FF-F-F-F-FF",
},
maxIterations: 5,
maxIterations: 4,
shortestPath: 3,
},
// http://algorithmicbotany.org/papers/abop/abop-ch1.pdf
"Koch Cube 2": {
Expand All @@ -115,7 +116,8 @@ export const subtypes = {
rules: {
F: "FF-F+F-F-FF",
},
maxIterations: 5,
maxIterations: 4,
shortestPath: 3,
},
// https://onlinemathtools.com/l-system-generator
"Koch Curve": {
Expand All @@ -136,6 +138,7 @@ export const subtypes = {
F: "FF-F-F-F-F-F+F",
},
maxIterations: 4,
shortestPath: 3,
},
// http://mathforum.org/advanced/robertd/lsys2d.html
"Koch Island": {
Expand Down Expand Up @@ -178,7 +181,8 @@ export const subtypes = {
9: "--8++++6[+9++++7]--7",
},
angle: Math.PI / 5,
maxIterations: 6,
maxIterations: 5,
shortestPath: 5,
},
Plusses: {
axiom: "XYXYXYX+XYXYXYX+XYXYXYX+XYXYXYX",
Expand Down
Loading