Skip to content

Instantly share code, notes, and snippets.

@jakubtomsu
Created October 6, 2024 09:33
Show Gist options
  • Save jakubtomsu/c7ae9f9a222b31f52cb5e6f35f04d226 to your computer and use it in GitHub Desktop.
Save jakubtomsu/c7ae9f9a222b31f52cb5e6f35f04d226 to your computer and use it in GitHub Desktop.
Very simple parser for quake .map readable level data format (Valve version). I didn't fully test it.
package qmap
import "core:fmt"
import "core:os"
import "core:strconv"
import "core:strings"
// https://developer.valvesoftware.com/wiki/MAP_(file_format)
File :: struct {
version: int,
wad: string,
entities: [dynamic]Entity,
}
Entity :: struct {
classname: string,
values: map[string]string,
brushes: [dynamic]Brush,
}
Brush :: struct {
planes: [dynamic]Plane,
}
Plane :: struct {
points: [3]Vec3,
texture: string,
axis: [2]Vec3,
offset: [2]f32,
rotation: f32,
scale: [2]f32,
}
Vec3 :: [3]f32
Color :: [3]f32
Version :: enum {
Invalid,
Quake1,
Valve220,
}
version_from_int :: proc(v: int) -> Version {
switch v {
case 220:
return .Valve220
case:
return .Invalid
}
}
load :: proc(file_name: string, allocator := context.allocator) -> (result: File, ok: bool) {
context.allocator = allocator
data := os.read_entire_file_from_filename(file_name) or_return
return parse(string(data)), true
}
Parser :: struct {
iter: string,
}
parse :: proc(data: string, allocator := context.allocator) -> (result: File) {
context.allocator = allocator
entity: Entity
parser := Parser {
iter = data,
}
for line in strings.split_lines_iterator(&parser.iter) {
switch line[0] {
case '/':
continue
case '{':
entity := parse_entity(&parser)
append(&result.entities, entity)
}
}
return result
}
parse_entity :: proc(parser: ^Parser) -> (result: Entity) {
for line in strings.split_lines_iterator(&parser.iter) {
switch line[0] {
case '/':
continue
case '{':
brush := parse_brush(parser)
append(&result.brushes, brush)
case '}':
break
case '"':
temp := line[1:]
key_end_index := strings.index(temp, "\"")
assert(key_end_index > 0)
key := temp[:key_end_index]
val := temp[key_end_index + 1:]
val = strings.trim_left_space(val)
assert(val[0] == '\"' && val[len(val) - 1] == '\"')
val = val[1:][:len(val) - 2]
result.values[key] = val
}
}
return result
}
parse_brush :: proc(parser: ^Parser) -> (result: Brush) {
for line in strings.split_lines_iterator(&parser.iter) {
switch line[0] {
case '/':
continue
case '}':
break
case '(':
plane: Plane
temp := line
for i in 0 ..< 3 {
assert(temp[0] == '(')
temp = strings.trim_left_space(temp[1:])
for j in 0 ..< 3 {
val, num, _ := strconv.parse_f32_prefix(temp)
plane.points[i][j] = val
temp = temp[num + 1:]
}
assert(temp[0] == ')')
assert(temp[1] == ' ')
temp = temp[2:]
}
plane.texture = split_first(temp)
temp = temp[len(plane.texture) + 1:]
if temp[0] == '[' {
for i in 0 ..< 2 {
assert(temp[0] == '[')
assert(temp[1] == ' ')
temp = temp[2:]
for j in 0 ..< 4 {
val, num, _ := strconv.parse_f32_prefix(temp)
if j == 3 {
plane.offset[i] = val
} else {
plane.axis[i][j] = val
}
temp = temp[num + 1:]
}
assert(temp[0] == ']')
assert(temp[1] == ' ')
temp = temp[2:]
}
} else {
unimplemented()
}
val: f32
num: int
val, num, _ = strconv.parse_f32_prefix(temp)
plane.rotation = val
temp = temp[num + 1:]
val, num, _ = strconv.parse_f32_prefix(temp)
plane.scale[0] = val
temp = temp[num + 1:]
val, num, _ = strconv.parse_f32_prefix(temp)
plane.scale[1] = val
append(&result.planes, plane)
}
}
return result
}
split_first :: proc(s: string, sep: rune = ' ') -> string {
for r, i in s {
if r == sep {
return s[:i]
}
}
return s
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment