0B3

This page documents the .ob3 format, a bespoke format for 3D models created by Jagex. It is used by the RuneScape Classic engine since client version #74.

Note: There is also an earlier version of this format called .ob2.

OB3 Model Class

The following is the fully renamed client code used to represent OB3 models.

public class OB3Model {

    private static final int num_seq = 0xbc614e; // 12345678
    public int vertex_count;
    public int vertices_x[];
    public int vertices_y[];
    public int vertices_z[];
    public int face_count;
    public int face_vertex_count[];
    public int face_vertices[][];
    public int face_fill_back[];
    public int face_fill_front[];
    public int face_gouraud[];

    public OB3Model(byte data[], int offset) {
        int vertex_count = get_uint16(data, offset);
        offset += 2;
        int face_count = get_uint16(data, offset);
        offset += 2;

        vertices_x = new int[vertex_count];
        vertices_y = new int[vertex_count];
        vertices_z = new int[vertex_count];
        face_vertex_count = new int[face_count];
        face_vertices = new int[face_count][];
        face_fill_back = new int[face_count];
        face_fill_front = new int[face_count];
        face_gouraud = new int[face_count];

        for (int v = 0; v < vertex_count; v++) {
            vertices_x[v] = get_int16b(data, offset);
            offset += 2;
        }

        for (int v = 0; v < vertex_count; v++) {
            vertices_y[v] = get_int16b(data, offset);
            offset += 2;
        }

        for (int v = 0; v < vertex_count; v++) {
            vertices_z[v] = get_int16b(data, offset);
            offset += 2;
        }

        this.vertex_count = vertex_count;
        for (int f = 0; f < face_count; f++)
            face_vertex_count[f] = get_ubyte(data[offset++]);

        for (int f = 0; f < face_count; f++) {
            face_fill_back[f] = get_int16b(data, offset);
            offset += 2;
            if (face_fill_back[f] == 32767)
                face_fill_back[f] = num_seq;
        }

        for (int f = 0; f < face_count; f++) {
            face_fill_front[f] = get_int16b(data, offset);
            offset += 2;
            if (face_fill_front[f] == 32767)
                face_fill_front[f] = num_seq;
        }

        for (int f = 0; f < face_count; f++) {
            int i = get_ubyte(data[offset++]);
            if (i == 0)
                face_gouraud[f] = 0;
            else
                face_gouraud[f] = num_seq;
        }

        for (int f = 0; f < face_count; f++) {
            face_vertices[f] = new int[face_vertex_count[f]];
            for (int fv = 0; fv < face_vertex_count[f]; fv++) {
                if (vertex_count < 256) {
                    face_vertices[f][fv] = get_ubyte(data[offset++]);
                } else {
                    face_vertices[f][fv] = get_uint16(data, offset);
                    offset += 2;
                }
            }
        }

        this.face_count = face_count;
    }

    private static int get_ubyte(byte b) {
        return (b & 0xff);
    }

    private static int get_uint16(byte b[], int start) {
        return (get_ubyte(b[start]) << 8) + get_ubyte(b[start + 1]);
    }

    private static int get_int16b(byte b[], int start) {
        int i = get_ubyte(b[start]) * 256 + get_ubyte(b[start + 1]);
        if (i > 32767)
            i -= 0x10000;
        return i;
    }
}

Faces

A negative face_fill_back or face_fill_front value indicates a solid colour, whereas a positive value indicates a texture. The texture is defined by its offset in the client's texture array.

When converting to/from Wavefront OBJ format, remember that the OB3 face vertices are one less than the OBJ face vertices.

The below code will allow you to encode or decode colours in this format.

public static int decode_colour(int i) {
    i = -(i + 1);
    int r = i >> 10 & 0x1f;
    int g = i >> 5 & 0x1f;
    int b = i & 0x1f;
    return (r << 19) + (g << 11) + (b << 3);
}

public static int encode_colour(int r, int g, int b) {
    return -1 - (r / 8) * 1024 - (g / 8) * 32 - b / 8;
}