minetest-cereal/world/blocks.rkt

219 lines
7.2 KiB
Racket

#lang racket
(require test-engine/racket-tests)
(provide pos->point point->pos (struct-out minetest-block)
point-x point-y point-z)
;; #### Constants ####
;; The algorithm used to convert block 'pos' values into positions in
;; X,Y,Z coordinates uses these values. I do not know why it was done
;; this way.
(define POS_MAX_SIGN 2048)
(define POS_MODULO 4096)
(define POS_BIGINT 16777216)
;; #### Data Definitions ####
;; # Coordinate is Number[-30912 30927]
;; Interpretation: a single number representing an x, y, or z value
;; of a location in a Minetest world
;; The origin coordinate of either the x, y, or z axis.
(define COORDINATE-01 0)
;; The minimum an x, y, or z can be.
(define COORDINATE-02 -30912)
;; 3/4 of the way up of the full coordinate range
(define COORDINATE-03 15456)
#;
(define (fn-for-coordinate c)
(... c))
;; # Pos is Integer
;; Interpretation: The 'pos' field from a Minetest world database is
;; run through a math algorithm to determine the x, y, and z coordinates.
(define POS-01 -16756737) ; Translates to coordinate (-1 5 -1)
(define POS-02 -16752641) ; Translates to coordinate (-1 6 -1)
(define POS-03 20479) ; Translates to coordinate (-1 5 0)
;; Template
#;
(define (fn-for-pos pos)
(if (not (number? pos))
(error "position must be a number")
(... pos)))
;; # Point is (list x y z)
;; Where x, y, and z are the numeric coordinates for the point in their
;; respective axis. This could have been a struct (and I'll be
;; replicating basic accessors), but the alorithm for the Pos is
;; better geared toward recursion and list output.
; Origin point - single-player games usually start near here
(define POINT-0 (list 0 0 0))
; Due West and slightly above the origin point
(define POINT-1 (list -200 5 0))
; Slightly East, far North, and below the origin point.
(define POINT-2 (list 25 -30 500))
;; The default template for Point looks odd, but this is a data definition
;; for a list of exactly three elements, not one of arbitrary length.
#;
(define (fn-for-point p)
(... (first p) ; x coordinate
(first (rest p)) ; y coordinate
(first (rest (rest p))))) ; z coordinate
;; Using functions from those defined in the function section below allows
;; for a struct-like template:
#;
(define (fn-for-point p)
(... (point-x p) ; x coordinate
(point-y p) ; y coordinate
(point-z p))) ; z coordinate
;; ListOfPoint is one of
;; - empty
;; - (cons Point ListOfPoint)
(define LOP-0 empty)
(define LOP-1 (cons (list 0 0 0) (cons (list 500 13 105) empty)))
#;
(define (fn-for-lop lop)
(cond [(empty? lop) (...)]
[else
(... (fn-for-point (first lop))
(fn-for-lop (rest lop)))]))
;; # Minetest-Block is (minetest-block pos data)
;; Interpretation: This duplicates a single block entry from a Minetest
;; map.sqlite (or compatible) database.
;; - pos is the identifying string, which can be converted to the
;; x,y,z coordinates of the block
;; - data is the chunk of bits that the Minetest game engine translates
;; into actual information about the block.
(struct minetest-block (pos data))
;; As of this writing, I do not know how to describe the examples.
;; They were records pulled from one of my single-player Minetest worlds.
;; As written the data may be corrupt, having been converted to a string.
(define mblock-01 (minetest-block -16756737 "\31\0\2\2x\234\355\330\1\r\0\0\b\3\2407\260\177[S8\247\203\30$\0\0\0\0\0\0\0\300u\5\0\0\0\274\267\375\17\0\0\0\300\274\6\t\236\360\1x\234c\0\0\0\1\0\1\0\0\0\377\377\377\377\0\0\1\0\0\0\3air\n\0\0"))
(define mblock-02 (minetest-block 20479 "\31\0\2\2x\234\355\330\1\r\0\0\b\3\2407\260\177[S8\247\203\30$\0\0\0\0\0\0\0\300u\5\0\0\0\274\267\375\17\0\0\0\300\274\6\t\236\360\1x\234c\0\0\0\1\0\1\0\0\0\377\377\377\377\0\0\1\0\0\0\3air\n\0\0"))
#;
(define (fn-for-minetest-block block)
(... (minetest-block-pos block)
(minetest-block-data block)))
;; Function Definitions
;; Pos -> Point
;; Convert a Pos formatted position identifier into a Point.
;; We're treating Pos as a self-referencing recursive number
;; for the purposes of Minetests position algorithm, so
;; this function is structured differently from the normal data
;; template.
;; By HTDP standards, the recursion is jacked-up, because the algorithm
;; is a jacked up, becuase the data definition for Pos is jacked up,
;; which is the whole reason I'm writing a way to convert it in the first
;; place.
(check-expect (pos->point -16756737 empty) (list -1 5 -1))
(check-expect (pos->point -16752641 empty) (list -1 6 -1))
(check-expect (pos->point 20479 empty) (list -1 5 0))
(define (pos->point pos build-point)
(cond [(not (number? pos)) (error "position must be a number")]
[(> (length build-point) 3) (error "build-point too long")]
[(= (length build-point) 3) (reverse build-point)]
[else
(let* ([coordinate (pos-to-coordinate pos)])
(append (pos->point (splice-pos coordinate pos)
(cons coordinate build-point))))]))
;; Pos -> Coordinate
;; Take a Pos, calculate a single coordinate
(check-expect (pos-to-coordinate -16756737) -1)
(check-expect (pos-to-coordinate -4091) 5)
(check-expect (pos-to-coordinate -4090) 6)
(check-expect (pos-to-coordinate 20479) -1)
(define (pos-to-coordinate pos)
(below-max (modulo pos POS_MODULO)))
(define (below-max number)
(if (< number POS_MAX_SIGN)
number
(- number (* 2 POS_MAX_SIGN))))
;; Point -> Coordinate
;; Retrieve the x value of a Point. Just a convenience function that makes
;; reading the code a bit easier.
(check-expect (point-x (list 1 2 3)) 1)
(check-expect (point-x (list 90 80 70)) 90)
(define (point-x p) (first p))
;; Point -> Coordinate
;; Retrieve the y value of a Point. Just a convenience function that makes
;; reading the code a bit easier.
(check-expect (point-y (list 1 2 3)) 2)
(check-expect (point-y (list 90 80 70)) 80)
(define (point-y p) (first (rest p)))
;; Point -> Coordinate
;; Retrieve the z value of a Point. Just a convenience function that makes
;; reading the code a bit easier.
(check-expect (point-z (list 1 2 3)) 3)
(check-expect (point-z (list 90 80 70)) 70)
(define (point-z p) (first (rest (rest p))))
;; Coordinate Pos -> Pos
;; Takes a previously calculated coordinate and Pos number, return a new
;; Pos value with the previously calculated coordinate removed.
(check-expect (splice-pos -1 -16756737) -4091)
(check-expect (splice-pos 5 -4091) -1)
(check-expect (splice-pos 5 0) 0)
(define (splice-pos coordinate pos)
(ceiling (/ (- pos coordinate) POS_MODULO)))
;; Point -> Pos
;; Convert a Point into a Minetest block Pos
(check-expect (point->pos (list -1 5 -1)) -16756737)
(check-expect (point->pos (list -1 6 -1)) -16752641)
(check-expect (point->pos (list -1 5 0)) 20479)
(define (point->pos point)
(int64 (+ (point-x point)
(* (point-y point) POS_MODULO)
(* (point-z point) POS_BIGINT))))
;; Number -> Number
;; Presumably this forces a 64 bit integer. Again cheating a little
;; by simply stealing from the sample code without independent tests.
(define (int64 u)
(cond [(and (<= u (expt 2 63)) (>= u (* -1 (expt 2 63)))) u]
[(>= u (expt 2 63)) (int64 (- u (expt 2 63)))]
[else (int64 (+ u (expt 2 63)))]))