121 lines
4.0 KiB
JavaScript
121 lines
4.0 KiB
JavaScript
import {eq_vect, resolve_color, set_turtle_color, get_turtle_color, turtle_radius, rotate, turn_to_deg, command_to_state, turtle_init, background_color, turtle_angle, last} from "./turtle_geometry.js"
|
|
|
|
function hex (n) {
|
|
return n.toString(16).padStart(2, "0")
|
|
}
|
|
|
|
function svg_render_line (prev, curr) {
|
|
if (!prev.pendown) return ""
|
|
if (eq_vect(prev.position, curr.position)) return ""
|
|
const {position: [x1, y1], pencolor, penwidth} = prev
|
|
const {position: [x2, y2]} = curr
|
|
const [r, g, b, a] = resolve_color(pencolor)
|
|
return `
|
|
<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="#${hex(r)}${hex(g)}${hex(b)}" stroke-linecap="round" stroke-opacity="${a/255}" stroke-width="${penwidth}"/>
|
|
`
|
|
}
|
|
|
|
function escape_svg (svg) {
|
|
return svg
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'")
|
|
}
|
|
|
|
export function extract_ludus (svg) {
|
|
const code = svg.split("<ludus>")[1]?.split("</ludus>")[0] ?? ""
|
|
return code
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, `"`)
|
|
.replace(/'/g, `'`)
|
|
}
|
|
|
|
function svg_render_path (states) {
|
|
const path = []
|
|
for (let i = 1; i < states.length; ++i) {
|
|
const prev = states[i - 1]
|
|
const curr = states[i]
|
|
path.push(svg_render_line(prev, curr))
|
|
}
|
|
return path.join("")
|
|
}
|
|
|
|
function svg_render_turtle (state) {
|
|
if (!state.visible) return ""
|
|
const [fr, fg, fb, fa] = get_turtle_color()
|
|
const fill_alpha = fa/255
|
|
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
|
|
const origin = [0, turtle_radius]
|
|
const [x1, y1] = origin
|
|
const [x2, y2] = rotate(origin, turtle_angle)
|
|
const [x3, y3] = rotate(origin, -turtle_angle)
|
|
const [pr, pg, pb, pa] = resolve_color(pencolor)
|
|
const pen_alpha = pa/255
|
|
const ink = pendown ? `<line x1="${x1}" y1="${y1}" x2="0" y2="0" stroke="#${hex(pr)}${hex(pg)}${hex(pb)}" stroke-linecap="round" stroke-opacity="${pen_alpha}" stroke-width="${penwidth}" />` : ""
|
|
return `
|
|
<g transform="translate(${x}, ${y})rotate(${-turn_to_deg(heading)})">
|
|
<polygon points="${x1} ${y1} ${x2} ${y2} ${x3} ${y3}" stroke="none" fill="#${hex(fr)}${hex(fg)}${hex(fb)})" fill-opacity="${fill_alpha}"/>
|
|
${ink}
|
|
</g>
|
|
`
|
|
}
|
|
|
|
// TODO: update this to match the new `p5` function
|
|
export function svg (commands, code) {
|
|
const all_states = {}
|
|
for (const command of commands) {
|
|
const [turtle_id, _, this_command] = command
|
|
let stack = all_states[turtle_id]
|
|
if (!stack) {
|
|
const new_stack = [turtle_init]
|
|
all_states[turtle_id] = new_stack
|
|
stack = new_stack
|
|
}
|
|
const prev_state = last(all_states[turtle_id])
|
|
const new_state = command_to_state(prev_state, this_command)
|
|
all_states[turtle_id].push(new_state)
|
|
}
|
|
let maxX = -Infinity, maxY = -Infinity, minX = Infinity, minY = Infinity
|
|
for (const states of Object.values(all_states)) {
|
|
for (const {position: [x, y]} of states) {
|
|
maxX = Math.max(maxX, x)
|
|
maxY = Math.max(maxY, y)
|
|
minX = Math.min(minX, x)
|
|
minY = Math.min(minY, y)
|
|
}
|
|
}
|
|
const [r, g, b] = resolve_color(background_color)
|
|
if ((r+g+b)/3 > 128) set_turtle_color([0, 0, 0, 150])
|
|
const view_width = (maxX - minX) * 1.2
|
|
const view_height = (maxY - minY) * 1.2
|
|
const margin = Math.max(view_width, view_height) * 0.1
|
|
const x_origin = minX - margin
|
|
const y_origin = -maxY - margin
|
|
let path = ""
|
|
let turtle = ""
|
|
for (const states of Object.values(all_states)) {
|
|
path = path + svg_render_path(states)
|
|
turtle = svg_render_turtle(last(states))
|
|
}
|
|
return `<?xml version="1.0" standalone="no"?>
|
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="${x_origin} ${y_origin} ${view_width} ${view_height}" width="10in" height="8in">
|
|
|
|
<rect x="${x_origin - 5}" y="${y_origin - 5}" width="${view_width + 10}" height="${view_height + 10}" fill="#${hex(r)}${hex(g)}${hex(b)}" stroke-width="0" paint-order="fill" />
|
|
|
|
<g transform="scale(-1, 1) rotate(180)">
|
|
${path}
|
|
${turtle}
|
|
</g>
|
|
|
|
<ludus>
|
|
${escape_svg(code)}
|
|
</ludus>
|
|
</svg>
|
|
`
|
|
}
|
|
|