【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

glBegin(), glEnd(), glFrustum()

OpenGL ESはサブセットのため関数が少ないです。入門書などでもよく使われるglBegin()とglEnd()がないのはつらいので実装しました。glNormal3f()やglVertex3f()をキャッシュしてglNormalPointer()やglVertexPointer()に渡しています。GL_QUADS/GL_QUAD_STRIPは四角形を2つの三角形に分割することで実装しました。また、glFrustum()もglMultMatrixf()に行列を渡すことで実装しました。

以下にコードを示します(パブリックドメイン)。ラッピングを進めているためC#です。完成したラッパーはid:n7shi:20091006にあります。

public static class GL
{
    public const int GL_QUADS = 7;
    public const int GL_QUAD_STRIP = 8;

    private static GLenum _mode;
    private static List<float> vertices, normals;

    public static void glBegin(GLenum mode)
    {
        _mode = mode;
        normals = null;
        vertices = new List<float>();
    }

    private static float _nx, _ny, _nz;

    public static void glVertex3f(float x, float y, float z)
    {
        vertices.AddRange(new[] { x, y, z });
        if (normals != null) normals.AddRange(new[] { _nx, _ny, _nz });
    }

    public static new void glNormal3f(float nx, float ny, float nz)
    {
        if (vertices != null)
        {
            if (normals == null) normals = new List<float>();
            while (normals.Count < vertices.Count)
                normals.AddRange(new[] { _nx, _ny, _nz });
        }
        NativeWrappers.GL.glNormal3f(_nx = nx, _ny = ny, _nz = nz);
    }

    public static void glEnd()
    {
        float[] vs, ns = null;
        if (_mode == GL_QUADS)
        {
            _mode = GL_TRIANGLES;
            var vsrc = vertices.ToArray();
            vs = new float[vsrc.Length * 3 / 2];
            for (int i = 0, j = 0; i < vsrc.Length; i += 12, j += 18)
                QuadToTriangle(vsrc, i, vs, j);
            if (normals != null)
            {
                var nsrc = normals.ToArray();
                ns = new float[nsrc.Length * 3 / 2];
                for (int i = 0, j = 0; i < nsrc.Length; i += 12, j += 18)
                    QuadToTriangle(nsrc, i, vs, j);
            }
        }
        else
        {
            vs = vertices.ToArray();
            if (normals != null) ns = normals.ToArray();
            if (_mode == GL_QUAD_STRIP) _mode = GL_TRIANGLE_STRIP;
        }
        normals = null;
        vertices = null;
        glVertexPointer(3, GL_FLOAT, 0, vs);
        if (ns != null) glNormalPointer(GL_FLOAT, 0, ns);
        glEnableClientState(GL_VERTEX_ARRAY);
        if (ns != null) glEnableClientState(GL_NORMAL_ARRAY);
        glDrawArrays(_mode, 0, vs.Length / 3);
        glDisableClientState(GL_VERTEX_ARRAY);
        if (ns != null)
        {
            glDisableClientState(GL_NORMAL_ARRAY);
            glNormalPointer(GL_FLOAT, 0, null);
            NativeWrappers.GL.glNormal3f(_nx, _ny, _nz);
        }
    }

    private static void QuadToTriangle(float[] source, int sourceIndex, float[] dest, int destIndex)
    {
        Array.Copy(source, sourceIndex, dest, destIndex, 9);
        Array.Copy(source, sourceIndex, dest, destIndex + 9, 3);
        Array.Copy(source, sourceIndex + 6, dest, destIndex + 12, 6);
    }

    public static void glFrustum(
        double left, double right, double bottom, double top, double near_val, double far_val)
    {
        var m = new float[16];
        m[0] = (float)((2 * near_val) / (right - left));
        m[5] = (float)((2 * near_val) / (top - bottom));
        m[8] = (float)((right + left) / (right - left));
        m[9] = (float)((top + bottom) / (top - bottom));
        m[10] = (float)(-(far_val + near_val) / (far_val - near_val));
        m[11] = -1;
        m[14] = (float)(-(2 * far_val * near_val) / (far_val - near_val));
        glMultMatrixf(m);
    }
}

glEnd()の最後で glNormalPointer(GL_FLOAT, 0, null); としていますが、これを省略すると挙動がおかしくなります。ESではないOpenGLでは省略しても問題ないことから、Vincent ESの癖のようです。これに気付くまで4時間くらいハマりました。