Files
ordoor/internal/data/object.go

267 lines
7.8 KiB
Go

package data
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)
var (
// FIXME: My poor understanding of the .obj format prevents me from
// successfully loading these files
objBlacklist = []string{
/* Lots of unexpected magic values, disable the check for now
"15_rocks.obj", // Unexpected magic value: 19726564 (expected 21102801)
"2_cath.obj", // Unexpected magic value: 21364973 (expected 21102801)
"2nd_flor.obj", // Unexpected magic value: 22151377 (expected 21102801)
"3_cath.obj", // Unexpected magic value: 21102809 (expected 21102801)
"4_cath.obj", // Unexpected magic value: 21496017 (expected 21102801)
"BODIES.obj", // Unexpected magic value: 21627103 (expected 21102801)
"BRDG_TIL.OBJ", // Unexpected magic value: 17957074 (expected 21102801)
"Cheveron.obj", // Unexpected magic value: 10879118 (expected 21102801)
"Heavy_Plasma_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Heavy_Plasma_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Heavy_Plasma_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Heavy_Plasma_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Ht_drt.obj", // Unexpected magic value: 22806737 (expected 21102801)
"Ht_grs.obj", // Unexpected magic value: 22806737 (expected 21102801)
"IVY02.OBJ", // Unexpected magic value: 18481399 (expected 21102801)
"J_top2.obj", // Unexpected magic value: 22347993 (expected 21102801)
"Lascannon_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Lascannon_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Lascannon_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Lascannon_Pain_Mask.obj", // Unexpected magic value: 14811136 (expected 21102801)
"Man_Shadow.obj", // Unexpected magic value: 22479091 (expected 21102801)
"Melta_Effect.obj", // Unexpected magic value: 14745777 (expected 21102801)
"Melta_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Melta_Pain_Effect.obj", // Unexpected magic value: 14745777 (expected 21102801)
"Melta_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Multi_Melta_Effect.obj",
"Multi_Melta_Mask.obj",
"Multi_Melta_Pain_Effect.obj",
"Multi_Melta_Pain_Mask.obj",
"Plasma_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Plasma_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Plasma_Pain_Effect.obj", // Unexpected magic value: 14811345 (expected 21102801)
"Plasma_Pain_Mask.obj", // Unexpected magic value: 14811345 (expected 21102801)
"TZEENTCH.OBJ", // Unexpected magic value: 18088209 (expected 21102801)
"altar.obj", // Unexpected magic value: 18219222 (expected 21102801)
*/
"j_tree2.obj", // ObjectHeader is completely empty
"inven.obj", // Main header padding contains unknown values: [134744072 134744072 134744072]
}
objFrameMagic = uint32(0x014200D1)
)
func init() {
sort.Strings(objBlacklist)
}
type FrameHeader struct {
Magic uint32
Width uint16 // FIXME: I'm not certain this is what these are. If they are, they may be the wrong way around
Height uint16
Padding1 uint32 // I don't think this is used. Could be wrong.
PixelSize uint32 // Size of PixelData, excluding this frame header
Padding2 uint64 // I don't think this is used either. Could be wrong.
}
func (f FrameHeader) Check(expectedSize uint32) error {
// There seem to be different frame types, keyed by the magic value?
// if f.Magic != objFrameMagic {
// return fmt.Errorf("Unexpected magic value: %d (expected %d)", f.Magic, objFrameMagic)
// }
if f.Padding1 != 0 || f.Padding2 != 0 {
return fmt.Errorf("Frame header padding contains unknown values: %d %d", f.Padding1, f.Padding2)
}
// Remove 24 bytes from passed-in size to account for the header
if f.PixelSize != expectedSize-24 {
return fmt.Errorf("Advertised pixel size: %d differs from expected: %v", f.PixelSize, expectedSize-24)
}
return nil
}
type Frame struct {
FrameHeader
PixelData []byte
}
type frameInfoHeader struct {
Offset uint32 // Offset of the frame relative to the frame data segment
Size uint32 // Size of the frame in bytes, including the header
}
func (f frameInfoHeader) Check() error {
if f.Size < 24 {
return fmt.Errorf("Unexpected frame size: %d (expected >= 24)", f.Size)
}
return nil
}
// ObjectHeader totals 32 bytes on disk
type ObjectHeader struct {
NumFrames uint32 // How many frames does this object have?
MainHeaderSize uint32 // Number of bytes taken by this header. Should always be `32`
FrameInfoSize uint32 // Number of bytes taken up by the next block. 8 * NumFrames
FrameDataOffset uint32 // The starting point of the frame data
FrameDataSize uint32 // frame data should take up this many bytes
Padding [3]uint32 // Unused, as far as I can see
}
func (h ObjectHeader) ExpectedFrameInfoSize() uint32 {
return h.NumFrames * 8
}
func (h ObjectHeader) Check() error {
if h.MainHeaderSize != 32 {
return fmt.Errorf("Unexpected main header size: %d (expected 32)", h.MainHeaderSize)
}
if h.ExpectedFrameInfoSize() != h.FrameInfoSize {
return fmt.Errorf("Unexpected frame info size: %d (expected %d)", h.FrameInfoSize, h.ExpectedFrameInfoSize())
}
if h.Padding[0] != 0 || h.Padding[1] != 0 || h.Padding[2] != 0 {
return fmt.Errorf("Main header padding contains unknown values: %+v", h.Padding)
}
return nil
}
type Object struct {
ObjectHeader
Filename string
Frames []*Frame
}
func LoadObject(filename string) (*Object, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
out := &Object{Filename: filename}
if err := binary.Read(f, binary.LittleEndian, &out.ObjectHeader); err != nil {
return nil, err
}
if err := out.ObjectHeader.Check(); err != nil {
return nil, err
}
// Now load all frames into memory
framesInfo := make([]frameInfoHeader, out.NumFrames)
if err := binary.Read(f, binary.LittleEndian, &framesInfo); err != nil {
return nil, err
}
if _, err := f.Seek(int64(out.FrameDataOffset), io.SeekStart); err != nil {
return nil, err
}
// FIXME: this might - *might* - load interstitial data we don't really
// need, so wasting memory.
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
buf := bytes.NewReader(data)
for _, frameInfo := range framesInfo {
if err := frameInfo.Check(); err != nil {
return nil, err
}
if _, err := buf.Seek(int64(frameInfo.Offset), io.SeekStart); err != nil {
return nil, err
}
frame := &Frame{}
if err := binary.Read(buf, binary.LittleEndian, &frame.FrameHeader); err != nil {
return nil, err
}
if err := frame.Check(frameInfo.Size); err != nil {
return nil, err
}
// It's safe to assume that a `bytes.Reader` will always satisfy the
// requested read size.
frame.PixelData = make([]byte, frame.PixelSize)
if _, err := buf.Read(frame.PixelData); err != nil {
return nil, err
}
out.Frames = append(out.Frames, frame)
}
return out, nil
}
func readObjFrame(f io.Reader, obj *Object) error {
return nil
}
func LoadObjects(dir string) (map[string]*Object, error) {
fis, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
out := make(map[string]*Object, len(fis))
for _, fi := range fis {
filename := filepath.Join(dir, fi.Name())
basename := filepath.Base(filename)
extname := filepath.Ext(filename)
// Don't try to load non-.obj files
if !strings.EqualFold(extname, ".obj") {
continue
}
i := sort.SearchStrings(objBlacklist, basename)
if i >= len(objBlacklist) || objBlacklist[i] == basename {
continue
}
obj, err := LoadObject(filename)
if err != nil {
return nil, fmt.Errorf("%s: %v", filename, err)
}
out[basename] = obj
}
return out, nil
}