123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- // Copyright 2023 prestidigitator (as registered on forum.minetest.net)
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package schematic
- import (
- "bytes"
- "compress/zlib"
- "encoding/binary"
- "encoding/json"
- "fmt"
- "io"
- "strings"
- "text/template"
- "gopkg.in/yaml.v3"
- )
- var yamlTemplate = template.Must(template.New("yaml").Funcs(map[string]any{
- "yProbVal": func(prob uint8) uint8 { return (prob & 0x7f) << 1 },
- "param1Prob": func(param1 uint8) uint8 { return (param1 & 0x7f) << 1 },
- "param1Force": func(param1 uint8) uint8 { return param1 & 0x80 },
- "strEsc": func(s string) (string, error) {
- bs, err := yaml.Marshal(s); return strings.TrimSpace(string(bs)), err
- },
- }).Parse(strings.TrimSpace(`
- size: {x: {{ .Size.X }}, y: {{ .Size.Y }}, z: {{ .Size.Z -}} }
- {{- "\n" -}}
- yslice_prob:
- {{- range $y, $p := .YProbs -}}
- {{- "\n" -}}
- - {ypos: {{ $y }}, prob: {{ yProbVal $p -}} }
- {{- end -}}
- {{- "\n" -}}
- data:
- {{- range $_, $node := .Nodes -}}
- {{- $n := index $.Names $node.Content | strEsc -}}
- {{- $p := param1Prob $node.Param1 -}}
- {{- $f := param1Force $node.Param1 -}}
- {{- $p2 := $node.Param2 -}}
- {{- "\n" -}}
- - {name: {{ $n }}, prob: {{ $p }}, param2: {{ $p2 }}
- {{- if $f -}} , force_place: true {{- end -}}
- }
- {{- end -}}
- {{- "\n" -}}
- `)))
- var jsonTemplate = template.Must(template.New("json").Funcs(map[string]any{
- "yProbVal": func(prob uint8) uint8 { return (prob & 0x7f) << 1 },
- "param1Prob": func(param1 uint8) uint8 { return (param1 & 0x7f) << 1 },
- "param1Force": func(param1 uint8) uint8 { return param1 & 0x80 },
- "strEsc": func(s string) (string, error) {
- bs, err := json.Marshal(s); return string(bs), err
- },
- }).Parse(strings.TrimSpace(`
- {{- $s := .schematic -}}
- {{- $indent := .indent -}}
- {{- $i0 := "" -}}
- {{- $i1oc := "" -}}
- {{- $i1 := " " -}}
- {{- $i2oc := "" -}}
- {{- $i2 := " " -}}
- {{- if $indent -}}
- {{- $i0 = "\n" -}}
- {{- $i1oc = printf "\n%s" $indent -}}
- {{- $i1 = printf "\n%s" $indent -}}
- {{- $i2oc = printf "\n%s%s" $indent $indent -}}
- {{- $i2 = printf "\n%s%s" $indent $indent -}}
- {{- end -}}
- {
- {{- $i1oc -}}
- "size": {"x": {{ $s.Size.X }}, "y": {{ $s.Size.Y }}, "z": {{ $s.Size.Z -}} },
- {{- $i1 -}}
- "yslice_prob": [
- {{- range $y, $p := $s.YProbs -}}
- {{- if gt $y 0 -}} ,{{- $i2 -}} {{- else -}}{{- $i2oc -}}{{- end -}}
- {"ypos": {{ $y }}, "prob": {{ yProbVal $p -}} }
- {{- end -}}
- {{- $i1oc -}}
- ],
- {{- $i1 -}}
- "data": [
- {{- range $i, $node := $s.Nodes -}}
- {{- $n := index $s.Names $node.Content | strEsc -}}
- {{- $p := param1Prob $node.Param1 -}}
- {{- $f := param1Force $node.Param1 -}}
- {{- $p2 := $node.Param2 -}}
- {{- if gt $i 0 -}} ,{{- $i2 -}} {{- else -}}{{- $i2oc -}}{{- end -}}
- {"name": {{ $n }}, "prob": {{ $p }}, "param2": {{ $p2 }}
- {{- if $f -}} , "force_place": true {{- end -}}
- }
- {{- end -}}
- {{- $i1oc -}}
- ]
- {{- $i0 -}}
- }
- {{- $i0 -}}
- `)))
- var luaTemplate = template.Must(template.New("lua").Funcs(map[string]any{
- "inc": func(n int) int { return n+1 },
- "yProbVal": func(prob uint8) uint8 { return (prob & 0x7f) << 1 },
- "param1Prob": func(param1 uint8) uint8 { return (param1 & 0x7f) << 1 },
- "param1Force": func(param1 uint8) uint8 { return param1 & 0x80 },
- "strEsc": func(s string) (string, error) {
- var sb strings.Builder
- sb.Grow(len(s)+2)
- sb.WriteRune('"')
- for i := 0; i < len(s); i++ {
- switch b := s[i]; b {
- case '\a': sb.WriteString(`\a`)
- case '\b': sb.WriteString(`\b`)
- case '\f': sb.WriteString(`\f`)
- case '\n': sb.WriteString(`\n`)
- case '\r': sb.WriteString(`\r`)
- case '\t': sb.WriteString(`\t`)
- case '\\': sb.WriteString(`\\`)
- case '"': sb.WriteString(`\"`)
- case '\'': sb.WriteString(`\'`)
- case 0: sb.WriteString(`\0`)
- default:
- if 0x20 <= b && b <= 0x7e {
- sb.WriteByte(b)
- } else {
- fmt.Fprintf(&sb, `\%03d`, b)
- }
- }
- }
- sb.WriteRune('"')
- return sb.String(), nil
- },
- }).Parse(strings.TrimSpace(`
- {{- $s := .schematic -}}
- {{- $indent := .indent -}}
- {{- $comments := .comments -}}
- schematic = {
- {{- printf "\n%s" $indent -}}
- size = {x={{ printf "%d" $s.Size.X }}, y={{ printf "%d" $s.Size.Y -}}
- , z={{ printf "%d" $s.Size.Z -}} },
- {{- printf "\n%s" $indent -}}
- yslice_prob = {
- {{- range $y, $p := $s.YProbs -}}
- {{- printf "\n%s%s" $indent $indent -}}
- {ypos={{ printf "%d" $y }}, prob={{ yProbVal $p | printf "%d" -}} },
- {{- end -}}
- {{- printf "\n%s" $indent -}}
- },
- {{- printf "\n%s" $indent -}}
- data = {
- {{- $x := 0 -}}
- {{- $y := 0 -}}
- {{- $z := 0 -}}
- {{- range $i, $node := $s.Nodes}}
- {{- $x = inc $x -}}
- {{- if ge $x $s.Size.X -}}
- {{- $x = 0 -}}
- {{- $y = inc $y -}}
- {{- if ge $y $s.Size.Y -}}
- {{- $y = 0 -}}
- {{- $z = inc $z -}}
- {{- end -}}
- {{- end -}}
- {{- $n := index $s.Names $node.Content | strEsc -}}
- {{- $p := param1Prob $node.Param1 -}}
- {{- $f := param1Force $node.Param1 -}}
- {{- $p2 := $node.Param2 -}}
- {{- if and $comments (eq $x 0) -}}
- {{- if or (gt $y 0) (gt $z 0) -}}
- {{- "\n" -}}
- {{- end -}}
- {{- printf "\n%s%s" $indent $indent -}}
- -- z={{ $z }}, y={{ $y }}
- {{- end -}}
- {{- printf "\n%s%s" $indent $indent -}}
- {name={{ $n }}, prob={{ printf "%d" $p }}, param2={{ printf "%d" $p2 }}
- {{- if $f -}} , force_place=true {{- end -}}
- },
- {{- end -}}
- {{- printf "\n%s" $indent -}}
- }
- {{- "\n" -}}
- }
- {{- "\n" -}}
- `)))
- type Schematic struct{
- Size Vec3i
- YProbs []uint8
- Names []string
- Nodes []Node
- }
- type Vec3i struct{
- X, Y, Z uint16
- }
- type Node struct{
- Content uint16
- Param1, Param2 uint8
- }
- type schematicSer struct{
- Size sizeSer `yaml:"size" json:"size"`
- YProbs []yprobSer `yaml:"yslice_prob" json:"yslice_prob"`
- Data []datumSer `yaml:"data" json:"data"`
- }
- type sizeSer struct{
- X uint16 `yaml:"x" json:"x"`
- Y uint16 `yaml:"y" json:"y"`
- Z uint16 `yaml:"z" json:"z"`
- }
- type yprobSer struct{
- YPos uint16 `yaml:"ypos" json:"ypos"`
- Prob uint8 `yaml:"prob" json:"prob"`
- }
- type datumSer struct{
- Name string `yaml:"name" json:"name"`
- Prob uint8 `yaml:"prob" json:"prob"`
- Param2 uint8 `yaml:"param2" json:"param2"`
- Force bool `yaml:"force_place,omitempty" json:"force_place,omitempty"`
- }
- func FromMTSStream(ins io.Reader) (s Schematic, err error) {
- defer func() {
- if pval := recover(); pval != nil && pval != &err { panic(pval) }
- }()
- buf := make([]byte, 12)
- readnb := func(ins io.Reader, n int) {
- if len(buf) < n { buf = make([]byte, n) }
- _, err = io.ReadFull(ins, buf[:n])
- if err != nil { panic(&err) }
- }
- readnb(ins, 12)
- sig := append([]byte{}, buf[:4]...)
- ver := binary.BigEndian.Uint16(buf[4:6])
- sx := binary.BigEndian.Uint16(buf[6:8])
- sy := binary.BigEndian.Uint16(buf[8:10])
- sz := binary.BigEndian.Uint16(buf[10:12])
- if bytes.Compare(sig, []byte("MTSM")) != 0 { err = InvalidMTSSignature; return }
- if ver > 4 { err = UnsupportedMTSVersion; return }
- readnb(ins, int(sy))
- yprobs := make([]uint8, sy)
- if ver >= 3 {
- for i, _ := range yprobs { yprobs[i] = buf[i] }
- } else {
- for i, _ := range yprobs { yprobs[i] = 255 }
- }
- readnb(ins, 2)
- nnames := binary.BigEndian.Uint16(buf[:2])
- names, ignoreIndex := make([]string, nnames), -1
- for i, _ := range names {
- readnb(ins, 2)
- nameLen := int(binary.BigEndian.Uint16(buf[:2]))
- readnb(ins, nameLen)
- name := string(buf[:nameLen])
- if name == "ignore" { name, ignoreIndex = "air", i }
- names[i] = name
- }
- vol := sx * sy * sz
- zin, err := zlib.NewReader(ins)
- if err != nil { return }
- defer zin.Close()
- readnb(zin, 2*int(vol))
- content := make([]uint16, vol)
- for i, _ := range content {
- content[i] = binary.BigEndian.Uint16(buf[2*i : 2*i+2])
- }
- readnb(zin, int(vol))
- param1s := make([]uint8, vol)
- for i, _ := range param1s { param1s[i] = buf[i] }
- readnb(zin, int(vol))
- param2s := make([]uint8, vol)
- for i, _ := range param2s { param2s[i] = buf[i] }
- if ver < 2 {
- for i, _ := range param1s {
- if ignoreIndex >= 0 && int(content[i]) == ignoreIndex {
- param1s[i] = 0
- } else if param1s[i] == 0 {
- param1s[i] = 255
- }
- }
- }
- if ver < 4 {
- for i, _ := range yprobs { yprobs[i] >>= 1 }
- for i, _ := range param1s { param1s[i] >>= 1 }
- }
- nodes := make([]Node, vol)
- for i, _ := range nodes {
- nodes[i] = Node{
- Content: content[i],
- Param1: param1s[i],
- Param2: param2s[i],
- }
- }
- s = Schematic{
- Size: Vec3i{X: sx, Y: sy, Z: sz},
- YProbs: yprobs,
- Names: names,
- Nodes: nodes,
- }
- return
- }
- func FromYAMLStream(ins io.Reader) (Schematic, error) {
- var ss schematicSer
- err := yaml.NewDecoder(ins).Decode(&ss)
- if err != nil { return Schematic{}, err }
- return fromSer(ss)
- }
- func FromJSONStream(ins io.Reader) (Schematic, error) {
- var ss schematicSer
- err := json.NewDecoder(ins).Decode(&ss)
- if err != nil { return Schematic{}, err }
- return fromSer(ss)
- }
- func fromSer(ss schematicSer) (s Schematic, err error) {
- sx, sy, sz := ss.Size.X, ss.Size.Y, ss.Size.Z
- s.Size.X, s.Size.Y, s.Size.Z = sx, sy, sz
- yprobs := make([]uint8, sy)
- for _, yp := range ss.YProbs {
- y, p := yp.YPos, yp.Prob
- if y >= sy {
- err = fmt.Errorf("y-slice y index %d out-of-range [0, %d)", y, sy)
- return
- }
- yprobs[y] = p >> 1
- }
- s.YProbs = yprobs
- names, nameMap := []string{}, map[string]uint16{}
- for _, d := range ss.Data {
- name := d.Name
- if _, ok := nameMap[name]; !ok {
- nameMap[name] = uint16(len(names))
- names = append(names, name)
- }
- }
- s.Names = names
- nodes := make([]Node, len(ss.Data))
- for i, d := range ss.Data {
- param1 := d.Prob >> 1
- if d.Force { param1 |= 0x80 }
- nodes[i] = Node{
- Content: nameMap[d.Name],
- Param1: param1,
- Param2: d.Param2,
- }
- }
- s.Nodes = nodes
- return
- }
- func (s Schematic) WriteMTS(outs io.Writer) (err error) {
- var header [12]byte
- var uint16Buf [2]byte
- copy(header[:6], []byte{'M', 'T', 'S', 'M', 0, 4})
- binary.BigEndian.PutUint16(header[6:8], s.Size.X)
- binary.BigEndian.PutUint16(header[8:10], s.Size.Y)
- binary.BigEndian.PutUint16(header[10:12], s.Size.Z)
- _, err = outs.Write(header[:])
- if err != nil { return err }
- for _, p := range s.YProbs {
- _, err = outs.Write([]byte{p})
- if err != nil { return err }
- }
- binary.BigEndian.PutUint16(uint16Buf[:2], uint16(len(s.Names)))
- _, err = outs.Write(uint16Buf[:])
- if err != nil { return err }
- for _, name := range s.Names {
- binary.BigEndian.PutUint16(uint16Buf[:2], uint16(len(name)))
- _, err = outs.Write(uint16Buf[:])
- if err != nil { return err }
- _, err = outs.Write([]byte(name))
- if err != nil { return err }
- }
- zout := zlib.NewWriter(outs)
- defer func() {
- if e := zout.Close(); e != nil && err == nil { err = e }
- }()
- for _, node := range s.Nodes {
- binary.BigEndian.PutUint16(uint16Buf[:2], uint16(node.Content))
- _, err = zout.Write(uint16Buf[:])
- if err != nil { return err }
- }
- for _, node := range s.Nodes {
- _, err = zout.Write([]byte{byte(node.Param1)})
- if err != nil { return err }
- }
- for _, node := range s.Nodes {
- _, err = zout.Write([]byte{byte(node.Param2)})
- if err != nil { return err }
- }
- return
- }
- func (s Schematic) WriteYAML(outs io.Writer, indent string) error {
- return yamlTemplate.Execute(outs, s)
- }
- func (s Schematic) WriteJSON(outs io.Writer, indent string) error {
- return jsonTemplate.Execute(outs, map[string]any{"indent": indent, "schematic": s})
- }
- func (s Schematic) WriteLua(outs io.Writer, indent string, comments bool) (
- err error,
- ) {
- return luaTemplate.Execute(
- outs, map[string]any{"schematic": s, "indent": indent, "comments": comments},
- )
- }
|