func TestRoundTrip(t *testing.T) { cam, _, _ := initScene() cx, cy, cz := 0.0, 0.0, 14.0 // camera location to cam.SetLocation(cx, cy, cz) // ...point directly at 0, 0, 0 // Create the matricies to go between clip and world space. toClip := lin.NewM4().Mult(cam.vm, cam.pm) toWorld := lin.NewM4().Mult(cam.ipm, cam.ivm) if !lin.NewM4().Mult(toClip, toWorld).Aeq(lin.M4I) { t.Errorf("Invalid world<->clip matricies") } // start with world coordinates carefully chosen to give x=1, y=1 clip values px, py := 6.002062, 3.751289 pnt := lin.NewV4().SetS(px, py, 0, 1) pnt.MultMv(toClip, pnt) if !lin.Aeq(pnt.X/pnt.W, 1) || !lin.Aeq(pnt.Y/pnt.W, 1) { t.Errorf("%f %f gave clip %f %f %f, expected (1 1 -0.071429)", px, py, pnt.X, pnt.Y, pnt.Z) } // now reverse back to world coordinates. pnt.MultMv(toWorld, pnt) if !lin.Aeq(pnt.X, px) || !lin.Aeq(pnt.Y, py) { t.Errorf("got point %f %f %f, expected x=%f y=%f", pnt.X, pnt.Y, pnt.Z, px, py) } }
// Test perspective and view inverses. func TestInverses(t *testing.T) { cam, _, _ := initScene() cam.AdjustPitch(45) cam.Move(0, -15, 15, cam.Lookat()) // the inverses multiplied with non-inverses should be the identity matrix. if !lin.NewM4().Mult(cam.pm, cam.ipm).Aeq(lin.M4I) { t.Error("Invalid inverse projection matrix") } if !lin.NewM4().Mult(cam.vm, cam.ivm).Aeq(lin.M4I) { t.Error("Invalid inverse view matrix") } }
// initScene is one time initialization that creates a single VAO func (tag *trtag) initScene() { tag.mvp64 = lin.NewM4() tag.persp = lin.NewM4().Persp(60, float64(600)/float64(600), 0.1, 50) tag.mvp = render.NewMvp() tag.initData() // Bind the OpenGL calls and dump some version info. gl.Init() fmt.Printf("%s %s", gl.GetString(gl.RENDERER), gl.GetString(gl.VERSION)) fmt.Printf(" GLSL %s\n", gl.GetString(gl.SHADING_LANGUAGE_VERSION)) // Gather the one scene into this one vertex array object. gl.GenVertexArrays(1, &tag.vao) gl.BindVertexArray(tag.vao) // create shaders tag.initShader() gl.UseProgram(tag.shaders) // vertex data. var vbuff uint32 gl.GenBuffers(1, &vbuff) gl.BindBuffer(gl.ARRAY_BUFFER, vbuff) gl.BufferData(gl.ARRAY_BUFFER, int64(len(tag.verticies)*4), gl.Pointer(&(tag.verticies[0])), gl.STATIC_DRAW) vattr := uint32(gl.GetAttribLocation(tag.shaders, "in_v")) gl.EnableVertexAttribArray(vattr) gl.VertexAttribPointer(vattr, 3, gl.FLOAT, false, 0, 0) // colour data. var cbuff uint32 gl.GenBuffers(1, &cbuff) gl.BindBuffer(gl.ARRAY_BUFFER, cbuff) gl.BufferData(gl.ARRAY_BUFFER, int64(len(tag.colour)*4), gl.Pointer(&(tag.colour[0])), gl.STATIC_DRAW) cattr := uint32(gl.GetAttribLocation(tag.shaders, "in_c")) gl.EnableVertexAttribArray(cattr) gl.VertexAttribPointer(cattr, 4, gl.FLOAT, false, 0, 0) // faces data. var ebuff uint32 gl.GenBuffers(1, &ebuff) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuff) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, int64(len(tag.faces)), gl.Pointer(&(tag.faces[0])), gl.STATIC_DRAW) // set some state that doesn't need to change during drawing. gl.ClearColor(0.0, 0.0, 0.0, 1.0) gl.Enable(gl.CULL_FACE) gl.CullFace(gl.BACK) }
// initScene is called once on startup to load the 3D data. func (ld *ldtag) initScene() { ld.persp = lin.NewM4() ld.mvp64 = lin.NewM4() ld.mvp = render.NewMvp() gl.Init() ldr := load.NewLoader() meshes, _ := ldr.Obj("monkey") mesh := meshes[0] ld.faceCount = int32(len(mesh.F)) // Gather the one scene into this one vertex array object. gl.GenVertexArrays(1, &ld.vao) gl.BindVertexArray(ld.vao) // vertex data. var vbuff uint32 gl.GenBuffers(1, &vbuff) gl.BindBuffer(gl.ARRAY_BUFFER, vbuff) gl.BufferData(gl.ARRAY_BUFFER, int64(len(mesh.V)*4), gl.Pointer(&(mesh.V[0])), gl.STATIC_DRAW) gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0) gl.EnableVertexAttribArray(0) // normal data. var nbuff uint32 gl.GenBuffers(1, &nbuff) gl.BindBuffer(gl.ARRAY_BUFFER, nbuff) gl.BufferData(gl.ARRAY_BUFFER, int64(len(mesh.N)*4), gl.Pointer(&(mesh.N[0])), gl.STATIC_DRAW) gl.VertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0) gl.EnableVertexAttribArray(1) // faces data, uint32 in this case, so 4 bytes per element. var fbuff uint32 gl.GenBuffers(1, &fbuff) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, fbuff) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, int64(len(mesh.F)*2), gl.Pointer(&(mesh.F[0])), gl.STATIC_DRAW) ld.initShader() gl.ClearColor(0.2, 0.2, 0.2, 1.0) gl.Enable(gl.DEPTH_TEST) gl.Enable(gl.CULL_FACE) // set the initial perspetive matrix. ld.resize(0, 0, 800, 600) }
// Concatenate each pose with the inverse base pose to avoid doing this at animation time. // If the joint has a parent, then it needs to be pre-concatenated with its parent's base pose. // Thus it all negates at animation time like so: // (parentPose * parentInverseBasePose) * (parentBasePose * childPose * childInverseBasePose) => // parentPose * (parentInverseBasePose * parentBasePose) * childPose * childInverseBasePose => // parentPose * childPose * childInverseBasePose func (l *loader) genFrame(scr *scratch, pt *transform, pcnt, numPoses, parent int) *lin.M4 { pt.q.Unit() m4 := lin.NewM4().SetQ(pt.q) m4.Transpose(m4).ScaleSM(pt.s.X, pt.s.Y, pt.s.Z) // apply scale before rotation. m4.Wx, m4.Wy, m4.Wz, m4.Ww = pt.t.X, pt.t.Y, pt.t.Z, 1 // translation added in, not multiplied. if parent >= 0 { // parentBasePose * childPose * childInverseBasePose return m4.Mult(scr.inversebaseframe[pcnt], m4).Mult(m4, scr.baseframe[parent]) } else { // childPose * childInverseBasePose return m4.Mult(scr.inversebaseframe[pcnt], m4) } }
// createBaseFrames constructs the joint transform base-pose matricies. // These are temporary structures used later in genFrame to prepare the // per-frame animation data. func (l *loader) createBaseFrames(iqd *IqData, poses []*transform, scr *scratch) { i3 := lin.NewM3() // scratch vx, vy, vz := lin.NewV3(), lin.NewV3(), lin.NewV3() // scratch numJoints := len(poses) scr.baseframe = make([]*lin.M4, numJoints) // joint transforms. scr.inversebaseframe = make([]*lin.M4, numJoints) // inverse transforms. for cnt := 0; cnt < numJoints; cnt++ { j := poses[cnt] // Get the joint transform. j.q.Unit() // ensure unit quaternion. m4 := lin.NewM4().SetQ(j.q) m4.Transpose(m4).ScaleSM(j.s.X, j.s.Y, j.s.Z) // apply scale before rotation. m4.Wx, m4.Wy, m4.Wz, m4.Ww = j.t.X, j.t.Y, j.t.Z, 1 // translation added in, not multiplied. scr.baseframe[cnt] = m4 // invert the joint transform for frame generation later on. i3.Inv(i3.SetM4(m4)) itx := -vx.SetS(i3.Xx, i3.Yx, i3.Zx).Dot(j.t) ity := -vy.SetS(i3.Xy, i3.Yy, i3.Zy).Dot(j.t) itz := -vz.SetS(i3.Xz, i3.Yz, i3.Zz).Dot(j.t) i4 := lin.NewM4() i4.Xx, i4.Xy, i4.Xz, i4.Xw = i3.Xx, i3.Xy, i3.Xz, 0 i4.Yx, i4.Yy, i4.Yz, i4.Yw = i3.Yx, i3.Yy, i3.Yz, 0 i4.Zx, i4.Zy, i4.Zz, i4.Zw = i3.Zx, i3.Zy, i3.Zz, 0 i4.Wx, i4.Wy, i4.Wz, i4.Ww = itx, ity, itz, 1 scr.inversebaseframe[cnt] = i4 // Combine the joint transforms and inverse transform with the parent transform. parent := iqd.Joints[cnt] if parent >= 0 { // childBasePose * parentBasePose scr.baseframe[cnt].Mult(scr.baseframe[cnt], scr.baseframe[parent]) // childInverseBasePose * parentInverseBasePose scr.inversebaseframe[cnt].Mult(scr.inversebaseframe[parent], scr.inversebaseframe[cnt]) } } }
// initScene is one time initialization that creates a single VAO func (sf *sftag) initScene() { sf.sTime = time.Now() sf.initData() // Bind the OpenGL calls and dump some version info. gl.Init() fmt.Printf("%s %s", gl.GetString(gl.RENDERER), gl.GetString(gl.VERSION)) fmt.Printf(" GLSL %s\n", gl.GetString(gl.SHADING_LANGUAGE_VERSION)) gl.GenVertexArrays(1, &sf.vao) gl.BindVertexArray(sf.vao) // vertex data. var vbuff uint32 gl.GenBuffers(1, &vbuff) gl.BindBuffer(gl.ARRAY_BUFFER, vbuff) gl.BufferData(gl.ARRAY_BUFFER, int64(len(sf.verticies)*4), gl.Pointer(&(sf.verticies[0])), gl.STATIC_DRAW) gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0) gl.EnableVertexAttribArray(0) // faces data. var ebuff uint32 gl.GenBuffers(1, &ebuff) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuff) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, int64(len(sf.faces)), gl.Pointer(&(sf.faces[0])), gl.STATIC_DRAW) // create texture and shaders after all the data has been set up. // renderer := render.New() shader := "fire" loader := load.NewLoader() vsrc, verr := loader.Vsh(shader) fsrc, ferr := loader.Fsh(shader) if verr == nil && ferr == nil { sf.shaders = gl.CreateProgram() if err := gl.BindProgram(sf.shaders, vsrc, fsrc); err != nil { fmt.Printf("Failed to create program: %s\n", err) } sf.mvpref = gl.GetUniformLocation(sf.shaders, "mvpm") sf.gTime = gl.GetUniformLocation(sf.shaders, "time") sf.sizes = gl.GetUniformLocation(sf.shaders, "screen") sf.mvp = render.NewMvp().Set(lin.NewM4().Ortho(0, 4, 0, 4, 0, 10)) // set some state that doesn't need to change during drawing. gl.ClearColor(0.0, 0.0, 0.0, 1.0) } }
// initRender is one time initialization that creates a single VAO // to display a single ray trace generated texture. func (rt *rtrace) initRender() { rt.verts = []float32{ // four verticies for a quad. 0, 0, 0, 4, 0, 0, 0, 4, 0, 4, 4, 0, } rt.faces = []uint8{ // create quad from 2 triangles. 0, 2, 1, 1, 2, 3, } rt.uvs = []float32{ // texture coordinates to sample the image. 0, 0, 1, 0, 0, 1, 1, 1, } // Start up OpenGL and create a single vertex array object. gl.Init() gl.GenVertexArrays(1, &rt.vao) gl.BindVertexArray(rt.vao) // vertex data. var vbuff uint32 gl.GenBuffers(1, &vbuff) gl.BindBuffer(gl.ARRAY_BUFFER, vbuff) gl.BufferData(gl.ARRAY_BUFFER, int64(len(rt.verts)*4), gl.Pointer(&(rt.verts[0])), gl.STATIC_DRAW) gl.VertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0) gl.EnableVertexAttribArray(0) // faces data. var ebuff uint32 gl.GenBuffers(1, &ebuff) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebuff) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, int64(len(rt.faces)), gl.Pointer(&(rt.faces[0])), gl.STATIC_DRAW) // texture coordatinates var tbuff uint32 gl.GenBuffers(1, &tbuff) gl.BindBuffer(gl.ARRAY_BUFFER, tbuff) gl.BufferData(gl.ARRAY_BUFFER, int64(len(rt.uvs)*4), gl.Pointer(&(rt.uvs[0])), gl.STATIC_DRAW) var tattr uint32 = 2 gl.VertexAttribPointer(tattr, 2, gl.FLOAT, false, 0, 0) gl.EnableVertexAttribArray(tattr) // use ray trace generated texture image. bounds := rt.img.Bounds() width, height := int32(bounds.Dx()), int32(bounds.Dy()) ptr := gl.Pointer(&(rt.img.Pix[0])) gl.GenTextures(1, &rt.texId) gl.BindTexture(gl.TEXTURE_2D, rt.texId) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, ptr) // texture sampling shader. loader := load.NewLoader() shader := "tuv" vsrc, verr := loader.Vsh(shader) fsrc, ferr := loader.Fsh(shader) if verr != nil || ferr != nil { log.Fatalf("Failed to load shaders %s %s\n", verr, ferr) } rt.shaders = gl.CreateProgram() if err := gl.BindProgram(rt.shaders, vsrc, fsrc); err != nil { log.Fatalf("Failed to create program: %s\n", err) } rt.mvpId = gl.GetUniformLocation(rt.shaders, "mvpm") rt.tex2D = gl.GetUniformLocation(rt.shaders, "sampler2D") rt.mvp = render.NewMvp().Set(lin.NewM4().Ortho(0, 4, 0, 4, 0, 10)) // set some state that doesn't need to change during drawing. gl.ClearColor(0.0, 0.0, 0.0, 1.0) }