Adding upstream version 0.0~git20250520.a1d9079+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
590ac7ff5f
commit
20149b7f3a
456 changed files with 70406 additions and 0 deletions
2
exp/README
Normal file
2
exp/README
Normal file
|
@ -0,0 +1,2 @@
|
|||
golang.org/x/mobile/exp contains experimental packages for mobile app
|
||||
development.
|
220
exp/app/debug/fps.go
Normal file
220
exp/app/debug/fps.go
Normal file
|
@ -0,0 +1,220 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
// Package debug provides GL-based debugging tools for apps.
|
||||
package debug
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"time"
|
||||
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/gl/glutil"
|
||||
"golang.org/x/mobile/geom"
|
||||
)
|
||||
|
||||
// FPS draws a count of the frames rendered per second.
|
||||
type FPS struct {
|
||||
sz size.Event
|
||||
images *glutil.Images
|
||||
m *glutil.Image
|
||||
lastDraw time.Time
|
||||
// TODO: store *gl.Context
|
||||
}
|
||||
|
||||
// NewFPS creates an FPS tied to the current GL context.
|
||||
func NewFPS(images *glutil.Images) *FPS {
|
||||
return &FPS{
|
||||
lastDraw: time.Now(),
|
||||
images: images,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw draws the per second framerate in the bottom-left of the screen.
|
||||
func (p *FPS) Draw(sz size.Event) {
|
||||
const imgW, imgH = 7*(fontWidth+1) + 1, fontHeight + 2
|
||||
|
||||
if sz.WidthPx == 0 && sz.HeightPx == 0 {
|
||||
return
|
||||
}
|
||||
if p.sz != sz {
|
||||
p.sz = sz
|
||||
if p.m != nil {
|
||||
p.m.Release()
|
||||
}
|
||||
p.m = p.images.NewImage(imgW, imgH)
|
||||
}
|
||||
|
||||
display := [7]byte{
|
||||
4: 'F',
|
||||
5: 'P',
|
||||
6: 'S',
|
||||
}
|
||||
now := time.Now()
|
||||
f := 0
|
||||
if dur := now.Sub(p.lastDraw); dur > 0 {
|
||||
f = int(time.Second / dur)
|
||||
}
|
||||
display[2] = '0' + byte((f/1e0)%10)
|
||||
display[1] = '0' + byte((f/1e1)%10)
|
||||
display[0] = '0' + byte((f/1e2)%10)
|
||||
draw.Draw(p.m.RGBA, p.m.RGBA.Bounds(), image.White, image.Point{}, draw.Src)
|
||||
for i, c := range display {
|
||||
glyph := glyphs[c]
|
||||
if len(glyph) != fontWidth*fontHeight {
|
||||
continue
|
||||
}
|
||||
for y := 0; y < fontHeight; y++ {
|
||||
for x := 0; x < fontWidth; x++ {
|
||||
if glyph[fontWidth*y+x] == ' ' {
|
||||
continue
|
||||
}
|
||||
p.m.RGBA.SetRGBA((fontWidth+1)*i+x+1, y+1, color.RGBA{A: 0xff})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.m.Upload()
|
||||
p.m.Draw(
|
||||
sz,
|
||||
geom.Point{0, sz.HeightPt - imgH},
|
||||
geom.Point{imgW, sz.HeightPt - imgH},
|
||||
geom.Point{0, sz.HeightPt},
|
||||
p.m.RGBA.Bounds(),
|
||||
)
|
||||
|
||||
p.lastDraw = now
|
||||
}
|
||||
|
||||
func (f *FPS) Release() {
|
||||
if f.m != nil {
|
||||
f.m.Release()
|
||||
f.m = nil
|
||||
f.images = nil
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
fontWidth = 5
|
||||
fontHeight = 7
|
||||
)
|
||||
|
||||
// glyphs comes from the 6x10 fixed font from the plan9port:
|
||||
// https://github.com/9fans/plan9port/tree/master/font/fixed
|
||||
//
|
||||
// 6x10 becomes 5x7 because each glyph has a 1-pixel margin plus space for
|
||||
// descenders.
|
||||
//
|
||||
// Its README file says that those fonts were converted from XFree86, and are
|
||||
// in the public domain.
|
||||
var glyphs = [256]string{
|
||||
'0': "" +
|
||||
" X " +
|
||||
" X X " +
|
||||
"X X" +
|
||||
"X X" +
|
||||
"X X" +
|
||||
" X X " +
|
||||
" X ",
|
||||
'1': "" +
|
||||
" X " +
|
||||
" XX " +
|
||||
"X X " +
|
||||
" X " +
|
||||
" X " +
|
||||
" X " +
|
||||
"XXXXX",
|
||||
'2': "" +
|
||||
" XXX " +
|
||||
"X X" +
|
||||
" X" +
|
||||
" XX " +
|
||||
" X " +
|
||||
"X " +
|
||||
"XXXXX",
|
||||
'3': "" +
|
||||
"XXXXX" +
|
||||
" X" +
|
||||
" X " +
|
||||
" XX " +
|
||||
" X" +
|
||||
"X X" +
|
||||
" XXX ",
|
||||
'4': "" +
|
||||
" X " +
|
||||
" XX " +
|
||||
" X X " +
|
||||
"X X " +
|
||||
"XXXXX" +
|
||||
" X " +
|
||||
" X ",
|
||||
'5': "" +
|
||||
"XXXXX" +
|
||||
"X " +
|
||||
"X XX " +
|
||||
"XX X" +
|
||||
" X" +
|
||||
"X X" +
|
||||
" XXX ",
|
||||
'6': "" +
|
||||
" XX " +
|
||||
" X " +
|
||||
"X " +
|
||||
"X XX " +
|
||||
"XX X" +
|
||||
"X X" +
|
||||
" XXX ",
|
||||
'7': "" +
|
||||
"XXXXX" +
|
||||
" X" +
|
||||
" X " +
|
||||
" X " +
|
||||
" X " +
|
||||
" X " +
|
||||
" X ",
|
||||
'8': "" +
|
||||
" XXX " +
|
||||
"X X" +
|
||||
"X X" +
|
||||
" XXX " +
|
||||
"X X" +
|
||||
"X X" +
|
||||
" XXX ",
|
||||
'9': "" +
|
||||
" XXX " +
|
||||
"X X" +
|
||||
"X XX" +
|
||||
" XX X" +
|
||||
" X" +
|
||||
" X " +
|
||||
" XX ",
|
||||
'F': "" +
|
||||
"XXXXX" +
|
||||
"X " +
|
||||
"X " +
|
||||
"XXXX " +
|
||||
"X " +
|
||||
"X " +
|
||||
"X ",
|
||||
'P': "" +
|
||||
"XXXX " +
|
||||
"X X" +
|
||||
"X X" +
|
||||
"XXXX " +
|
||||
"X " +
|
||||
"X " +
|
||||
"X ",
|
||||
'S': "" +
|
||||
" XXX " +
|
||||
"X X" +
|
||||
"X " +
|
||||
" XXX " +
|
||||
" X" +
|
||||
"X X" +
|
||||
" XXX ",
|
||||
}
|
456
exp/audio/al/al.go
Normal file
456
exp/audio/al/al.go
Normal file
|
@ -0,0 +1,456 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
// Package al provides OpenAL Soft bindings for Go.
|
||||
//
|
||||
// Calls are not safe for concurrent use.
|
||||
//
|
||||
// More information about OpenAL Soft is available at
|
||||
// http://www.openal.org/documentation/openal-1.1-specification.pdf.
|
||||
//
|
||||
// In order to use this package on Linux desktop distros,
|
||||
// you will need OpenAL library as an external dependency.
|
||||
// On Ubuntu 14.04 'Trusty', you may have to install this library
|
||||
// by running the command below.
|
||||
//
|
||||
// sudo apt-get install libopenal-dev
|
||||
//
|
||||
// When compiled for Android, this package uses OpenAL Soft. Please add its
|
||||
// license file to the open source notices of your application.
|
||||
// OpenAL Soft's license file could be found at
|
||||
// http://repo.or.cz/w/openal-soft.git/blob/HEAD:/COPYING.
|
||||
package al
|
||||
|
||||
// Capability represents OpenAL extension capabilities.
|
||||
type Capability int32
|
||||
|
||||
// Enable enables a capability.
|
||||
func Enable(c Capability) {
|
||||
alEnable(int32(c))
|
||||
}
|
||||
|
||||
// Disable disables a capability.
|
||||
func Disable(c Capability) {
|
||||
alDisable(int32(c))
|
||||
}
|
||||
|
||||
// Enabled reports whether the specified capability is enabled.
|
||||
func Enabled(c Capability) bool {
|
||||
return alIsEnabled(int32(c))
|
||||
}
|
||||
|
||||
// Vector represents an vector in a Cartesian coordinate system.
|
||||
type Vector [3]float32
|
||||
|
||||
// Orientation represents the angular position of an object in a
|
||||
// right-handed Cartesian coordinate system.
|
||||
// A cross product between the forward and up vector returns a vector
|
||||
// that points to the right.
|
||||
type Orientation struct {
|
||||
// Forward vector is the direction that the object is looking at.
|
||||
Forward Vector
|
||||
// Up vector represents the rotation of the object.
|
||||
Up Vector
|
||||
}
|
||||
|
||||
func orientationFromSlice(v []float32) Orientation {
|
||||
return Orientation{
|
||||
Forward: Vector{v[0], v[1], v[2]},
|
||||
Up: Vector{v[3], v[4], v[5]},
|
||||
}
|
||||
}
|
||||
|
||||
func (v Orientation) slice() []float32 {
|
||||
return []float32{v.Forward[0], v.Forward[1], v.Forward[2], v.Up[0], v.Up[1], v.Up[2]}
|
||||
}
|
||||
|
||||
// Geti returns the int32 value of the given parameter.
|
||||
func Geti(param int) int32 {
|
||||
return alGetInteger(param)
|
||||
}
|
||||
|
||||
// Getiv returns the int32 vector value of the given parameter.
|
||||
func Getiv(param int, v []int32) {
|
||||
alGetIntegerv(param, v)
|
||||
}
|
||||
|
||||
// Getf returns the float32 value of the given parameter.
|
||||
func Getf(param int) float32 {
|
||||
return alGetFloat(param)
|
||||
}
|
||||
|
||||
// Getfv returns the float32 vector value of the given parameter.
|
||||
func Getfv(param int, v []float32) {
|
||||
alGetFloatv(param, v[:])
|
||||
}
|
||||
|
||||
// Getb returns the bool value of the given parameter.
|
||||
func Getb(param int) bool {
|
||||
return alGetBoolean(param)
|
||||
}
|
||||
|
||||
// Getbv returns the bool vector value of the given parameter.
|
||||
func Getbv(param int, v []bool) {
|
||||
alGetBooleanv(param, v)
|
||||
}
|
||||
|
||||
// GetString returns the string value of the given parameter.
|
||||
func GetString(param int) string {
|
||||
return alGetString(param)
|
||||
}
|
||||
|
||||
// DistanceModel returns the distance model.
|
||||
func DistanceModel() int32 {
|
||||
return Geti(paramDistanceModel)
|
||||
}
|
||||
|
||||
// SetDistanceModel sets the distance model.
|
||||
func SetDistanceModel(v int32) {
|
||||
alDistanceModel(v)
|
||||
}
|
||||
|
||||
// DopplerFactor returns the doppler factor.
|
||||
func DopplerFactor() float32 {
|
||||
return Getf(paramDopplerFactor)
|
||||
}
|
||||
|
||||
// SetDopplerFactor sets the doppler factor.
|
||||
func SetDopplerFactor(v float32) {
|
||||
alDopplerFactor(v)
|
||||
}
|
||||
|
||||
// DopplerVelocity returns the doppler velocity.
|
||||
func DopplerVelocity() float32 {
|
||||
return Getf(paramDopplerVelocity)
|
||||
}
|
||||
|
||||
// SetDopplerVelocity sets the doppler velocity.
|
||||
func SetDopplerVelocity(v float32) {
|
||||
alDopplerVelocity(v)
|
||||
}
|
||||
|
||||
// SpeedOfSound is the speed of sound in meters per second (m/s).
|
||||
func SpeedOfSound() float32 {
|
||||
return Getf(paramSpeedOfSound)
|
||||
}
|
||||
|
||||
// SetSpeedOfSound sets the speed of sound, its unit should be meters per second (m/s).
|
||||
func SetSpeedOfSound(v float32) {
|
||||
alSpeedOfSound(v)
|
||||
}
|
||||
|
||||
// Vendor returns the vendor.
|
||||
func Vendor() string {
|
||||
return GetString(paramVendor)
|
||||
}
|
||||
|
||||
// Version returns the version string.
|
||||
func Version() string {
|
||||
return GetString(paramVersion)
|
||||
}
|
||||
|
||||
// Renderer returns the renderer information.
|
||||
func Renderer() string {
|
||||
return GetString(paramRenderer)
|
||||
}
|
||||
|
||||
// Extensions returns the enabled extensions.
|
||||
func Extensions() string {
|
||||
return GetString(paramExtensions)
|
||||
}
|
||||
|
||||
// Error returns the most recently generated error.
|
||||
func Error() int32 {
|
||||
return alGetError()
|
||||
}
|
||||
|
||||
// Source represents an individual sound source in 3D-space.
|
||||
// They take PCM data, apply modifications and then submit them to
|
||||
// be mixed according to their spatial location.
|
||||
type Source uint32
|
||||
|
||||
// GenSources generates n new sources. These sources should be deleted
|
||||
// once they are not in use.
|
||||
func GenSources(n int) []Source {
|
||||
return alGenSources(n)
|
||||
}
|
||||
|
||||
// PlaySources plays the sources.
|
||||
func PlaySources(source ...Source) {
|
||||
alSourcePlayv(source)
|
||||
}
|
||||
|
||||
// PauseSources pauses the sources.
|
||||
func PauseSources(source ...Source) {
|
||||
alSourcePausev(source)
|
||||
}
|
||||
|
||||
// StopSources stops the sources.
|
||||
func StopSources(source ...Source) {
|
||||
alSourceStopv(source)
|
||||
}
|
||||
|
||||
// RewindSources rewinds the sources to their beginning positions.
|
||||
func RewindSources(source ...Source) {
|
||||
alSourceRewindv(source)
|
||||
}
|
||||
|
||||
// DeleteSources deletes the sources.
|
||||
func DeleteSources(source ...Source) {
|
||||
alDeleteSources(source)
|
||||
}
|
||||
|
||||
// Gain returns the source gain.
|
||||
func (s Source) Gain() float32 {
|
||||
return s.Getf(paramGain)
|
||||
}
|
||||
|
||||
// SetGain sets the source gain.
|
||||
func (s Source) SetGain(v float32) {
|
||||
s.Setf(paramGain, v)
|
||||
}
|
||||
|
||||
// MinGain returns the source's minimum gain setting.
|
||||
func (s Source) MinGain() float32 {
|
||||
return s.Getf(paramMinGain)
|
||||
}
|
||||
|
||||
// SetMinGain sets the source's minimum gain setting.
|
||||
func (s Source) SetMinGain(v float32) {
|
||||
s.Setf(paramMinGain, v)
|
||||
}
|
||||
|
||||
// MaxGain returns the source's maximum gain setting.
|
||||
func (s Source) MaxGain() float32 {
|
||||
return s.Getf(paramMaxGain)
|
||||
}
|
||||
|
||||
// SetMaxGain sets the source's maximum gain setting.
|
||||
func (s Source) SetMaxGain(v float32) {
|
||||
s.Setf(paramMaxGain, v)
|
||||
}
|
||||
|
||||
// Position returns the position of the source.
|
||||
func (s Source) Position() Vector {
|
||||
v := Vector{}
|
||||
s.Getfv(paramPosition, v[:])
|
||||
return v
|
||||
}
|
||||
|
||||
// SetPosition sets the position of the source.
|
||||
func (s Source) SetPosition(v Vector) {
|
||||
s.Setfv(paramPosition, v[:])
|
||||
}
|
||||
|
||||
// Velocity returns the source's velocity.
|
||||
func (s Source) Velocity() Vector {
|
||||
v := Vector{}
|
||||
s.Getfv(paramVelocity, v[:])
|
||||
return v
|
||||
}
|
||||
|
||||
// SetVelocity sets the source's velocity.
|
||||
func (s Source) SetVelocity(v Vector) {
|
||||
s.Setfv(paramVelocity, v[:])
|
||||
}
|
||||
|
||||
// Orientation returns the orientation of the source.
|
||||
func (s Source) Orientation() Orientation {
|
||||
v := make([]float32, 6)
|
||||
s.Getfv(paramOrientation, v)
|
||||
return orientationFromSlice(v)
|
||||
}
|
||||
|
||||
// SetOrientation sets the orientation of the source.
|
||||
func (s Source) SetOrientation(o Orientation) {
|
||||
s.Setfv(paramOrientation, o.slice())
|
||||
}
|
||||
|
||||
// State returns the playing state of the source.
|
||||
func (s Source) State() int32 {
|
||||
return s.Geti(paramSourceState)
|
||||
}
|
||||
|
||||
// BuffersQueued returns the number of the queued buffers.
|
||||
func (s Source) BuffersQueued() int32 {
|
||||
return s.Geti(paramBuffersQueued)
|
||||
}
|
||||
|
||||
// BuffersProcessed returns the number of the processed buffers.
|
||||
func (s Source) BuffersProcessed() int32 {
|
||||
return s.Geti(paramBuffersProcessed)
|
||||
}
|
||||
|
||||
// OffsetSeconds returns the current playback position of the source in seconds.
|
||||
func (s Source) OffsetSeconds() int32 {
|
||||
return s.Geti(paramSecOffset)
|
||||
}
|
||||
|
||||
// OffsetSample returns the sample offset of the current playback position.
|
||||
func (s Source) OffsetSample() int32 {
|
||||
return s.Geti(paramSampleOffset)
|
||||
}
|
||||
|
||||
// OffsetByte returns the byte offset of the current playback position.
|
||||
func (s Source) OffsetByte() int32 {
|
||||
return s.Geti(paramByteOffset)
|
||||
}
|
||||
|
||||
// Geti returns the int32 value of the given parameter.
|
||||
func (s Source) Geti(param int) int32 {
|
||||
return alGetSourcei(s, param)
|
||||
}
|
||||
|
||||
// Getf returns the float32 value of the given parameter.
|
||||
func (s Source) Getf(param int) float32 {
|
||||
return alGetSourcef(s, param)
|
||||
}
|
||||
|
||||
// Getfv returns the float32 vector value of the given parameter.
|
||||
func (s Source) Getfv(param int, v []float32) {
|
||||
alGetSourcefv(s, param, v)
|
||||
}
|
||||
|
||||
// Seti sets an int32 value for the given parameter.
|
||||
func (s Source) Seti(param int, v int32) {
|
||||
alSourcei(s, param, v)
|
||||
}
|
||||
|
||||
// Setf sets a float32 value for the given parameter.
|
||||
func (s Source) Setf(param int, v float32) {
|
||||
alSourcef(s, param, v)
|
||||
}
|
||||
|
||||
// Setfv sets a float32 vector value for the given parameter.
|
||||
func (s Source) Setfv(param int, v []float32) {
|
||||
alSourcefv(s, param, v)
|
||||
}
|
||||
|
||||
// QueueBuffers adds the buffers to the buffer queue.
|
||||
func (s Source) QueueBuffers(buffer ...Buffer) {
|
||||
alSourceQueueBuffers(s, buffer)
|
||||
}
|
||||
|
||||
// UnqueueBuffers removes the specified buffers from the buffer queue.
|
||||
func (s Source) UnqueueBuffers(buffer ...Buffer) {
|
||||
alSourceUnqueueBuffers(s, buffer)
|
||||
}
|
||||
|
||||
// ListenerGain returns the total gain applied to the final mix.
|
||||
func ListenerGain() float32 {
|
||||
return GetListenerf(paramGain)
|
||||
}
|
||||
|
||||
// ListenerPosition returns the position of the listener.
|
||||
func ListenerPosition() Vector {
|
||||
v := Vector{}
|
||||
GetListenerfv(paramPosition, v[:])
|
||||
return v
|
||||
}
|
||||
|
||||
// ListenerVelocity returns the velocity of the listener.
|
||||
func ListenerVelocity() Vector {
|
||||
v := Vector{}
|
||||
GetListenerfv(paramVelocity, v[:])
|
||||
return v
|
||||
}
|
||||
|
||||
// ListenerOrientation returns the orientation of the listener.
|
||||
func ListenerOrientation() Orientation {
|
||||
v := make([]float32, 6)
|
||||
GetListenerfv(paramOrientation, v)
|
||||
return orientationFromSlice(v)
|
||||
}
|
||||
|
||||
// SetListenerGain sets the total gain that will be applied to the final mix.
|
||||
func SetListenerGain(v float32) {
|
||||
SetListenerf(paramGain, v)
|
||||
}
|
||||
|
||||
// SetListenerPosition sets the position of the listener.
|
||||
func SetListenerPosition(v Vector) {
|
||||
SetListenerfv(paramPosition, v[:])
|
||||
}
|
||||
|
||||
// SetListenerVelocity sets the velocity of the listener.
|
||||
func SetListenerVelocity(v Vector) {
|
||||
SetListenerfv(paramVelocity, v[:])
|
||||
}
|
||||
|
||||
// SetListenerOrientation sets the orientation of the listener.
|
||||
func SetListenerOrientation(v Orientation) {
|
||||
SetListenerfv(paramOrientation, v.slice())
|
||||
}
|
||||
|
||||
// GetListenerf returns the float32 value of the listener parameter.
|
||||
func GetListenerf(param int) float32 {
|
||||
return alGetListenerf(param)
|
||||
}
|
||||
|
||||
// GetListenerfv returns the float32 vector value of the listener parameter.
|
||||
func GetListenerfv(param int, v []float32) {
|
||||
alGetListenerfv(param, v)
|
||||
}
|
||||
|
||||
// SetListenerf sets the float32 value for the listener parameter.
|
||||
func SetListenerf(param int, v float32) {
|
||||
alListenerf(param, v)
|
||||
}
|
||||
|
||||
// SetListenerfv sets the float32 vector value of the listener parameter.
|
||||
func SetListenerfv(param int, v []float32) {
|
||||
alListenerfv(param, v)
|
||||
}
|
||||
|
||||
// A buffer represents a chunk of PCM audio data that could be buffered to an audio
|
||||
// source. A single buffer could be shared between multiple sources.
|
||||
type Buffer uint32
|
||||
|
||||
// GenBuffers generates n new buffers. The generated buffers should be deleted
|
||||
// once they are no longer in use.
|
||||
func GenBuffers(n int) []Buffer {
|
||||
return alGenBuffers(n)
|
||||
}
|
||||
|
||||
// DeleteBuffers deletes the buffers.
|
||||
func DeleteBuffers(buffer ...Buffer) {
|
||||
alDeleteBuffers(buffer)
|
||||
}
|
||||
|
||||
// Geti returns the int32 value of the given parameter.
|
||||
func (b Buffer) Geti(param int) int32 {
|
||||
return b.Geti(param)
|
||||
}
|
||||
|
||||
// Frequency returns the frequency of the buffer data in Hertz (Hz).
|
||||
func (b Buffer) Frequency() int32 {
|
||||
return b.Geti(paramFreq)
|
||||
}
|
||||
|
||||
// Bits return the number of bits used to represent a sample.
|
||||
func (b Buffer) Bits() int32 {
|
||||
return b.Geti(paramBits)
|
||||
}
|
||||
|
||||
// Channels return the number of the audio channels.
|
||||
func (b Buffer) Channels() int32 {
|
||||
return b.Geti(paramChannels)
|
||||
}
|
||||
|
||||
// Size returns the size of the data.
|
||||
func (b Buffer) Size() int32 {
|
||||
return b.Geti(paramSize)
|
||||
}
|
||||
|
||||
// BufferData buffers PCM data to the current buffer.
|
||||
func (b Buffer) BufferData(format uint32, data []byte, freq int32) {
|
||||
alBufferData(b, format, data, freq)
|
||||
}
|
||||
|
||||
// Valid reports whether the buffer exists and is valid.
|
||||
func (b Buffer) Valid() bool {
|
||||
return alIsBuffer(b)
|
||||
}
|
486
exp/audio/al/al_android.go
Normal file
486
exp/audio/al/al_android.go
Normal file
|
@ -0,0 +1,486 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package al
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include <jni.h>
|
||||
#include <limits.h>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
|
||||
void al_init(uintptr_t java_vm, uintptr_t jni_env, jobject context, void** handle) {
|
||||
JavaVM* vm = (JavaVM*)java_vm;
|
||||
JNIEnv* env = (JNIEnv*)jni_env;
|
||||
|
||||
jclass android_content_Context = (*env)->FindClass(env, "android/content/Context");
|
||||
jmethodID get_application_info = (*env)->GetMethodID(env, android_content_Context, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
|
||||
jclass android_content_pm_ApplicationInfo = (*env)->FindClass(env, "android/content/pm/ApplicationInfo");
|
||||
jfieldID native_library_dir = (*env)->GetFieldID(env, android_content_pm_ApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
|
||||
jobject app_info = (*env)->CallObjectMethod(env, context, get_application_info);
|
||||
jstring native_dir = (*env)->GetObjectField(env, app_info, native_library_dir);
|
||||
const char *cnative_dir = (*env)->GetStringUTFChars(env, native_dir, 0);
|
||||
|
||||
char lib_path[PATH_MAX] = "";
|
||||
strlcat(lib_path, cnative_dir, sizeof(lib_path));
|
||||
strlcat(lib_path, "/libopenal.so", sizeof(lib_path));
|
||||
*handle = dlopen(lib_path, RTLD_LAZY);
|
||||
(*env)->ReleaseStringUTFChars(env, native_dir, cnative_dir);
|
||||
}
|
||||
|
||||
void call_alEnable(LPALENABLE fn, ALenum capability) {
|
||||
fn(capability);
|
||||
}
|
||||
|
||||
void call_alDisable(LPALDISABLE fn, ALenum capability) {
|
||||
fn(capability);
|
||||
}
|
||||
|
||||
ALboolean call_alIsEnabled(LPALISENABLED fn, ALenum capability) {
|
||||
return fn(capability);
|
||||
}
|
||||
|
||||
ALint call_alGetInteger(LPALGETINTEGER fn, ALenum p) {
|
||||
return fn(p);
|
||||
}
|
||||
|
||||
void call_alGetIntegerv(LPALGETINTEGERV fn, ALenum p, ALint* v) {
|
||||
fn(p, v);
|
||||
}
|
||||
|
||||
ALfloat call_alGetFloat(LPALGETFLOAT fn, ALenum p) {
|
||||
return fn(p);
|
||||
}
|
||||
|
||||
void call_alGetFloatv(LPALGETFLOATV fn, ALenum p, ALfloat* v) {
|
||||
fn(p, v);
|
||||
}
|
||||
|
||||
ALboolean call_alGetBoolean(LPALGETBOOLEAN fn, ALenum p) {
|
||||
return fn(p);
|
||||
}
|
||||
|
||||
void call_alGetBooleanv(LPALGETBOOLEANV fn, ALenum p, ALboolean* v) {
|
||||
fn(p, v);
|
||||
}
|
||||
|
||||
const char* call_alGetString(LPALGETSTRING fn, ALenum p) {
|
||||
return fn(p);
|
||||
}
|
||||
|
||||
void call_alDistanceModel(LPALDISTANCEMODEL fn, ALenum v) {
|
||||
fn(v);
|
||||
}
|
||||
|
||||
void call_alDopplerFactor(LPALDOPPLERFACTOR fn, ALfloat v) {
|
||||
fn(v);
|
||||
}
|
||||
|
||||
void call_alDopplerVelocity(LPALDOPPLERVELOCITY fn, ALfloat v) {
|
||||
fn(v);
|
||||
}
|
||||
|
||||
void call_alSpeedOfSound(LPALSPEEDOFSOUND fn, ALfloat v) {
|
||||
fn(v);
|
||||
}
|
||||
|
||||
ALint call_alGetError(LPALGETERROR fn) {
|
||||
return fn();
|
||||
}
|
||||
|
||||
void call_alGenSources(LPALGENSOURCES fn, ALsizei n, ALuint* s) {
|
||||
fn(n, s);
|
||||
}
|
||||
|
||||
void call_alSourcePlayv(LPALSOURCEPLAYV fn, ALsizei n, const ALuint* s) {
|
||||
fn(n, s);
|
||||
}
|
||||
|
||||
void call_alSourcePausev(LPALSOURCEPAUSEV fn, ALsizei n, const ALuint* s) {
|
||||
fn(n, s);
|
||||
}
|
||||
|
||||
void call_alSourceStopv(LPALSOURCESTOPV fn, ALsizei n, const ALuint* s) {
|
||||
fn(n, s);
|
||||
}
|
||||
|
||||
void call_alSourceRewindv(LPALSOURCEREWINDV fn, ALsizei n, const ALuint* s) {
|
||||
fn(n, s);
|
||||
}
|
||||
|
||||
void call_alDeleteSources(LPALDELETESOURCES fn, ALsizei n, const ALuint* s) {
|
||||
fn(n, s);
|
||||
}
|
||||
|
||||
void call_alGetSourcei(LPALGETSOURCEI fn, ALuint s, ALenum k, ALint* v) {
|
||||
fn(s, k, v);
|
||||
}
|
||||
|
||||
void call_alGetSourcef(LPALGETSOURCEF fn, ALuint s, ALenum k, ALfloat* v) {
|
||||
fn(s, k, v);
|
||||
}
|
||||
|
||||
void call_alGetSourcefv(LPALGETSOURCEFV fn, ALuint s, ALenum k, ALfloat* v) {
|
||||
fn(s, k, v);
|
||||
}
|
||||
|
||||
void call_alSourcei(LPALSOURCEI fn, ALuint s, ALenum k, ALint v) {
|
||||
fn(s, k, v);
|
||||
}
|
||||
|
||||
void call_alSourcef(LPALSOURCEF fn, ALuint s, ALenum k, ALfloat v) {
|
||||
fn(s, k, v);
|
||||
}
|
||||
|
||||
void call_alSourcefv(LPALSOURCEFV fn, ALuint s, ALenum k, const ALfloat* v) {
|
||||
fn(s, k, v);
|
||||
}
|
||||
|
||||
void call_alSourceQueueBuffers(LPALSOURCEQUEUEBUFFERS fn, ALuint s, ALsizei n, const ALuint* b) {
|
||||
fn(s, n, b);
|
||||
}
|
||||
|
||||
void call_alSourceUnqueueBuffers(LPALSOURCEUNQUEUEBUFFERS fn, ALuint s, ALsizei n, ALuint* b) {
|
||||
fn(s, n, b);
|
||||
}
|
||||
|
||||
void call_alGetListenerf(LPALGETLISTENERF fn, ALenum k, ALfloat* v) {
|
||||
fn(k, v);
|
||||
}
|
||||
|
||||
void call_alGetListenerfv(LPALLISTENERFV fn, ALenum k, ALfloat* v) {
|
||||
fn(k, v);
|
||||
}
|
||||
|
||||
void call_alListenerf(LPALLISTENERF fn, ALenum k, ALfloat v) {
|
||||
fn(k, v);
|
||||
}
|
||||
|
||||
void call_alListenerfv(LPALLISTENERFV fn, ALenum k, const ALfloat* v) {
|
||||
fn(k, v);
|
||||
}
|
||||
|
||||
void call_alGenBuffers(LPALGENBUFFERS fn, ALsizei n, ALuint* v) {
|
||||
fn(n, v);
|
||||
}
|
||||
|
||||
void call_alDeleteBuffers(LPALDELETEBUFFERS fn, ALsizei n, ALuint* v) {
|
||||
fn(n, v);
|
||||
}
|
||||
|
||||
void call_alGetBufferi(LPALGETBUFFERI fn, ALuint b, ALenum k, ALint* v) {
|
||||
fn(b, k, v);
|
||||
}
|
||||
|
||||
void call_alBufferData(LPALBUFFERDATA fn, ALuint b, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq) {
|
||||
fn(b, format, data, size, freq);
|
||||
}
|
||||
|
||||
ALboolean call_alIsBuffer(LPALISBUFFER fn, ALuint b) {
|
||||
return fn(b);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/mobile/internal/mobileinit"
|
||||
)
|
||||
|
||||
var (
|
||||
alHandle unsafe.Pointer
|
||||
alEnableFunc C.LPALENABLE
|
||||
alDisableFunc C.LPALDISABLE
|
||||
alIsEnabledFunc C.LPALISENABLED
|
||||
alGetIntegerFunc C.LPALGETINTEGER
|
||||
alGetIntegervFunc C.LPALGETINTEGERV
|
||||
alGetFloatFunc C.LPALGETFLOAT
|
||||
alGetFloatvFunc C.LPALGETFLOATV
|
||||
alGetBooleanFunc C.LPALGETBOOLEAN
|
||||
alGetBooleanvFunc C.LPALGETBOOLEANV
|
||||
alGetStringFunc C.LPALGETSTRING
|
||||
alDistanceModelFunc C.LPALDISTANCEMODEL
|
||||
alDopplerFactorFunc C.LPALDOPPLERFACTOR
|
||||
alDopplerVelocityFunc C.LPALDOPPLERVELOCITY
|
||||
alSpeedOfSoundFunc C.LPALSPEEDOFSOUND
|
||||
alGetErrorFunc C.LPALGETERROR
|
||||
alGenSourcesFunc C.LPALGENSOURCES
|
||||
alSourcePlayvFunc C.LPALSOURCEPLAYV
|
||||
alSourcePausevFunc C.LPALSOURCEPAUSEV
|
||||
alSourceStopvFunc C.LPALSOURCESTOPV
|
||||
alSourceRewindvFunc C.LPALSOURCEREWINDV
|
||||
alDeleteSourcesFunc C.LPALDELETESOURCES
|
||||
alGetSourceiFunc C.LPALGETSOURCEI
|
||||
alGetSourcefFunc C.LPALGETSOURCEF
|
||||
alGetSourcefvFunc C.LPALGETSOURCEFV
|
||||
alSourceiFunc C.LPALSOURCEI
|
||||
alSourcefFunc C.LPALSOURCEF
|
||||
alSourcefvFunc C.LPALSOURCEFV
|
||||
alSourceQueueBuffersFunc C.LPALSOURCEQUEUEBUFFERS
|
||||
alSourceUnqueueBuffersFunc C.LPALSOURCEUNQUEUEBUFFERS
|
||||
alGetListenerfFunc C.LPALGETLISTENERF
|
||||
alGetListenerfvFunc C.LPALGETLISTENERFV
|
||||
alListenerfFunc C.LPALLISTENERF
|
||||
alListenerfvFunc C.LPALLISTENERFV
|
||||
alGenBuffersFunc C.LPALGENBUFFERS
|
||||
alDeleteBuffersFunc C.LPALDELETEBUFFERS
|
||||
alGetBufferiFunc C.LPALGETBUFFERI
|
||||
alBufferDataFunc C.LPALBUFFERDATA
|
||||
alIsBufferFunc C.LPALISBUFFER
|
||||
|
||||
alcGetErrorFunc C.LPALCGETERROR
|
||||
alcOpenDeviceFunc C.LPALCOPENDEVICE
|
||||
alcCloseDeviceFunc C.LPALCCLOSEDEVICE
|
||||
alcCreateContextFunc C.LPALCCREATECONTEXT
|
||||
alcMakeContextCurrentFunc C.LPALCMAKECONTEXTCURRENT
|
||||
alcDestroyContextFunc C.LPALCDESTROYCONTEXT
|
||||
)
|
||||
|
||||
func initAL() {
|
||||
err := mobileinit.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
C.al_init(C.uintptr_t(vm), C.uintptr_t(env), C.jobject(ctx), &alHandle)
|
||||
if alHandle == nil {
|
||||
return errors.New("al: cannot load libopenal.so")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("al: %v", err)
|
||||
}
|
||||
|
||||
alEnableFunc = C.LPALENABLE(fn("alEnable"))
|
||||
alDisableFunc = C.LPALDISABLE(fn("alDisable"))
|
||||
alIsEnabledFunc = C.LPALISENABLED(fn("alIsEnabled"))
|
||||
alGetIntegerFunc = C.LPALGETINTEGER(fn("alGetInteger"))
|
||||
alGetIntegervFunc = C.LPALGETINTEGERV(fn("alGetIntegerv"))
|
||||
alGetFloatFunc = C.LPALGETFLOAT(fn("alGetFloat"))
|
||||
alGetFloatvFunc = C.LPALGETFLOATV(fn("alGetFloatv"))
|
||||
alGetBooleanFunc = C.LPALGETBOOLEAN(fn("alGetBoolean"))
|
||||
alGetBooleanvFunc = C.LPALGETBOOLEANV(fn("alGetBooleanv"))
|
||||
alGetStringFunc = C.LPALGETSTRING(fn("alGetString"))
|
||||
alDistanceModelFunc = C.LPALDISTANCEMODEL(fn("alDistanceModel"))
|
||||
alDopplerFactorFunc = C.LPALDOPPLERFACTOR(fn("alDopplerFactor"))
|
||||
alDopplerVelocityFunc = C.LPALDOPPLERVELOCITY(fn("alDopplerVelocity"))
|
||||
alSpeedOfSoundFunc = C.LPALSPEEDOFSOUND(fn("alSpeedOfSound"))
|
||||
alGetErrorFunc = C.LPALGETERROR(fn("alGetError"))
|
||||
alGenSourcesFunc = C.LPALGENSOURCES(fn("alGenSources"))
|
||||
alSourcePlayvFunc = C.LPALSOURCEPLAYV(fn("alSourcePlayv"))
|
||||
alSourcePausevFunc = C.LPALSOURCEPAUSEV(fn("alSourcePausev"))
|
||||
alSourceStopvFunc = C.LPALSOURCESTOPV(fn("alSourceStopv"))
|
||||
alSourceRewindvFunc = C.LPALSOURCEREWINDV(fn("alSourceRewindv"))
|
||||
alDeleteSourcesFunc = C.LPALDELETESOURCES(fn("alDeleteSources"))
|
||||
alGetSourceiFunc = C.LPALGETSOURCEI(fn("alGetSourcei"))
|
||||
alGetSourcefFunc = C.LPALGETSOURCEF(fn("alGetSourcef"))
|
||||
alGetSourcefvFunc = C.LPALGETSOURCEFV(fn("alGetSourcefv"))
|
||||
alSourceiFunc = C.LPALSOURCEI(fn("alSourcei"))
|
||||
alSourcefFunc = C.LPALSOURCEF(fn("alSourcef"))
|
||||
alSourcefvFunc = C.LPALSOURCEFV(fn("alSourcefv"))
|
||||
alSourceQueueBuffersFunc = C.LPALSOURCEQUEUEBUFFERS(fn("alSourceQueueBuffers"))
|
||||
alSourceUnqueueBuffersFunc = C.LPALSOURCEUNQUEUEBUFFERS(fn("alSourceUnqueueBuffers"))
|
||||
alGetListenerfFunc = C.LPALGETLISTENERF(fn("alGetListenerf"))
|
||||
alGetListenerfvFunc = C.LPALGETLISTENERFV(fn("alGetListenerfv"))
|
||||
alListenerfFunc = C.LPALLISTENERF(fn("alListenerf"))
|
||||
alListenerfvFunc = C.LPALLISTENERFV(fn("alListenerfv"))
|
||||
alGenBuffersFunc = C.LPALGENBUFFERS(fn("alGenBuffers"))
|
||||
alDeleteBuffersFunc = C.LPALDELETEBUFFERS(fn("alDeleteBuffers"))
|
||||
alGetBufferiFunc = C.LPALGETBUFFERI(fn("alGetBufferi"))
|
||||
alBufferDataFunc = C.LPALBUFFERDATA(fn("alBufferData"))
|
||||
alIsBufferFunc = C.LPALISBUFFER(fn("alIsBuffer"))
|
||||
|
||||
alcGetErrorFunc = C.LPALCGETERROR(fn("alcGetError"))
|
||||
alcOpenDeviceFunc = C.LPALCOPENDEVICE(fn("alcOpenDevice"))
|
||||
alcCloseDeviceFunc = C.LPALCCLOSEDEVICE(fn("alcCloseDevice"))
|
||||
alcCreateContextFunc = C.LPALCCREATECONTEXT(fn("alcCreateContext"))
|
||||
alcMakeContextCurrentFunc = C.LPALCMAKECONTEXTCURRENT(fn("alcMakeContextCurrent"))
|
||||
alcDestroyContextFunc = C.LPALCDESTROYCONTEXT(fn("alcDestroyContext"))
|
||||
}
|
||||
|
||||
func fn(fname string) unsafe.Pointer {
|
||||
name := C.CString(fname)
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
|
||||
p := C.dlsym(alHandle, name)
|
||||
if uintptr(p) == 0 {
|
||||
log.Fatalf("al: couldn't dlsym %q", fname)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func alEnable(capability int32) {
|
||||
C.call_alEnable(alEnableFunc, C.ALenum(capability))
|
||||
}
|
||||
|
||||
func alDisable(capability int32) {
|
||||
C.call_alDisable(alDisableFunc, C.ALenum(capability))
|
||||
}
|
||||
|
||||
func alIsEnabled(capability int32) bool {
|
||||
return C.call_alIsEnabled(alIsEnabledFunc, C.ALenum(capability)) == C.AL_TRUE
|
||||
}
|
||||
|
||||
func alGetInteger(k int) int32 {
|
||||
return int32(C.call_alGetInteger(alGetIntegerFunc, C.ALenum(k)))
|
||||
}
|
||||
|
||||
func alGetIntegerv(k int, v []int32) {
|
||||
C.call_alGetIntegerv(alGetIntegervFunc, C.ALenum(k), (*C.ALint)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alGetFloat(k int) float32 {
|
||||
return float32(C.call_alGetFloat(alGetFloatFunc, C.ALenum(k)))
|
||||
}
|
||||
|
||||
func alGetFloatv(k int, v []float32) {
|
||||
C.call_alGetFloatv(alGetFloatvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alGetBoolean(k int) bool {
|
||||
return C.call_alGetBoolean(alGetBooleanFunc, C.ALenum(k)) == C.AL_TRUE
|
||||
}
|
||||
|
||||
func alGetBooleanv(k int, v []bool) {
|
||||
val := make([]C.ALboolean, len(v))
|
||||
for i, bv := range v {
|
||||
if bv {
|
||||
val[i] = C.AL_TRUE
|
||||
} else {
|
||||
val[i] = C.AL_FALSE
|
||||
}
|
||||
}
|
||||
C.call_alGetBooleanv(alGetBooleanvFunc, C.ALenum(k), &val[0])
|
||||
}
|
||||
|
||||
func alGetString(v int) string {
|
||||
value := C.call_alGetString(alGetStringFunc, C.ALenum(v))
|
||||
return C.GoString(value)
|
||||
}
|
||||
|
||||
func alDistanceModel(v int32) {
|
||||
C.call_alDistanceModel(alDistanceModelFunc, C.ALenum(v))
|
||||
}
|
||||
|
||||
func alDopplerFactor(v float32) {
|
||||
C.call_alDopplerFactor(alDopplerFactorFunc, C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alDopplerVelocity(v float32) {
|
||||
C.call_alDopplerVelocity(alDopplerVelocityFunc, C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alSpeedOfSound(v float32) {
|
||||
C.call_alSpeedOfSound(alSpeedOfSoundFunc, C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alGetError() int32 {
|
||||
return int32(C.call_alGetError(alGetErrorFunc))
|
||||
}
|
||||
|
||||
func alGenSources(n int) []Source {
|
||||
s := make([]Source, n)
|
||||
C.call_alGenSources(alGenSourcesFunc, C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
return s
|
||||
}
|
||||
|
||||
func alSourcePlayv(s []Source) {
|
||||
C.call_alSourcePlayv(alSourcePlayvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alSourcePausev(s []Source) {
|
||||
C.call_alSourcePausev(alSourcePausevFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alSourceStopv(s []Source) {
|
||||
C.call_alSourceStopv(alSourceStopvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alSourceRewindv(s []Source) {
|
||||
C.call_alSourceRewindv(alSourceRewindvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alDeleteSources(s []Source) {
|
||||
C.call_alDeleteSources(alDeleteSourcesFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alGetSourcei(s Source, k int) int32 {
|
||||
var v C.ALint
|
||||
C.call_alGetSourcei(alGetSourceiFunc, C.ALuint(s), C.ALenum(k), &v)
|
||||
return int32(v)
|
||||
}
|
||||
|
||||
func alGetSourcef(s Source, k int) float32 {
|
||||
var v C.ALfloat
|
||||
C.call_alGetSourcef(alGetSourcefFunc, C.ALuint(s), C.ALenum(k), &v)
|
||||
return float32(v)
|
||||
}
|
||||
|
||||
func alGetSourcefv(s Source, k int, v []float32) {
|
||||
C.call_alGetSourcefv(alGetSourcefvFunc, C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alSourcei(s Source, k int, v int32) {
|
||||
C.call_alSourcei(alSourcefFunc, C.ALuint(s), C.ALenum(k), C.ALint(v))
|
||||
}
|
||||
|
||||
func alSourcef(s Source, k int, v float32) {
|
||||
C.call_alSourcef(alSourcefFunc, C.ALuint(s), C.ALenum(k), C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alSourcefv(s Source, k int, v []float32) {
|
||||
C.call_alSourcefv(alSourcefvFunc, C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alSourceQueueBuffers(s Source, b []Buffer) {
|
||||
C.call_alSourceQueueBuffers(alSourceQueueBuffersFunc, C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
|
||||
}
|
||||
|
||||
func alSourceUnqueueBuffers(s Source, b []Buffer) {
|
||||
C.call_alSourceUnqueueBuffers(alSourceUnqueueBuffersFunc, C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
|
||||
}
|
||||
|
||||
func alGetListenerf(k int) float32 {
|
||||
var v C.ALfloat
|
||||
C.call_alGetListenerf(alListenerfFunc, C.ALenum(k), &v)
|
||||
return float32(v)
|
||||
}
|
||||
|
||||
func alGetListenerfv(k int, v []float32) {
|
||||
C.call_alGetListenerfv(alGetListenerfvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alListenerf(k int, v float32) {
|
||||
C.call_alListenerf(alListenerfFunc, C.ALenum(k), C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alListenerfv(k int, v []float32) {
|
||||
C.call_alListenerfv(alListenerfvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alGenBuffers(n int) []Buffer {
|
||||
s := make([]Buffer, n)
|
||||
C.call_alGenBuffers(alGenBuffersFunc, C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
return s
|
||||
}
|
||||
|
||||
func alDeleteBuffers(b []Buffer) {
|
||||
C.call_alDeleteBuffers(alDeleteBuffersFunc, C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
|
||||
}
|
||||
|
||||
func alGetBufferi(b Buffer, k int) int32 {
|
||||
var v C.ALint
|
||||
C.call_alGetBufferi(alGetBufferiFunc, C.ALuint(b), C.ALenum(k), &v)
|
||||
return int32(v)
|
||||
}
|
||||
|
||||
func alBufferData(b Buffer, format uint32, data []byte, freq int32) {
|
||||
C.call_alBufferData(alBufferDataFunc, C.ALuint(b), C.ALenum(format), unsafe.Pointer(&data[0]), C.ALsizei(len(data)), C.ALsizei(freq))
|
||||
}
|
||||
|
||||
func alIsBuffer(b Buffer) bool {
|
||||
return C.call_alIsBuffer(alIsBufferFunc, C.ALuint(b)) == C.AL_TRUE
|
||||
}
|
208
exp/audio/al/al_notandroid.go
Normal file
208
exp/audio/al/al_notandroid.go
Normal file
|
@ -0,0 +1,208 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || (linux && !android) || windows
|
||||
|
||||
package al
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -DGOOS_darwin
|
||||
#cgo linux CFLAGS: -DGOOS_linux
|
||||
#cgo windows CFLAGS: -DGOOS_windows
|
||||
#cgo darwin LDFLAGS: -framework OpenAL
|
||||
#cgo linux LDFLAGS: -lopenal
|
||||
#cgo windows LDFLAGS: -lOpenAL32
|
||||
|
||||
#ifdef GOOS_darwin
|
||||
#include <stdlib.h>
|
||||
#include <OpenAL/al.h>
|
||||
#endif
|
||||
|
||||
#ifdef GOOS_linux
|
||||
#include <stdlib.h>
|
||||
#include <AL/al.h> // install on Ubuntu with: sudo apt-get install libopenal-dev
|
||||
#endif
|
||||
|
||||
#ifdef GOOS_windows
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
#include <AL/al.h>
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
func alEnable(capability int32) {
|
||||
C.alEnable(C.ALenum(capability))
|
||||
}
|
||||
|
||||
func alDisable(capability int32) {
|
||||
C.alDisable(C.ALenum(capability))
|
||||
}
|
||||
|
||||
func alIsEnabled(capability int32) bool {
|
||||
return C.alIsEnabled(C.ALenum(capability)) == C.AL_TRUE
|
||||
}
|
||||
|
||||
func alGetInteger(k int) int32 {
|
||||
return int32(C.alGetInteger(C.ALenum(k)))
|
||||
}
|
||||
|
||||
func alGetIntegerv(k int, v []int32) {
|
||||
C.alGetIntegerv(C.ALenum(k), (*C.ALint)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alGetFloat(k int) float32 {
|
||||
return float32(C.alGetFloat(C.ALenum(k)))
|
||||
}
|
||||
|
||||
func alGetFloatv(k int, v []float32) {
|
||||
C.alGetFloatv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alGetBoolean(k int) bool {
|
||||
return C.alGetBoolean(C.ALenum(k)) == C.AL_TRUE
|
||||
}
|
||||
|
||||
func alGetBooleanv(k int, v []bool) {
|
||||
val := make([]C.ALboolean, len(v))
|
||||
for i, bv := range v {
|
||||
if bv {
|
||||
val[i] = C.AL_TRUE
|
||||
} else {
|
||||
val[i] = C.AL_FALSE
|
||||
}
|
||||
}
|
||||
C.alGetBooleanv(C.ALenum(k), &val[0])
|
||||
}
|
||||
|
||||
func alGetString(v int) string {
|
||||
value := C.alGetString(C.ALenum(v))
|
||||
return C.GoString((*C.char)(value))
|
||||
}
|
||||
|
||||
func alDistanceModel(v int32) {
|
||||
C.alDistanceModel(C.ALenum(v))
|
||||
}
|
||||
|
||||
func alDopplerFactor(v float32) {
|
||||
C.alDopplerFactor(C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alDopplerVelocity(v float32) {
|
||||
C.alDopplerVelocity(C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alSpeedOfSound(v float32) {
|
||||
C.alSpeedOfSound(C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alGetError() int32 {
|
||||
return int32(C.alGetError())
|
||||
}
|
||||
|
||||
func alGenSources(n int) []Source {
|
||||
s := make([]Source, n)
|
||||
C.alGenSources(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
return s
|
||||
}
|
||||
|
||||
func alSourcePlayv(s []Source) {
|
||||
C.alSourcePlayv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alSourcePausev(s []Source) {
|
||||
C.alSourcePausev(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
|
||||
}
|
||||
|
||||
func alSourceStopv(s []Source) {
|
||||
C.alSourceStopv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alSourceRewindv(s []Source) {
|
||||
C.alSourceRewindv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alDeleteSources(s []Source) {
|
||||
C.alDeleteSources(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
}
|
||||
|
||||
func alGetSourcei(s Source, k int) int32 {
|
||||
var v C.ALint
|
||||
C.alGetSourcei(C.ALuint(s), C.ALenum(k), &v)
|
||||
return int32(v)
|
||||
}
|
||||
|
||||
func alGetSourcef(s Source, k int) float32 {
|
||||
var v C.ALfloat
|
||||
C.alGetSourcef(C.ALuint(s), C.ALenum(k), &v)
|
||||
return float32(v)
|
||||
}
|
||||
|
||||
func alGetSourcefv(s Source, k int, v []float32) {
|
||||
C.alGetSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alSourcei(s Source, k int, v int32) {
|
||||
C.alSourcei(C.ALuint(s), C.ALenum(k), C.ALint(v))
|
||||
}
|
||||
|
||||
func alSourcef(s Source, k int, v float32) {
|
||||
C.alSourcef(C.ALuint(s), C.ALenum(k), C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alSourcefv(s Source, k int, v []float32) {
|
||||
C.alSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alSourceQueueBuffers(s Source, b []Buffer) {
|
||||
C.alSourceQueueBuffers(C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
|
||||
}
|
||||
|
||||
func alSourceUnqueueBuffers(s Source, b []Buffer) {
|
||||
C.alSourceUnqueueBuffers(C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
|
||||
}
|
||||
|
||||
func alGetListenerf(k int) float32 {
|
||||
var v C.ALfloat
|
||||
C.alGetListenerf(C.ALenum(k), &v)
|
||||
return float32(v)
|
||||
}
|
||||
|
||||
func alGetListenerfv(k int, v []float32) {
|
||||
C.alGetListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alListenerf(k int, v float32) {
|
||||
C.alListenerf(C.ALenum(k), C.ALfloat(v))
|
||||
}
|
||||
|
||||
func alListenerfv(k int, v []float32) {
|
||||
C.alListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
|
||||
}
|
||||
|
||||
func alGenBuffers(n int) []Buffer {
|
||||
s := make([]Buffer, n)
|
||||
C.alGenBuffers(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
|
||||
return s
|
||||
}
|
||||
|
||||
func alDeleteBuffers(b []Buffer) {
|
||||
C.alDeleteBuffers(C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
|
||||
}
|
||||
|
||||
func alGetBufferi(b Buffer, k int) int32 {
|
||||
var v C.ALint
|
||||
C.alGetBufferi(C.ALuint(b), C.ALenum(k), &v)
|
||||
return int32(v)
|
||||
}
|
||||
|
||||
func alBufferData(b Buffer, format uint32, data []byte, freq int32) {
|
||||
C.alBufferData(C.ALuint(b), C.ALenum(format), unsafe.Pointer(&data[0]), C.ALsizei(len(data)), C.ALsizei(freq))
|
||||
}
|
||||
|
||||
func alIsBuffer(b Buffer) bool {
|
||||
return C.alIsBuffer(C.ALuint(b)) == C.AL_TRUE
|
||||
}
|
74
exp/audio/al/alc.go
Normal file
74
exp/audio/al/alc.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package al
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
device unsafe.Pointer
|
||||
context unsafe.Pointer
|
||||
)
|
||||
|
||||
// DeviceError returns the last known error from the current device.
|
||||
func DeviceError() int32 {
|
||||
return alcGetError(device)
|
||||
}
|
||||
|
||||
// TODO(jbd): Investigate the cases where multiple audio output
|
||||
// devices might be needed.
|
||||
|
||||
// OpenDevice opens the default audio device.
|
||||
// Calls to OpenDevice are safe for concurrent use.
|
||||
func OpenDevice() error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
// already opened
|
||||
if device != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dev := alcOpenDevice("")
|
||||
if dev == nil {
|
||||
return errors.New("al: cannot open the default audio device")
|
||||
}
|
||||
ctx := alcCreateContext(dev, nil)
|
||||
if ctx == nil {
|
||||
alcCloseDevice(dev)
|
||||
return errors.New("al: cannot create a new context")
|
||||
}
|
||||
if !alcMakeContextCurrent(ctx) {
|
||||
alcCloseDevice(dev)
|
||||
return errors.New("al: cannot make context current")
|
||||
}
|
||||
device = dev
|
||||
context = ctx
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseDevice closes the device and frees related resources.
|
||||
// Calls to CloseDevice are safe for concurrent use.
|
||||
func CloseDevice() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
if device == nil {
|
||||
return
|
||||
}
|
||||
|
||||
alcCloseDevice(device)
|
||||
if context != nil {
|
||||
alcDestroyContext(context)
|
||||
}
|
||||
device = nil
|
||||
context = nil
|
||||
}
|
76
exp/audio/al/alc_android.go
Normal file
76
exp/audio/al/alc_android.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package al
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
|
||||
ALCint call_alcGetError(LPALCGETERROR fn, ALCdevice* d) {
|
||||
return fn(d);
|
||||
}
|
||||
|
||||
ALCdevice* call_alcOpenDevice(LPALCOPENDEVICE fn, const ALCchar* name) {
|
||||
return fn(name);
|
||||
}
|
||||
|
||||
ALCboolean call_alcCloseDevice(LPALCCLOSEDEVICE fn, ALCdevice* d) {
|
||||
return fn(d);
|
||||
}
|
||||
|
||||
ALCcontext* call_alcCreateContext(LPALCCREATECONTEXT fn, ALCdevice* d, const ALCint* attrs) {
|
||||
return fn(d, attrs);
|
||||
}
|
||||
|
||||
ALCboolean call_alcMakeContextCurrent(LPALCMAKECONTEXTCURRENT fn, ALCcontext* c) {
|
||||
return fn(c);
|
||||
}
|
||||
|
||||
void call_alcDestroyContext(LPALCDESTROYCONTEXT fn, ALCcontext* c) {
|
||||
return fn(c);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func alcGetError(d unsafe.Pointer) int32 {
|
||||
dev := (*C.ALCdevice)(d)
|
||||
return int32(C.call_alcGetError(alcGetErrorFunc, dev))
|
||||
}
|
||||
|
||||
func alcOpenDevice(name string) unsafe.Pointer {
|
||||
once.Do(initAL)
|
||||
n := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(n))
|
||||
|
||||
return (unsafe.Pointer)(C.call_alcOpenDevice(alcOpenDeviceFunc, (*C.ALCchar)(unsafe.Pointer(n))))
|
||||
}
|
||||
|
||||
func alcCloseDevice(d unsafe.Pointer) bool {
|
||||
dev := (*C.ALCdevice)(d)
|
||||
return C.call_alcCloseDevice(alcCloseDeviceFunc, dev) == C.AL_TRUE
|
||||
}
|
||||
|
||||
func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer {
|
||||
dev := (*C.ALCdevice)(d)
|
||||
// TODO(jbd): Handle attrs.
|
||||
return (unsafe.Pointer)(C.call_alcCreateContext(alcCreateContextFunc, dev, nil))
|
||||
}
|
||||
|
||||
func alcMakeContextCurrent(c unsafe.Pointer) bool {
|
||||
ctx := (*C.ALCcontext)(c)
|
||||
return C.call_alcMakeContextCurrent(alcMakeContextCurrentFunc, ctx) == C.AL_TRUE
|
||||
}
|
||||
|
||||
func alcDestroyContext(c unsafe.Pointer) {
|
||||
C.call_alcDestroyContext(alcDestroyContextFunc, (*C.ALCcontext)(c))
|
||||
}
|
70
exp/audio/al/alc_notandroid.go
Normal file
70
exp/audio/al/alc_notandroid.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || (linux && !android) || windows
|
||||
|
||||
package al
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -DGOOS_darwin
|
||||
#cgo linux CFLAGS: -DGOOS_linux
|
||||
#cgo windows CFLAGS: -DGOOS_windows
|
||||
#cgo darwin LDFLAGS: -framework OpenAL
|
||||
#cgo linux LDFLAGS: -lopenal
|
||||
#cgo windows LDFLAGS: -lOpenAL32
|
||||
|
||||
#ifdef GOOS_darwin
|
||||
#include <stdlib.h>
|
||||
#include <OpenAL/alc.h>
|
||||
#endif
|
||||
|
||||
#ifdef GOOS_linux
|
||||
#include <stdlib.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
|
||||
#ifdef GOOS_windows
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
#include <AL/alc.h>
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
/*
|
||||
On Ubuntu 14.04 'Trusty', you may have to install these libraries:
|
||||
sudo apt-get install libopenal-dev
|
||||
*/
|
||||
|
||||
func alcGetError(d unsafe.Pointer) int32 {
|
||||
dev := (*C.ALCdevice)(d)
|
||||
return int32(C.alcGetError(dev))
|
||||
}
|
||||
|
||||
func alcOpenDevice(name string) unsafe.Pointer {
|
||||
n := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(n))
|
||||
|
||||
return (unsafe.Pointer)(C.alcOpenDevice((*C.ALCchar)(unsafe.Pointer(n))))
|
||||
}
|
||||
|
||||
func alcCloseDevice(d unsafe.Pointer) bool {
|
||||
dev := (*C.ALCdevice)(d)
|
||||
return C.alcCloseDevice(dev) == C.ALC_TRUE
|
||||
}
|
||||
|
||||
func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer {
|
||||
dev := (*C.ALCdevice)(d)
|
||||
return (unsafe.Pointer)(C.alcCreateContext(dev, nil))
|
||||
}
|
||||
|
||||
func alcMakeContextCurrent(c unsafe.Pointer) bool {
|
||||
ctx := (*C.ALCcontext)(c)
|
||||
return C.alcMakeContextCurrent(ctx) == C.ALC_TRUE
|
||||
}
|
||||
|
||||
func alcDestroyContext(c unsafe.Pointer) {
|
||||
C.alcDestroyContext((*C.ALCcontext)(c))
|
||||
}
|
82
exp/audio/al/const.go
Normal file
82
exp/audio/al/const.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package al
|
||||
|
||||
// Error returns one of these error codes.
|
||||
const (
|
||||
InvalidName = 0xA001
|
||||
InvalidEnum = 0xA002
|
||||
InvalidValue = 0xA003
|
||||
InvalidOperation = 0xA004
|
||||
OutOfMemory = 0xA005
|
||||
)
|
||||
|
||||
// Distance models.
|
||||
const (
|
||||
InverseDistance = 0xD001
|
||||
InverseDistanceClamped = 0xD002
|
||||
LinearDistance = 0xD003
|
||||
LinearDistanceClamped = 0xD004
|
||||
ExponentDistance = 0xD005
|
||||
ExponentDistanceClamped = 0xD006
|
||||
)
|
||||
|
||||
// Global parameters.
|
||||
const (
|
||||
paramDistanceModel = 0xD000
|
||||
paramDopplerFactor = 0xC000
|
||||
paramDopplerVelocity = 0xC001
|
||||
paramSpeedOfSound = 0xC003
|
||||
paramVendor = 0xB001
|
||||
paramVersion = 0xB002
|
||||
paramRenderer = 0xB003
|
||||
paramExtensions = 0xB004
|
||||
)
|
||||
|
||||
// Source and listener parameters.
|
||||
const (
|
||||
paramGain = 0x100A
|
||||
paramPosition = 0x1004
|
||||
paramVelocity = 0x1006
|
||||
paramOrientation = 0x100F
|
||||
paramMinGain = 0x100D
|
||||
paramMaxGain = 0x100E
|
||||
paramSourceState = 0x1010
|
||||
paramBuffersQueued = 0x1015
|
||||
paramBuffersProcessed = 0x1016
|
||||
paramSecOffset = 0x1024
|
||||
paramSampleOffset = 0x1025
|
||||
paramByteOffset = 0x1026
|
||||
)
|
||||
|
||||
// A source could be in the state of initial, playing, paused or stopped.
|
||||
const (
|
||||
Initial = 0x1011
|
||||
Playing = 0x1012
|
||||
Paused = 0x1013
|
||||
Stopped = 0x1014
|
||||
)
|
||||
|
||||
// Buffer parameters.
|
||||
const (
|
||||
paramFreq = 0x2001
|
||||
paramBits = 0x2002
|
||||
paramChannels = 0x2003
|
||||
paramSize = 0x2004
|
||||
)
|
||||
|
||||
// Audio formats. Buffer.BufferData accepts one of these formats as the data format.
|
||||
const (
|
||||
FormatMono8 = 0x1100
|
||||
FormatMono16 = 0x1101
|
||||
FormatStereo8 = 0x1102
|
||||
FormatStereo16 = 0x1103
|
||||
)
|
||||
|
||||
// CapabilityDistanceModel represents the capability of specifying a different distance
|
||||
// model for each source.
|
||||
const CapabilityDistanceModel = Capability(0x200)
|
109
exp/f32/affine.go
Normal file
109
exp/f32/affine.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package f32
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An Affine is a 3x3 matrix of float32 values for which the bottom row is
|
||||
// implicitly always equal to [0 0 1].
|
||||
// Elements are indexed first by row then column, i.e. m[row][column].
|
||||
type Affine [2]Vec3
|
||||
|
||||
func (m Affine) String() string {
|
||||
return fmt.Sprintf(`Affine[% 0.3f, % 0.3f, % 0.3f,
|
||||
% 0.3f, % 0.3f, % 0.3f]`,
|
||||
m[0][0], m[0][1], m[0][2],
|
||||
m[1][0], m[1][1], m[1][2])
|
||||
}
|
||||
|
||||
// Identity sets m to be the identity transform.
|
||||
func (m *Affine) Identity() {
|
||||
*m = Affine{
|
||||
{1, 0, 0},
|
||||
{0, 1, 0},
|
||||
}
|
||||
}
|
||||
|
||||
// Eq reports whether each component of m is within epsilon of the same
|
||||
// component in n.
|
||||
func (m *Affine) Eq(n *Affine, epsilon float32) bool {
|
||||
for i := range m {
|
||||
for j := range m[i] {
|
||||
diff := m[i][j] - n[i][j]
|
||||
if diff < -epsilon || +epsilon < diff {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Mul sets m to be p × q.
|
||||
func (m *Affine) Mul(p, q *Affine) {
|
||||
// Store the result in local variables, in case m == a || m == b.
|
||||
m00 := p[0][0]*q[0][0] + p[0][1]*q[1][0]
|
||||
m01 := p[0][0]*q[0][1] + p[0][1]*q[1][1]
|
||||
m02 := p[0][0]*q[0][2] + p[0][1]*q[1][2] + p[0][2]
|
||||
m10 := p[1][0]*q[0][0] + p[1][1]*q[1][0]
|
||||
m11 := p[1][0]*q[0][1] + p[1][1]*q[1][1]
|
||||
m12 := p[1][0]*q[0][2] + p[1][1]*q[1][2] + p[1][2]
|
||||
m[0][0] = m00
|
||||
m[0][1] = m01
|
||||
m[0][2] = m02
|
||||
m[1][0] = m10
|
||||
m[1][1] = m11
|
||||
m[1][2] = m12
|
||||
}
|
||||
|
||||
// Inverse sets m to be the inverse of p.
|
||||
func (m *Affine) Inverse(p *Affine) {
|
||||
m00 := p[1][1]
|
||||
m01 := -p[0][1]
|
||||
m02 := p[1][2]*p[0][1] - p[1][1]*p[0][2]
|
||||
m10 := -p[1][0]
|
||||
m11 := p[0][0]
|
||||
m12 := p[1][0]*p[0][2] - p[1][2]*p[0][0]
|
||||
|
||||
det := m00*m11 - m10*m01
|
||||
|
||||
m[0][0] = m00 / det
|
||||
m[0][1] = m01 / det
|
||||
m[0][2] = m02 / det
|
||||
m[1][0] = m10 / det
|
||||
m[1][1] = m11 / det
|
||||
m[1][2] = m12 / det
|
||||
}
|
||||
|
||||
// Scale sets m to be a scale followed by p.
|
||||
// It is equivalent to m.Mul(p, &Affine{{x,0,0}, {0,y,0}}).
|
||||
func (m *Affine) Scale(p *Affine, x, y float32) {
|
||||
m[0][0] = p[0][0] * x
|
||||
m[0][1] = p[0][1] * y
|
||||
m[0][2] = p[0][2]
|
||||
m[1][0] = p[1][0] * x
|
||||
m[1][1] = p[1][1] * y
|
||||
m[1][2] = p[1][2]
|
||||
}
|
||||
|
||||
// Translate sets m to be a translation followed by p.
|
||||
// It is equivalent to m.Mul(p, &Affine{{1,0,x}, {0,1,y}}).
|
||||
func (m *Affine) Translate(p *Affine, x, y float32) {
|
||||
m[0][0] = p[0][0]
|
||||
m[0][1] = p[0][1]
|
||||
m[0][2] = p[0][0]*x + p[0][1]*y + p[0][2]
|
||||
m[1][0] = p[1][0]
|
||||
m[1][1] = p[1][1]
|
||||
m[1][2] = p[1][0]*x + p[1][1]*y + p[1][2]
|
||||
}
|
||||
|
||||
// Rotate sets m to a rotation in radians followed by p.
|
||||
// It is equivalent to m.Mul(p, affineRotation).
|
||||
func (m *Affine) Rotate(p *Affine, radians float32) {
|
||||
s, c := Sin(radians), Cos(radians)
|
||||
m.Mul(p, &Affine{
|
||||
{+c, +s, 0},
|
||||
{-s, +c, 0},
|
||||
})
|
||||
}
|
140
exp/f32/affine_test.go
Normal file
140
exp/f32/affine_test.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package f32
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var xyTests = []struct {
|
||||
x, y float32
|
||||
}{
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
{2, 3},
|
||||
{6.5, 4.3},
|
||||
}
|
||||
|
||||
var a = Affine{
|
||||
{3, 4, 5},
|
||||
{6, 7, 8},
|
||||
}
|
||||
|
||||
func TestInverse(t *testing.T) {
|
||||
wantInv := Affine{
|
||||
{-2.33333, 1.33333, 1},
|
||||
{2, -1, -2},
|
||||
}
|
||||
var gotInv Affine
|
||||
gotInv.Inverse(&a)
|
||||
if !gotInv.Eq(&wantInv, 0.01) {
|
||||
t.Errorf("Inverse: got %s want %s", gotInv, wantInv)
|
||||
}
|
||||
|
||||
var wantId, gotId Affine
|
||||
wantId.Identity()
|
||||
gotId.Mul(&a, &wantInv)
|
||||
if !gotId.Eq(&wantId, 0.01) {
|
||||
t.Errorf("Identity #0: got %s want %s", gotId, wantId)
|
||||
}
|
||||
gotId.Mul(&wantInv, &a)
|
||||
if !gotId.Eq(&wantId, 0.01) {
|
||||
t.Errorf("Identity #1: got %s want %s", gotId, wantId)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineScale(t *testing.T) {
|
||||
for _, test := range xyTests {
|
||||
want := a
|
||||
want.Mul(&want, &Affine{{test.x, 0, 0}, {0, test.y, 0}})
|
||||
got := a
|
||||
got.Scale(&got, test.x, test.y)
|
||||
|
||||
if !got.Eq(&want, 0.01) {
|
||||
t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineTranslate(t *testing.T) {
|
||||
for _, test := range xyTests {
|
||||
want := a
|
||||
want.Mul(&want, &Affine{{1, 0, test.x}, {0, 1, test.y}})
|
||||
got := a
|
||||
got.Translate(&got, test.x, test.y)
|
||||
|
||||
if !got.Eq(&want, 0.01) {
|
||||
t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAffineRotate(t *testing.T) {
|
||||
want := Affine{
|
||||
{-4.000, 3.000, 5.000},
|
||||
{-7.000, 6.000, 8.000},
|
||||
}
|
||||
got := a
|
||||
got.Rotate(&got, math.Pi/2)
|
||||
if !got.Eq(&want, 0.01) {
|
||||
t.Errorf("rotate π: got %s want %s", got, want)
|
||||
}
|
||||
|
||||
want = a
|
||||
got = a
|
||||
got.Rotate(&got, 2*math.Pi)
|
||||
if !got.Eq(&want, 0.01) {
|
||||
t.Errorf("rotate 2π: got %s want %s", got, want)
|
||||
}
|
||||
|
||||
got = a
|
||||
got.Rotate(&got, math.Pi)
|
||||
got.Rotate(&got, math.Pi)
|
||||
if !got.Eq(&want, 0.01) {
|
||||
t.Errorf("rotate π then π: got %s want %s", got, want)
|
||||
}
|
||||
|
||||
got = a
|
||||
got.Rotate(&got, math.Pi/3)
|
||||
got.Rotate(&got, -math.Pi/3)
|
||||
if !got.Eq(&want, 0.01) {
|
||||
t.Errorf("rotate π/3 then -π/3: got %s want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineScaleTranslate(t *testing.T) {
|
||||
mulVec := func(m *Affine, v [2]float32) (mv [2]float32) {
|
||||
mv[0] = m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]
|
||||
mv[1] = m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]
|
||||
return mv
|
||||
}
|
||||
v := [2]float32{1, 10}
|
||||
|
||||
var sThenT Affine
|
||||
sThenT.Identity()
|
||||
sThenT.Scale(&sThenT, 13, 17)
|
||||
sThenT.Translate(&sThenT, 101, 151)
|
||||
wantSTT := [2]float32{
|
||||
13 * (101 + 1),
|
||||
17 * (151 + 10),
|
||||
}
|
||||
if got := mulVec(&sThenT, v); got != wantSTT {
|
||||
t.Errorf("S then T: got %v, want %v", got, wantSTT)
|
||||
}
|
||||
|
||||
var tThenS Affine
|
||||
tThenS.Identity()
|
||||
tThenS.Translate(&tThenS, 101, 151)
|
||||
tThenS.Scale(&tThenS, 13, 17)
|
||||
wantTTS := [2]float32{
|
||||
101 + (13 * 1),
|
||||
151 + (17 * 10),
|
||||
}
|
||||
if got := mulVec(&tThenS, v); got != wantTTS {
|
||||
t.Errorf("T then S: got %v, want %v", got, wantTTS)
|
||||
}
|
||||
}
|
93
exp/f32/f32.go
Normal file
93
exp/f32/f32.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run gen.go -output table.go
|
||||
|
||||
// Package f32 implements some linear algebra and GL helpers for float32s.
|
||||
//
|
||||
// Types defined in this package have methods implementing common
|
||||
// mathematical operations. The common form for these functions is
|
||||
//
|
||||
// func (dst *T) Op(lhs, rhs *T)
|
||||
//
|
||||
// which reads in traditional mathematical notation as
|
||||
//
|
||||
// dst = lhs op rhs.
|
||||
//
|
||||
// It is safe to use the destination address as the left-hand side,
|
||||
// that is, dst *= rhs is dst.Mul(dst, rhs).
|
||||
//
|
||||
// # WARNING
|
||||
//
|
||||
// The interface to this package is not stable. It will change considerably.
|
||||
// Only use functions that provide package documentation. Semantics are
|
||||
// non-obvious. Be prepared for the package name to change.
|
||||
package f32
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type Radian float32
|
||||
|
||||
func Cos(x float32) float32 {
|
||||
const n = sinTableLen
|
||||
i := uint32(int32(x * (n / math.Pi)))
|
||||
i += n / 2
|
||||
i &= 2*n - 1
|
||||
if i >= n {
|
||||
return -sinTable[i&(n-1)]
|
||||
}
|
||||
return sinTable[i&(n-1)]
|
||||
}
|
||||
|
||||
func Sin(x float32) float32 {
|
||||
const n = sinTableLen
|
||||
i := uint32(int32(x * (n / math.Pi)))
|
||||
i &= 2*n - 1
|
||||
if i >= n {
|
||||
return -sinTable[i&(n-1)]
|
||||
}
|
||||
return sinTable[i&(n-1)]
|
||||
}
|
||||
|
||||
func Sqrt(x float32) float32 {
|
||||
return float32(math.Sqrt(float64(x))) // TODO(crawshaw): implement
|
||||
}
|
||||
|
||||
func Tan(x float32) float32 {
|
||||
return float32(math.Tan(float64(x))) // TODO(crawshaw): fast version
|
||||
}
|
||||
|
||||
// Bytes returns the byte representation of float32 values in the given byte
|
||||
// order. byteOrder must be either binary.BigEndian or binary.LittleEndian.
|
||||
func Bytes(byteOrder binary.ByteOrder, values ...float32) []byte {
|
||||
le := false
|
||||
switch byteOrder {
|
||||
case binary.BigEndian:
|
||||
case binary.LittleEndian:
|
||||
le = true
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid byte order %v", byteOrder))
|
||||
}
|
||||
|
||||
b := make([]byte, 4*len(values))
|
||||
for i, v := range values {
|
||||
u := math.Float32bits(v)
|
||||
if le {
|
||||
b[4*i+0] = byte(u >> 0)
|
||||
b[4*i+1] = byte(u >> 8)
|
||||
b[4*i+2] = byte(u >> 16)
|
||||
b[4*i+3] = byte(u >> 24)
|
||||
} else {
|
||||
b[4*i+0] = byte(u >> 24)
|
||||
b[4*i+1] = byte(u >> 16)
|
||||
b[4*i+2] = byte(u >> 8)
|
||||
b[4*i+3] = byte(u >> 0)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
361
exp/f32/f32_test.go
Normal file
361
exp/f32/f32_test.go
Normal file
|
@ -0,0 +1,361 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package f32
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAffineTranslationsCommute(t *testing.T) {
|
||||
a := &Affine{
|
||||
{1, 0, 3},
|
||||
{0, 1, 4},
|
||||
}
|
||||
b := &Affine{
|
||||
{1, 0, 20},
|
||||
{0, 1, 30},
|
||||
}
|
||||
|
||||
var m0, m1 Affine
|
||||
m0.Mul(a, b)
|
||||
m1.Mul(b, a)
|
||||
if !m0.Eq(&m1, 0) {
|
||||
t.Errorf("m0, m1 differ.\nm0: %v\nm1: %v", m0, m1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineMat3Equivalence(t *testing.T) {
|
||||
a0 := Affine{
|
||||
{13, 19, 37},
|
||||
{101, 149, 311},
|
||||
}
|
||||
m0 := Mat3{
|
||||
a0[0],
|
||||
a0[1],
|
||||
{0, 0, 1},
|
||||
}
|
||||
|
||||
a1 := Affine{
|
||||
{1009, 1051, 1087},
|
||||
{563, 569, 571},
|
||||
}
|
||||
m1 := Mat3{
|
||||
a1[0],
|
||||
a1[1],
|
||||
{0, 0, 1},
|
||||
}
|
||||
|
||||
a2 := Affine{}
|
||||
a2.Mul(&a0, &a1)
|
||||
m2 := Mat3{
|
||||
a2[0],
|
||||
a2[1],
|
||||
{0, 0, 1},
|
||||
}
|
||||
|
||||
mm := Mat3{}
|
||||
mm.Mul(&m0, &m1)
|
||||
|
||||
if !m2.Eq(&mm, 0) {
|
||||
t.Errorf("m2, mm differ.\nm2: %v\nmm: %v", m2, mm)
|
||||
}
|
||||
}
|
||||
|
||||
var x3 = Mat3{
|
||||
{0, 1, 2},
|
||||
{3, 4, 5},
|
||||
{6, 7, 8},
|
||||
}
|
||||
|
||||
var x3sq = Mat3{
|
||||
{15, 18, 21},
|
||||
{42, 54, 66},
|
||||
{69, 90, 111},
|
||||
}
|
||||
|
||||
var id3 = Mat3{
|
||||
{1, 0, 0},
|
||||
{0, 1, 0},
|
||||
{0, 0, 1},
|
||||
}
|
||||
|
||||
func TestMat3Mul(t *testing.T) {
|
||||
tests := []struct{ m0, m1, want Mat3 }{
|
||||
{x3, id3, x3},
|
||||
{id3, x3, x3},
|
||||
{x3, x3, x3sq},
|
||||
{
|
||||
Mat3{
|
||||
{+1.811, +0.000, +0.000},
|
||||
{+0.000, +2.414, +0.000},
|
||||
{+0.000, +0.000, -1.010},
|
||||
},
|
||||
Mat3{
|
||||
{+0.992, -0.015, +0.123},
|
||||
{+0.000, +0.992, +0.123},
|
||||
{-0.124, -0.122, +0.985},
|
||||
},
|
||||
Mat3{
|
||||
{+1.797, -0.027, +0.223},
|
||||
{+0.000, +2.395, +0.297},
|
||||
{+0.125, +0.123, -0.995},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got := Mat3{}
|
||||
got.Mul(&test.m0, &test.m1)
|
||||
if !got.Eq(&test.want, 0.01) {
|
||||
t.Errorf("test #%d:\n%s *\n%s =\n%s, want\n%s", i, test.m0, test.m1, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMat3SelfMul(t *testing.T) {
|
||||
m := x3
|
||||
m.Mul(&m, &m)
|
||||
if !m.Eq(&x3sq, 0) {
|
||||
t.Errorf("m, x3sq differ.\nm: %v\nx3sq: %v", m, x3sq)
|
||||
}
|
||||
}
|
||||
|
||||
var x4 = Mat4{
|
||||
{0, 1, 2, 3},
|
||||
{4, 5, 6, 7},
|
||||
{8, 9, 10, 11},
|
||||
{12, 13, 14, 15},
|
||||
}
|
||||
|
||||
var x4sq = Mat4{
|
||||
{56, 62, 68, 74},
|
||||
{152, 174, 196, 218},
|
||||
{248, 286, 324, 362},
|
||||
{344, 398, 452, 506},
|
||||
}
|
||||
|
||||
var id4 = Mat4{
|
||||
{1, 0, 0, 0},
|
||||
{0, 1, 0, 0},
|
||||
{0, 0, 1, 0},
|
||||
{0, 0, 0, 1},
|
||||
}
|
||||
|
||||
func TestMat4Eq(t *testing.T) {
|
||||
tests := []struct {
|
||||
m0, m1 Mat4
|
||||
eq bool
|
||||
}{
|
||||
{x4, x4, true},
|
||||
{id4, id4, true},
|
||||
{x4, id4, false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := test.m0.Eq(&test.m1, 0.01)
|
||||
if got != test.eq {
|
||||
t.Errorf("Eq=%v, want %v for\n%s\n%s", got, test.eq, test.m0, test.m1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMat4Mul(t *testing.T) {
|
||||
tests := []struct{ m0, m1, want Mat4 }{
|
||||
{x4, id4, x4},
|
||||
{id4, x4, x4},
|
||||
{x4, x4, x4sq},
|
||||
{
|
||||
Mat4{
|
||||
{+1.811, +0.000, +0.000, +0.000},
|
||||
{+0.000, +2.414, +0.000, +0.000},
|
||||
{+0.000, +0.000, -1.010, -1.000},
|
||||
{+0.000, +0.000, -2.010, +0.000},
|
||||
},
|
||||
Mat4{
|
||||
{+0.992, -0.015, +0.123, +0.000},
|
||||
{+0.000, +0.992, +0.123, +0.000},
|
||||
{-0.124, -0.122, +0.985, +0.000},
|
||||
{-0.000, -0.000, -8.124, +1.000},
|
||||
},
|
||||
Mat4{
|
||||
{+1.797, -0.027, +0.223, +0.000},
|
||||
{+0.000, +2.395, +0.297, +0.000},
|
||||
{+0.125, +0.123, +7.129, -1.000},
|
||||
{+0.249, +0.245, -1.980, +0.000},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
got := Mat4{}
|
||||
got.Mul(&test.m0, &test.m1)
|
||||
if !got.Eq(&test.want, 0.01) {
|
||||
t.Errorf("test #%d:\n%s *\n%s =\n%s, want\n%s", i, test.m0, test.m1, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMat4LookAt(t *testing.T) {
|
||||
tests := []struct {
|
||||
eye, center, up Vec3
|
||||
want Mat4
|
||||
}{
|
||||
{
|
||||
Vec3{1, 1, 8}, Vec3{0, 0, 0}, Vec3{0, 1, 0},
|
||||
Mat4{
|
||||
{0.992, -0.015, 0.123, 0.000},
|
||||
{0.000, 0.992, 0.123, 0.000},
|
||||
{-0.124, -0.122, 0.985, 0.000},
|
||||
{-0.000, -0.000, -8.124, 1.000},
|
||||
},
|
||||
},
|
||||
{
|
||||
Vec3{4, 5, 7}, Vec3{0.1, 0.2, 0.3}, Vec3{0, -1, 0},
|
||||
Mat4{
|
||||
{-0.864, 0.265, 0.428, 0.000},
|
||||
{0.000, -0.850, 0.526, 0.000},
|
||||
{0.503, 0.455, 0.735, 0.000},
|
||||
{-0.064, 0.007, -9.487, 1.000},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := Mat4{}
|
||||
got.LookAt(&test.eye, &test.center, &test.up)
|
||||
if !got.Eq(&test.want, 0.01) {
|
||||
t.Errorf("LookAt(%s,%s%s) =\n%s\nwant\n%s", test.eye, test.center, test.up, got, test.want)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMat4Perspective(t *testing.T) {
|
||||
want := Mat4{
|
||||
{1.811, 0.000, 0.000, 0.000},
|
||||
{0.000, 2.414, 0.000, 0.000},
|
||||
{0.000, 0.000, -1.010, -1.000},
|
||||
{0.000, 0.000, -2.010, 0.000},
|
||||
}
|
||||
got := Mat4{}
|
||||
|
||||
got.Perspective(Radian(math.Pi/4), 4.0/3, 1, 200)
|
||||
|
||||
if !got.Eq(&want, 0.01) {
|
||||
t.Errorf("got\n%s\nwant\n%s", got, want)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMat4Rotate(t *testing.T) {
|
||||
want := &Mat4{
|
||||
{2.000, 1.000, -0.000, 3.000},
|
||||
{6.000, 5.000, -4.000, 7.000},
|
||||
{10.000, 9.000, -8.000, 11.000},
|
||||
{14.000, 13.000, -12.000, 15.000},
|
||||
}
|
||||
|
||||
got := new(Mat4)
|
||||
got.Rotate(&x4, Radian(math.Pi/2), &Vec3{0, 1, 0})
|
||||
|
||||
if !got.Eq(want, 0.01) {
|
||||
t.Errorf("got\n%s\nwant\n%s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMat4Scale(t *testing.T) {
|
||||
want := &Mat4{
|
||||
{0 * 2, 1 * 3, 2 * 4, 3 * 1},
|
||||
{4 * 2, 5 * 3, 6 * 4, 7 * 1},
|
||||
{8 * 2, 9 * 3, 10 * 4, 11 * 1},
|
||||
{12 * 2, 13 * 3, 14 * 4, 15 * 1},
|
||||
}
|
||||
|
||||
got := new(Mat4)
|
||||
got.Scale(&x4, 2, 3, 4)
|
||||
|
||||
if !got.Eq(want, 0.01) {
|
||||
t.Errorf("got\n%s\nwant\n%s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMat4Translate(t *testing.T) {
|
||||
want := &Mat4{
|
||||
{0, 1, 2, 0*0.1 + 1*0.2 + 2*0.3 + 3*1},
|
||||
{4, 5, 6, 4*0.1 + 5*0.2 + 6*0.3 + 7*1},
|
||||
{8, 9, 10, 8*0.1 + 9*0.2 + 10*0.3 + 11*1},
|
||||
{12, 13, 14, 12*0.1 + 13*0.2 + 14*0.3 + 15*1},
|
||||
}
|
||||
|
||||
got := new(Mat4)
|
||||
got.Translate(&x4, 0.1, 0.2, 0.3)
|
||||
|
||||
if !got.Eq(want, 0.01) {
|
||||
t.Errorf("got\n%s\nwant\n%s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testTrig(t *testing.T, gotFunc func(float32) float32, wantFunc func(float64) float64) {
|
||||
nBad := 0
|
||||
for a := float32(-9); a < +9; a += .01 {
|
||||
got := gotFunc(a)
|
||||
want := float32(wantFunc(float64(a)))
|
||||
diff := got - want
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
if diff > 0.001 {
|
||||
if nBad++; nBad == 10 {
|
||||
t.Errorf("too many failures")
|
||||
break
|
||||
}
|
||||
t.Errorf("a=%+.2f: got %+.4f, want %+.4f, diff=%.4f", a, got, want, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCos(t *testing.T) { testTrig(t, Cos, math.Cos) }
|
||||
func TestSin(t *testing.T) { testTrig(t, Sin, math.Sin) }
|
||||
func TestTan(t *testing.T) { testTrig(t, Tan, math.Tan) }
|
||||
|
||||
func BenchmarkSin(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for a := 0; a < 3141; a++ {
|
||||
Sin(float32(a) / 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
testCases := []struct {
|
||||
byteOrder binary.ByteOrder
|
||||
want []byte
|
||||
}{{
|
||||
binary.BigEndian,
|
||||
[]byte{
|
||||
// The IEEE 754 binary32 format is 1 sign bit, 8 exponent bits and 23 fraction bits.
|
||||
0x00, 0x00, 0x00, 0x00, // float32(+0.00) is 0 0000000_0 0000000_00000000_00000000
|
||||
0x3f, 0xa0, 0x00, 0x00, // float32(+1.25) is 0 0111111_1 0100000_00000000_00000000
|
||||
0xc0, 0x00, 0x00, 0x00, // float32(-2.00) is 1 1000000_0 0000000_00000000_00000000
|
||||
},
|
||||
}, {
|
||||
binary.LittleEndian,
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xa0, 0x3f,
|
||||
0x00, 0x00, 0x00, 0xc0,
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
got := Bytes(tc.byteOrder, +0.00, +1.25, -2.00)
|
||||
if !bytes.Equal(got, tc.want) {
|
||||
t.Errorf("%v:\ngot % x\nwant % x", tc.byteOrder, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
48
exp/f32/gen.go
Normal file
48
exp/f32/gen.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
/*
|
||||
This program generates table.go. Invoke it as:
|
||||
go run gen.go -output table.go
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
)
|
||||
|
||||
// N is the number of entries in the sin look-up table. It must be a power of 2.
|
||||
const N = 4096
|
||||
|
||||
var filename = flag.String("output", "table.go", "output file name")
|
||||
|
||||
func main() {
|
||||
b := new(bytes.Buffer)
|
||||
fmt.Fprintf(b, "// Code generated by go run gen.go; DO NOT EDIT\n\npackage f32\n\n")
|
||||
fmt.Fprintf(b, "const sinTableLen = %d\n\n", N)
|
||||
fmt.Fprintf(b, "// sinTable[i] equals sin(i * π / sinTableLen).\n")
|
||||
fmt.Fprintf(b, "var sinTable = [sinTableLen]float32{\n")
|
||||
for i := 0; i < N; i++ {
|
||||
radians := float64(i) * (math.Pi / N)
|
||||
fmt.Fprintf(b, "%v,\n", float32(math.Sin(radians)))
|
||||
}
|
||||
fmt.Fprintf(b, "}\n")
|
||||
|
||||
data, err := format.Source(b.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(*filename, data, 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
63
exp/f32/mat3.go
Normal file
63
exp/f32/mat3.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package f32
|
||||
|
||||
import "fmt"
|
||||
|
||||
// A Mat3 is a 3x3 matrix of float32 values.
|
||||
// Elements are indexed first by row then column, i.e. m[row][column].
|
||||
type Mat3 [3]Vec3
|
||||
|
||||
func (m Mat3) String() string {
|
||||
return fmt.Sprintf(`Mat3[% 0.3f, % 0.3f, % 0.3f,
|
||||
% 0.3f, % 0.3f, % 0.3f,
|
||||
% 0.3f, % 0.3f, % 0.3f]`,
|
||||
m[0][0], m[0][1], m[0][2],
|
||||
m[1][0], m[1][1], m[1][2],
|
||||
m[2][0], m[2][1], m[2][2])
|
||||
}
|
||||
|
||||
func (m *Mat3) Identity() {
|
||||
*m = Mat3{
|
||||
{1, 0, 0},
|
||||
{0, 1, 0},
|
||||
{0, 0, 1},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mat3) Eq(n *Mat3, epsilon float32) bool {
|
||||
for i := range m {
|
||||
for j := range m[i] {
|
||||
diff := m[i][j] - n[i][j]
|
||||
if diff < -epsilon || +epsilon < diff {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Mul stores a × b in m.
|
||||
func (m *Mat3) Mul(a, b *Mat3) {
|
||||
// Store the result in local variables, in case m == a || m == b.
|
||||
m00 := a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0]
|
||||
m01 := a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1]
|
||||
m02 := a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2]
|
||||
m10 := a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0]
|
||||
m11 := a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1]
|
||||
m12 := a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2]
|
||||
m20 := a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0]
|
||||
m21 := a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1]
|
||||
m22 := a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2]
|
||||
m[0][0] = m00
|
||||
m[0][1] = m01
|
||||
m[0][2] = m02
|
||||
m[1][0] = m10
|
||||
m[1][1] = m11
|
||||
m[1][2] = m12
|
||||
m[2][0] = m20
|
||||
m[2][1] = m21
|
||||
m[2][2] = m22
|
||||
}
|
195
exp/f32/mat4.go
Normal file
195
exp/f32/mat4.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package f32
|
||||
|
||||
import "fmt"
|
||||
|
||||
// A Mat4 is a 4x4 matrix of float32 values.
|
||||
// Elements are indexed first by row then column, i.e. m[row][column].
|
||||
type Mat4 [4]Vec4
|
||||
|
||||
func (m Mat4) String() string {
|
||||
return fmt.Sprintf(`Mat4[% 0.3f, % 0.3f, % 0.3f, % 0.3f,
|
||||
% 0.3f, % 0.3f, % 0.3f, % 0.3f,
|
||||
% 0.3f, % 0.3f, % 0.3f, % 0.3f,
|
||||
% 0.3f, % 0.3f, % 0.3f, % 0.3f]`,
|
||||
m[0][0], m[0][1], m[0][2], m[0][3],
|
||||
m[1][0], m[1][1], m[1][2], m[1][3],
|
||||
m[2][0], m[2][1], m[2][2], m[2][3],
|
||||
m[3][0], m[3][1], m[3][2], m[3][3])
|
||||
}
|
||||
|
||||
func (m *Mat4) Identity() {
|
||||
*m = Mat4{
|
||||
{1, 0, 0, 0},
|
||||
{0, 1, 0, 0},
|
||||
{0, 0, 1, 0},
|
||||
{0, 0, 0, 1},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mat4) Eq(n *Mat4, epsilon float32) bool {
|
||||
for i := range m {
|
||||
for j := range m[i] {
|
||||
diff := m[i][j] - n[i][j]
|
||||
if diff < -epsilon || +epsilon < diff {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Mul stores a × b in m.
|
||||
func (m *Mat4) Mul(a, b *Mat4) {
|
||||
// Store the result in local variables, in case m == a || m == b.
|
||||
m00 := a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0] + a[0][3]*b[3][0]
|
||||
m01 := a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1] + a[0][3]*b[3][1]
|
||||
m02 := a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2] + a[0][3]*b[3][2]
|
||||
m03 := a[0][0]*b[0][3] + a[0][1]*b[1][3] + a[0][2]*b[2][3] + a[0][3]*b[3][3]
|
||||
m10 := a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0] + a[1][3]*b[3][0]
|
||||
m11 := a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1] + a[1][3]*b[3][1]
|
||||
m12 := a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2] + a[1][3]*b[3][2]
|
||||
m13 := a[1][0]*b[0][3] + a[1][1]*b[1][3] + a[1][2]*b[2][3] + a[1][3]*b[3][3]
|
||||
m20 := a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0] + a[2][3]*b[3][0]
|
||||
m21 := a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1] + a[2][3]*b[3][1]
|
||||
m22 := a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2] + a[2][3]*b[3][2]
|
||||
m23 := a[2][0]*b[0][3] + a[2][1]*b[1][3] + a[2][2]*b[2][3] + a[2][3]*b[3][3]
|
||||
m30 := a[3][0]*b[0][0] + a[3][1]*b[1][0] + a[3][2]*b[2][0] + a[3][3]*b[3][0]
|
||||
m31 := a[3][0]*b[0][1] + a[3][1]*b[1][1] + a[3][2]*b[2][1] + a[3][3]*b[3][1]
|
||||
m32 := a[3][0]*b[0][2] + a[3][1]*b[1][2] + a[3][2]*b[2][2] + a[3][3]*b[3][2]
|
||||
m33 := a[3][0]*b[0][3] + a[3][1]*b[1][3] + a[3][2]*b[2][3] + a[3][3]*b[3][3]
|
||||
m[0][0] = m00
|
||||
m[0][1] = m01
|
||||
m[0][2] = m02
|
||||
m[0][3] = m03
|
||||
m[1][0] = m10
|
||||
m[1][1] = m11
|
||||
m[1][2] = m12
|
||||
m[1][3] = m13
|
||||
m[2][0] = m20
|
||||
m[2][1] = m21
|
||||
m[2][2] = m22
|
||||
m[2][3] = m23
|
||||
m[3][0] = m30
|
||||
m[3][1] = m31
|
||||
m[3][2] = m32
|
||||
m[3][3] = m33
|
||||
}
|
||||
|
||||
// Perspective sets m to be the GL perspective matrix.
|
||||
func (m *Mat4) Perspective(fov Radian, aspect, near, far float32) {
|
||||
t := Tan(float32(fov) / 2)
|
||||
|
||||
m[0][0] = 1 / (aspect * t)
|
||||
m[1][1] = 1 / t
|
||||
m[2][2] = -(far + near) / (far - near)
|
||||
m[2][3] = -1
|
||||
m[3][2] = -2 * far * near / (far - near)
|
||||
}
|
||||
|
||||
// Scale sets m to be a scale followed by p.
|
||||
// It is equivalent to
|
||||
//
|
||||
// m.Mul(p, &Mat4{
|
||||
// {x, 0, 0, 0},
|
||||
// {0, y, 0, 0},
|
||||
// {0, 0, z, 0},
|
||||
// {0, 0, 0, 1},
|
||||
// }).
|
||||
func (m *Mat4) Scale(p *Mat4, x, y, z float32) {
|
||||
m[0][0] = p[0][0] * x
|
||||
m[0][1] = p[0][1] * y
|
||||
m[0][2] = p[0][2] * z
|
||||
m[0][3] = p[0][3]
|
||||
m[1][0] = p[1][0] * x
|
||||
m[1][1] = p[1][1] * y
|
||||
m[1][2] = p[1][2] * z
|
||||
m[1][3] = p[1][3]
|
||||
m[2][0] = p[2][0] * x
|
||||
m[2][1] = p[2][1] * y
|
||||
m[2][2] = p[2][2] * z
|
||||
m[2][3] = p[2][3]
|
||||
m[3][0] = p[3][0] * x
|
||||
m[3][1] = p[3][1] * y
|
||||
m[3][2] = p[3][2] * z
|
||||
m[3][3] = p[3][3]
|
||||
}
|
||||
|
||||
// Translate sets m to be a translation followed by p.
|
||||
// It is equivalent to
|
||||
//
|
||||
// m.Mul(p, &Mat4{
|
||||
// {1, 0, 0, x},
|
||||
// {0, 1, 0, y},
|
||||
// {0, 0, 1, z},
|
||||
// {0, 0, 0, 1},
|
||||
// }).
|
||||
func (m *Mat4) Translate(p *Mat4, x, y, z float32) {
|
||||
m[0][0] = p[0][0]
|
||||
m[0][1] = p[0][1]
|
||||
m[0][2] = p[0][2]
|
||||
m[0][3] = p[0][0]*x + p[0][1]*y + p[0][2]*z + p[0][3]
|
||||
m[1][0] = p[1][0]
|
||||
m[1][1] = p[1][1]
|
||||
m[1][2] = p[1][2]
|
||||
m[1][3] = p[1][0]*x + p[1][1]*y + p[1][2]*z + p[1][3]
|
||||
m[2][0] = p[2][0]
|
||||
m[2][1] = p[2][1]
|
||||
m[2][2] = p[2][2]
|
||||
m[2][3] = p[2][0]*x + p[2][1]*y + p[2][2]*z + p[2][3]
|
||||
m[3][0] = p[3][0]
|
||||
m[3][1] = p[3][1]
|
||||
m[3][2] = p[3][2]
|
||||
m[3][3] = p[3][0]*x + p[3][1]*y + p[3][2]*z + p[3][3]
|
||||
}
|
||||
|
||||
// Rotate sets m to a rotation in radians around a specified axis, followed by p.
|
||||
// It is equivalent to m.Mul(p, affineRotation).
|
||||
func (m *Mat4) Rotate(p *Mat4, angle Radian, axis *Vec3) {
|
||||
a := *axis
|
||||
a.Normalize()
|
||||
|
||||
c, s := Cos(float32(angle)), Sin(float32(angle))
|
||||
d := 1 - c
|
||||
|
||||
m.Mul(p, &Mat4{{
|
||||
c + d*a[0]*a[1],
|
||||
0 + d*a[0]*a[1] + s*a[2],
|
||||
0 + d*a[0]*a[1] - s*a[1],
|
||||
0,
|
||||
}, {
|
||||
0 + d*a[1]*a[0] - s*a[2],
|
||||
c + d*a[1]*a[1],
|
||||
0 + d*a[1]*a[2] + s*a[0],
|
||||
0,
|
||||
}, {
|
||||
0 + d*a[2]*a[0] + s*a[1],
|
||||
0 + d*a[2]*a[1] - s*a[0],
|
||||
c + d*a[2]*a[2],
|
||||
0,
|
||||
}, {
|
||||
0, 0, 0, 1,
|
||||
}})
|
||||
}
|
||||
|
||||
func (m *Mat4) LookAt(eye, center, up *Vec3) {
|
||||
f, s, u := new(Vec3), new(Vec3), new(Vec3)
|
||||
|
||||
*f = *center
|
||||
f.Sub(f, eye)
|
||||
f.Normalize()
|
||||
|
||||
s.Cross(f, up)
|
||||
s.Normalize()
|
||||
u.Cross(s, f)
|
||||
|
||||
*m = Mat4{
|
||||
{s[0], u[0], -f[0], 0},
|
||||
{s[1], u[1], -f[1], 0},
|
||||
{s[2], u[2], -f[2], 0},
|
||||
{-s.Dot(eye), -u.Dot(eye), +f.Dot(eye), 1},
|
||||
}
|
||||
}
|
4105
exp/f32/table.go
Normal file
4105
exp/f32/table.go
Normal file
File diff suppressed because it is too large
Load diff
49
exp/f32/vec3.go
Normal file
49
exp/f32/vec3.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package f32
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Vec3 [3]float32
|
||||
|
||||
func (v Vec3) String() string {
|
||||
return fmt.Sprintf("Vec3[% 0.3f, % 0.3f, % 0.3f]", v[0], v[1], v[2])
|
||||
}
|
||||
|
||||
func (v *Vec3) Normalize() {
|
||||
sq := v.Dot(v)
|
||||
inv := 1 / Sqrt(sq)
|
||||
v[0] *= inv
|
||||
v[1] *= inv
|
||||
v[2] *= inv
|
||||
}
|
||||
|
||||
func (v *Vec3) Sub(v0, v1 *Vec3) {
|
||||
v[0] = v0[0] - v1[0]
|
||||
v[1] = v0[1] - v1[1]
|
||||
v[2] = v0[2] - v1[2]
|
||||
}
|
||||
|
||||
func (v *Vec3) Add(v0, v1 *Vec3) {
|
||||
v[0] = v0[0] + v1[0]
|
||||
v[1] = v0[1] + v1[1]
|
||||
v[2] = v0[2] + v1[2]
|
||||
}
|
||||
|
||||
func (v *Vec3) Mul(v0, v1 *Vec3) {
|
||||
v[0] = v0[0] * v1[0]
|
||||
v[1] = v0[1] * v1[1]
|
||||
v[2] = v0[2] * v1[2]
|
||||
}
|
||||
|
||||
func (v *Vec3) Cross(v0, v1 *Vec3) {
|
||||
v[0] = v0[1]*v1[2] - v0[2]*v1[1]
|
||||
v[1] = v0[2]*v1[0] - v0[0]*v1[2]
|
||||
v[2] = v0[0]*v1[1] - v0[1]*v1[0]
|
||||
}
|
||||
|
||||
func (v *Vec3) Dot(v1 *Vec3) float32 {
|
||||
return v[0]*v1[0] + v[1]*v1[1] + v[2]*v1[2]
|
||||
}
|
47
exp/f32/vec4.go
Normal file
47
exp/f32/vec4.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package f32
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Vec4 [4]float32
|
||||
|
||||
func (v Vec4) String() string {
|
||||
return fmt.Sprintf("Vec4[% 0.3f, % 0.3f, % 0.3f, % 0.3f]", v[0], v[1], v[2], v[3])
|
||||
}
|
||||
|
||||
func (v *Vec4) Normalize() {
|
||||
sq := v.Dot(v)
|
||||
inv := 1 / Sqrt(sq)
|
||||
v[0] *= inv
|
||||
v[1] *= inv
|
||||
v[2] *= inv
|
||||
v[3] *= inv
|
||||
}
|
||||
|
||||
func (v *Vec4) Sub(v0, v1 *Vec4) {
|
||||
v[0] = v0[0] - v1[0]
|
||||
v[1] = v0[1] - v1[1]
|
||||
v[2] = v0[2] - v1[2]
|
||||
v[3] = v0[3] - v1[3]
|
||||
}
|
||||
|
||||
func (v *Vec4) Add(v0, v1 *Vec4) {
|
||||
v[0] = v0[0] + v1[0]
|
||||
v[1] = v0[1] + v1[1]
|
||||
v[2] = v0[2] + v1[2]
|
||||
v[3] = v0[3] + v1[3]
|
||||
}
|
||||
|
||||
func (v *Vec4) Mul(v0, v1 *Vec4) {
|
||||
v[0] = v0[0] * v1[0]
|
||||
v[1] = v0[1] * v1[1]
|
||||
v[2] = v0[2] * v1[2]
|
||||
v[3] = v0[3] * v1[3]
|
||||
}
|
||||
|
||||
func (v *Vec4) Dot(v1 *Vec4) float32 {
|
||||
return v[0]*v1[0] + v[1]*v1[1] + v[2]*v1[2] + v[3]*v1[3]
|
||||
}
|
6
exp/font/doc.go
Normal file
6
exp/font/doc.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package font provides platform independent access to system fonts.
|
||||
package font
|
27
exp/font/font.go
Normal file
27
exp/font/font.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || darwin
|
||||
|
||||
package font
|
||||
|
||||
// Default returns the default system font.
|
||||
// The font is encoded as a TTF.
|
||||
func Default() []byte {
|
||||
b, err := buildDefault()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Monospace returns the default system fixed-pitch font.
|
||||
// The font is encoded as a TTF.
|
||||
func Monospace() []byte {
|
||||
b, err := buildMonospace()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
15
exp/font/font_android.go
Normal file
15
exp/font/font_android.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package font
|
||||
|
||||
import "os"
|
||||
|
||||
func buildDefault() ([]byte, error) {
|
||||
return os.ReadFile("/system/fonts/DroidSans.ttf")
|
||||
}
|
||||
|
||||
func buildMonospace() ([]byte, error) {
|
||||
return os.ReadFile("/system/fonts/DroidSansMono.ttf")
|
||||
}
|
76
exp/font/font_darwin.go
Normal file
76
exp/font/font_darwin.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package font
|
||||
|
||||
/*
|
||||
#cgo darwin LDFLAGS: -framework CoreFoundation -framework CoreText
|
||||
#include <CoreFoundation/CFArray.h>
|
||||
#include <CoreFoundation/CFBase.h>
|
||||
#include <CoreFoundation/CFData.h>
|
||||
#include <CoreText/CTFont.h>
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
func buildFont(f C.CTFontRef) []byte {
|
||||
ctags := C.CTFontCopyAvailableTables(f, C.kCTFontTableOptionExcludeSynthetic)
|
||||
tagsCount := C.CFArrayGetCount(ctags)
|
||||
|
||||
var tags []uint32
|
||||
var dataRefs []C.CFDataRef
|
||||
var dataLens []uint32
|
||||
|
||||
for i := C.CFIndex(0); i < tagsCount; i++ {
|
||||
tag := (C.CTFontTableTag)((uintptr)(C.CFArrayGetValueAtIndex(ctags, i)))
|
||||
dataRef := C.CTFontCopyTable(f, tag, 0) // retained
|
||||
tags = append(tags, uint32(tag))
|
||||
dataRefs = append(dataRefs, dataRef)
|
||||
dataLens = append(dataLens, uint32(C.CFDataGetLength(dataRef)))
|
||||
}
|
||||
|
||||
totalLen := 0
|
||||
for _, l := range dataLens {
|
||||
totalLen += int(l)
|
||||
}
|
||||
|
||||
// Big-endian output.
|
||||
buf := make([]byte, 0, 12+16*len(tags)+totalLen)
|
||||
write16 := func(x uint16) { buf = append(buf, byte(x>>8), byte(x)) }
|
||||
write32 := func(x uint32) { buf = append(buf, byte(x>>24), byte(x>>16), byte(x>>8), byte(x)) }
|
||||
|
||||
// File format description: http://www.microsoft.com/typography/otspec/otff.htm
|
||||
write32(0x00010000) // version 1.0
|
||||
write16(uint16(len(tags))) // numTables
|
||||
write16(0) // searchRange
|
||||
write16(0) // entrySelector
|
||||
write16(0) // rangeShift
|
||||
|
||||
// Table tags, includes offsets into following data segments.
|
||||
offset := uint32(12 + 16*len(tags)) // offset starts after table tags
|
||||
for i, tag := range tags {
|
||||
write32(tag)
|
||||
write32(0)
|
||||
write32(offset)
|
||||
write32(dataLens[i])
|
||||
offset += dataLens[i]
|
||||
}
|
||||
|
||||
// Data segments.
|
||||
for i, dataRef := range dataRefs {
|
||||
data := (*[1<<31 - 2]byte)((unsafe.Pointer)(C.CFDataGetBytePtr(dataRef)))[:dataLens[i]]
|
||||
buf = append(buf, data...)
|
||||
C.CFRelease(C.CFTypeRef(dataRef))
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func buildDefault() ([]byte, error) {
|
||||
return buildFont(C.CTFontCreateUIFontForLanguage(C.kCTFontSystemFontType, 0, 0)), nil
|
||||
}
|
||||
|
||||
func buildMonospace() ([]byte, error) {
|
||||
return buildFont(C.CTFontCreateUIFontForLanguage(C.kCTFontUserFixedPitchFontType, 0, 0)), nil
|
||||
}
|
31
exp/font/font_linux.go
Normal file
31
exp/font/font_linux.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !android
|
||||
|
||||
package font
|
||||
|
||||
import "os"
|
||||
|
||||
func buildDefault() ([]byte, error) {
|
||||
// Try Noto first, but fall back to Droid as the latter was deprecated
|
||||
noto, nerr := os.ReadFile("/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf")
|
||||
if nerr != nil {
|
||||
if droid, err := os.ReadFile("/usr/share/fonts/truetype/droid/DroidSans.ttf"); err == nil {
|
||||
return droid, nil
|
||||
}
|
||||
}
|
||||
return noto, nerr
|
||||
}
|
||||
|
||||
func buildMonospace() ([]byte, error) {
|
||||
// Try Noto first, but fall back to Droid as the latter was deprecated
|
||||
noto, nerr := os.ReadFile("/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf")
|
||||
if nerr != nil {
|
||||
if droid, err := os.ReadFile("/usr/share/fonts/truetype/droid/DroidSansMono.ttf"); err == nil {
|
||||
return droid, nil
|
||||
}
|
||||
}
|
||||
return noto, nerr
|
||||
}
|
57
exp/font/font_test.go
Normal file
57
exp/font/font_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || darwin
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func looksLikeATTF(b []byte) error {
|
||||
if len(b) < 256 {
|
||||
return fmt.Errorf("not a TTF: not enough data")
|
||||
}
|
||||
b = b[:256]
|
||||
|
||||
// Look for the 4-byte magic header. See
|
||||
// http://www.microsoft.com/typography/otspec/otff.htm
|
||||
switch string(b[:4]) {
|
||||
case "\x00\x01\x00\x00", "ttcf":
|
||||
// No-op.
|
||||
default:
|
||||
return fmt.Errorf("not a TTF: missing magic header")
|
||||
}
|
||||
|
||||
// Look for a glyf table.
|
||||
if i := bytes.Index(b, []byte("glyf")); i < 0 {
|
||||
return fmt.Errorf(`not a TTF: missing "glyf" table`)
|
||||
} else if i%0x10 != 0x0c {
|
||||
return fmt.Errorf(`not a TTF: invalid "glyf" offset 0x%02x`, i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestLoadDefault(t *testing.T) {
|
||||
def, err := buildDefault()
|
||||
if err != nil {
|
||||
t.Skip("default font not found")
|
||||
}
|
||||
if err := looksLikeATTF(def); err != nil {
|
||||
t.Errorf("default font: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadMonospace(t *testing.T) {
|
||||
mono, err := buildMonospace()
|
||||
if err != nil {
|
||||
t.Skip("mono font not found")
|
||||
}
|
||||
if err := looksLikeATTF(mono); err != nil {
|
||||
t.Errorf("monospace font: %v", err)
|
||||
}
|
||||
}
|
94
exp/gl/glutil/context_darwin_desktop.go
Normal file
94
exp/gl/glutil/context_darwin_desktop.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && !ios
|
||||
|
||||
package glutil
|
||||
|
||||
// TODO(crawshaw): Only used in glutil tests for now (cgo is not support in _test.go files).
|
||||
// TODO(crawshaw): Export some kind of Context. Work out what we can offer, where. Maybe just for tests.
|
||||
// TODO(crawshaw): Support android and windows.
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION
|
||||
#cgo LDFLAGS: -framework OpenGL
|
||||
#import <OpenGL/OpenGL.h>
|
||||
#import <OpenGL/gl3.h>
|
||||
|
||||
CGLError CGCreate(CGLContextObj* ctx) {
|
||||
CGLError err;
|
||||
CGLPixelFormatAttribute attributes[] = {
|
||||
kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
|
||||
kCGLPFAColorSize, (CGLPixelFormatAttribute)24,
|
||||
kCGLPFAAlphaSize, (CGLPixelFormatAttribute)8,
|
||||
kCGLPFADepthSize, (CGLPixelFormatAttribute)16,
|
||||
kCGLPFAAccelerated,
|
||||
kCGLPFADoubleBuffer,
|
||||
(CGLPixelFormatAttribute) 0
|
||||
};
|
||||
CGLPixelFormatObj pix;
|
||||
GLint num;
|
||||
|
||||
if ((err = CGLChoosePixelFormat(attributes, &pix, &num)) != kCGLNoError) {
|
||||
return err;
|
||||
}
|
||||
if ((err = CGLCreateContext(pix, 0, ctx)) != kCGLNoError) {
|
||||
return err;
|
||||
}
|
||||
if ((err = CGLDestroyPixelFormat(pix)) != kCGLNoError) {
|
||||
return err;
|
||||
}
|
||||
if ((err = CGLSetCurrentContext(*ctx)) != kCGLNoError) {
|
||||
return err;
|
||||
}
|
||||
if ((err = CGLLockContext(*ctx)) != kCGLNoError) {
|
||||
return err;
|
||||
}
|
||||
return kCGLNoError;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// contextGL holds a copy of the OpenGL Context from thread-local storage.
|
||||
//
|
||||
// Do not move a contextGL between goroutines or OS threads.
|
||||
type contextGL struct {
|
||||
ctx C.CGLContextObj
|
||||
}
|
||||
|
||||
// createContext creates an OpenGL context, binds it as the current context
|
||||
// stored in thread-local storage, and locks the current goroutine to an os
|
||||
// thread.
|
||||
func createContext() (*contextGL, error) {
|
||||
// The OpenGL active context is stored in TLS.
|
||||
runtime.LockOSThread()
|
||||
|
||||
c := new(contextGL)
|
||||
if cglErr := C.CGCreate(&c.ctx); cglErr != C.kCGLNoError {
|
||||
return nil, fmt.Errorf("CGL: %v", C.GoString(C.CGLErrorString(cglErr)))
|
||||
}
|
||||
|
||||
// Using attribute arrays in OpenGL 3.3 requires the use of a VBA.
|
||||
// But VBAs don't exist in ES 2. So we bind a default one.
|
||||
var id C.GLuint
|
||||
C.glGenVertexArrays(1, &id)
|
||||
C.glBindVertexArray(id)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// destroy destroys an OpenGL context and unlocks the current goroutine from
|
||||
// its os thread.
|
||||
func (c *contextGL) destroy() {
|
||||
C.CGLUnlockContext(c.ctx)
|
||||
C.CGLSetCurrentContext(nil)
|
||||
C.CGLDestroyContext(c.ctx)
|
||||
c.ctx = nil
|
||||
runtime.UnlockOSThread()
|
||||
}
|
105
exp/gl/glutil/context_x11.go
Normal file
105
exp/gl/glutil/context_x11.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux && !android
|
||||
|
||||
package glutil
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lEGL
|
||||
#include <EGL/egl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void createContext(EGLDisplay *out_dpy, EGLContext *out_ctx, EGLSurface *out_surf) {
|
||||
EGLDisplay e_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
if (!e_dpy) {
|
||||
fprintf(stderr, "eglGetDisplay failed\n");
|
||||
exit(1);
|
||||
}
|
||||
EGLint e_major, e_minor;
|
||||
if (!eglInitialize(e_dpy, &e_major, &e_minor)) {
|
||||
fprintf(stderr, "eglInitialize failed\n");
|
||||
exit(1);
|
||||
}
|
||||
eglBindAPI(EGL_OPENGL_ES_API);
|
||||
static const EGLint config_attribs[] = {
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_CONFIG_CAVEAT, EGL_NONE,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLConfig config;
|
||||
EGLint num_configs;
|
||||
if (!eglChooseConfig(e_dpy, config_attribs, &config, 1, &num_configs)) {
|
||||
fprintf(stderr, "eglChooseConfig failed\n");
|
||||
exit(1);
|
||||
}
|
||||
static const EGLint ctx_attribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL_NONE
|
||||
};
|
||||
EGLContext e_ctx = eglCreateContext(e_dpy, config, EGL_NO_CONTEXT, ctx_attribs);
|
||||
if (e_ctx == EGL_NO_CONTEXT) {
|
||||
fprintf(stderr, "eglCreateContext failed\n");
|
||||
exit(1);
|
||||
}
|
||||
static const EGLint pbuf_attribs[] = {
|
||||
EGL_NONE
|
||||
};
|
||||
EGLSurface e_surf = eglCreatePbufferSurface(e_dpy, config, pbuf_attribs);
|
||||
if (e_surf == EGL_NO_SURFACE) {
|
||||
fprintf(stderr, "eglCreatePbufferSurface failed\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!eglMakeCurrent(e_dpy, e_surf, e_surf, e_ctx)) {
|
||||
fprintf(stderr, "eglMakeCurrent failed\n");
|
||||
exit(1);
|
||||
}
|
||||
*out_surf = e_surf;
|
||||
*out_ctx = e_ctx;
|
||||
*out_dpy = e_dpy;
|
||||
}
|
||||
|
||||
void destroyContext(EGLDisplay e_dpy, EGLContext e_ctx, EGLSurface e_surf) {
|
||||
if (!eglMakeCurrent(e_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
|
||||
fprintf(stderr, "eglMakeCurrent failed\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!eglDestroySurface(e_dpy, e_surf)) {
|
||||
fprintf(stderr, "eglDestroySurface failed\n");
|
||||
exit(1);
|
||||
}
|
||||
if (!eglDestroyContext(e_dpy, e_ctx)) {
|
||||
fprintf(stderr, "eglDestroyContext failed\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type contextGL struct {
|
||||
dpy C.EGLDisplay
|
||||
ctx C.EGLContext
|
||||
surf C.EGLSurface
|
||||
}
|
||||
|
||||
func createContext() (*contextGL, error) {
|
||||
runtime.LockOSThread()
|
||||
c := &contextGL{}
|
||||
C.createContext(&c.dpy, &c.ctx, &c.surf)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *contextGL) destroy() {
|
||||
C.destroyContext(c.dpy, c.ctx, c.surf)
|
||||
runtime.UnlockOSThread()
|
||||
}
|
6
exp/gl/glutil/doc.go
Normal file
6
exp/gl/glutil/doc.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package glutil implements OpenGL utility functions.
|
||||
package glutil
|
333
exp/gl/glutil/glimage.go
Normal file
333
exp/gl/glutil/glimage.go
Normal file
|
@ -0,0 +1,333 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || darwin || windows
|
||||
|
||||
package glutil
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/geom"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
// Images maintains the shared state used by a set of *Image objects.
|
||||
type Images struct {
|
||||
glctx gl.Context
|
||||
quadXY gl.Buffer
|
||||
quadUV gl.Buffer
|
||||
program gl.Program
|
||||
pos gl.Attrib
|
||||
mvp gl.Uniform
|
||||
uvp gl.Uniform
|
||||
inUV gl.Attrib
|
||||
textureSample gl.Uniform
|
||||
|
||||
mu sync.Mutex
|
||||
activeImages int
|
||||
}
|
||||
|
||||
// NewImages creates an *Images.
|
||||
func NewImages(glctx gl.Context) *Images {
|
||||
program, err := CreateProgram(glctx, vertexShader, fragmentShader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p := &Images{
|
||||
glctx: glctx,
|
||||
quadXY: glctx.CreateBuffer(),
|
||||
quadUV: glctx.CreateBuffer(),
|
||||
program: program,
|
||||
pos: glctx.GetAttribLocation(program, "pos"),
|
||||
mvp: glctx.GetUniformLocation(program, "mvp"),
|
||||
uvp: glctx.GetUniformLocation(program, "uvp"),
|
||||
inUV: glctx.GetAttribLocation(program, "inUV"),
|
||||
textureSample: glctx.GetUniformLocation(program, "textureSample"),
|
||||
}
|
||||
|
||||
glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadXY)
|
||||
glctx.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
|
||||
glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadUV)
|
||||
glctx.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Release releases any held OpenGL resources.
|
||||
// All *Image objects must be released first, or this function panics.
|
||||
func (p *Images) Release() {
|
||||
if p.program == (gl.Program{}) {
|
||||
return
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
rem := p.activeImages
|
||||
p.mu.Unlock()
|
||||
if rem > 0 {
|
||||
panic("glutil.Images.Release called, but active *Image objects remain")
|
||||
}
|
||||
|
||||
p.glctx.DeleteProgram(p.program)
|
||||
p.glctx.DeleteBuffer(p.quadXY)
|
||||
p.glctx.DeleteBuffer(p.quadUV)
|
||||
|
||||
p.program = gl.Program{}
|
||||
}
|
||||
|
||||
// Image bridges between an *image.RGBA and an OpenGL texture.
|
||||
//
|
||||
// The contents of the *image.RGBA can be uploaded as a texture and drawn as a
|
||||
// 2D quad.
|
||||
//
|
||||
// The number of active Images must fit in the system's OpenGL texture limit.
|
||||
// The typical use of an Image is as a texture atlas.
|
||||
type Image struct {
|
||||
RGBA *image.RGBA
|
||||
|
||||
gltex gl.Texture
|
||||
width int
|
||||
height int
|
||||
images *Images
|
||||
}
|
||||
|
||||
// NewImage creates an Image of the given size.
|
||||
//
|
||||
// Both a host-memory *image.RGBA and a GL texture are created.
|
||||
func (p *Images) NewImage(w, h int) *Image {
|
||||
dx := roundToPower2(w)
|
||||
dy := roundToPower2(h)
|
||||
|
||||
// TODO(crawshaw): Using VertexAttribPointer we can pass texture
|
||||
// data with a stride, which would let us use the exact number of
|
||||
// pixels on the host instead of the rounded up power 2 size.
|
||||
m := image.NewRGBA(image.Rect(0, 0, dx, dy))
|
||||
|
||||
img := &Image{
|
||||
RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
|
||||
images: p,
|
||||
width: dx,
|
||||
height: dy,
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
p.activeImages++
|
||||
p.mu.Unlock()
|
||||
|
||||
img.gltex = p.glctx.CreateTexture()
|
||||
|
||||
p.glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
|
||||
p.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
|
||||
runtime.SetFinalizer(img, (*Image).Release)
|
||||
return img
|
||||
}
|
||||
|
||||
func roundToPower2(x int) int {
|
||||
x2 := 1
|
||||
for x2 < x {
|
||||
x2 *= 2
|
||||
}
|
||||
return x2
|
||||
}
|
||||
|
||||
// Upload copies the host image data to the GL device.
|
||||
func (img *Image) Upload() {
|
||||
img.images.glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
|
||||
img.images.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix)
|
||||
}
|
||||
|
||||
// Release invalidates the Image and removes any underlying data structures.
|
||||
// The Image cannot be used after being deleted.
|
||||
func (img *Image) Release() {
|
||||
if img.gltex == (gl.Texture{}) {
|
||||
return
|
||||
}
|
||||
|
||||
img.images.glctx.DeleteTexture(img.gltex)
|
||||
img.gltex = gl.Texture{}
|
||||
|
||||
img.images.mu.Lock()
|
||||
img.images.activeImages--
|
||||
img.images.mu.Unlock()
|
||||
}
|
||||
|
||||
// Draw draws the srcBounds part of the image onto a parallelogram, defined by
|
||||
// three of its corners, in the current GL framebuffer.
|
||||
func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
|
||||
glimage := img.images
|
||||
glctx := img.images.glctx
|
||||
|
||||
glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
|
||||
glctx.Enable(gl.BLEND)
|
||||
|
||||
// TODO(crawshaw): Adjust viewport for the top bar on android?
|
||||
glctx.UseProgram(glimage.program)
|
||||
{
|
||||
// We are drawing a parallelogram PQRS, defined by three of its
|
||||
// corners, onto the entire GL framebuffer ABCD. The two quads may
|
||||
// actually be equal, but in the general case, PQRS can be smaller,
|
||||
// and PQRS is not necessarily axis-aligned.
|
||||
//
|
||||
// A +---------------+ B
|
||||
// | P +-----+ Q |
|
||||
// | | | |
|
||||
// | S +-----+ R |
|
||||
// D +---------------+ C
|
||||
//
|
||||
// There are two co-ordinate spaces: geom space and framebuffer space.
|
||||
// In geom space, the ABCD rectangle is:
|
||||
//
|
||||
// (0, 0) (geom.Width, 0)
|
||||
// (0, geom.Height) (geom.Width, geom.Height)
|
||||
//
|
||||
// and the PQRS quad is:
|
||||
//
|
||||
// (topLeft.X, topLeft.Y) (topRight.X, topRight.Y)
|
||||
// (bottomLeft.X, bottomLeft.Y) (implicit, implicit)
|
||||
//
|
||||
// In framebuffer space, the ABCD rectangle is:
|
||||
//
|
||||
// (-1, +1) (+1, +1)
|
||||
// (-1, -1) (+1, -1)
|
||||
//
|
||||
// First of all, convert from geom space to framebuffer space. For
|
||||
// later convenience, we divide everything by 2 here: px2 is half of
|
||||
// the P.X co-ordinate (in framebuffer space).
|
||||
px2 := -0.5 + float32(topLeft.X/sz.WidthPt)
|
||||
py2 := +0.5 - float32(topLeft.Y/sz.HeightPt)
|
||||
qx2 := -0.5 + float32(topRight.X/sz.WidthPt)
|
||||
qy2 := +0.5 - float32(topRight.Y/sz.HeightPt)
|
||||
sx2 := -0.5 + float32(bottomLeft.X/sz.WidthPt)
|
||||
sy2 := +0.5 - float32(bottomLeft.Y/sz.HeightPt)
|
||||
// Next, solve for the affine transformation matrix
|
||||
// [ a00 a01 a02 ]
|
||||
// a = [ a10 a11 a12 ]
|
||||
// [ 0 0 1 ]
|
||||
// that maps A to P:
|
||||
// a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
|
||||
// and likewise maps B to Q and D to S. Solving those three constraints
|
||||
// implies that C maps to R, since affine transformations keep parallel
|
||||
// lines parallel. This gives 6 equations in 6 unknowns:
|
||||
// -a00 + a01 + a02 = 2*px2
|
||||
// -a10 + a11 + a12 = 2*py2
|
||||
// +a00 + a01 + a02 = 2*qx2
|
||||
// +a10 + a11 + a12 = 2*qy2
|
||||
// -a00 - a01 + a02 = 2*sx2
|
||||
// -a10 - a11 + a12 = 2*sy2
|
||||
// which gives:
|
||||
// a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
|
||||
// and similarly for the other elements of a.
|
||||
writeAffine(glctx, glimage.mvp, &f32.Affine{{
|
||||
qx2 - px2,
|
||||
px2 - sx2,
|
||||
qx2 + sx2,
|
||||
}, {
|
||||
qy2 - py2,
|
||||
py2 - sy2,
|
||||
qy2 + sy2,
|
||||
}})
|
||||
}
|
||||
|
||||
{
|
||||
// Mapping texture co-ordinates is similar, except that in texture
|
||||
// space, the ABCD rectangle is:
|
||||
//
|
||||
// (0,0) (1,0)
|
||||
// (0,1) (1,1)
|
||||
//
|
||||
// and the PQRS quad is always axis-aligned. First of all, convert
|
||||
// from pixel space to texture space.
|
||||
w := float32(img.width)
|
||||
h := float32(img.height)
|
||||
px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w
|
||||
py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h
|
||||
qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w
|
||||
sy := float32(srcBounds.Max.Y-img.RGBA.Rect.Min.Y) / h
|
||||
// Due to axis alignment, qy = py and sx = px.
|
||||
//
|
||||
// The simultaneous equations are:
|
||||
// 0 + 0 + a02 = px
|
||||
// 0 + 0 + a12 = py
|
||||
// a00 + 0 + a02 = qx
|
||||
// a10 + 0 + a12 = qy = py
|
||||
// 0 + a01 + a02 = sx = px
|
||||
// 0 + a11 + a12 = sy
|
||||
writeAffine(glctx, glimage.uvp, &f32.Affine{{
|
||||
qx - px,
|
||||
0,
|
||||
px,
|
||||
}, {
|
||||
0,
|
||||
sy - py,
|
||||
py,
|
||||
}})
|
||||
}
|
||||
|
||||
glctx.ActiveTexture(gl.TEXTURE0)
|
||||
glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
|
||||
glctx.Uniform1i(glimage.textureSample, 0)
|
||||
|
||||
glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
|
||||
glctx.EnableVertexAttribArray(glimage.pos)
|
||||
glctx.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
|
||||
glctx.EnableVertexAttribArray(glimage.inUV)
|
||||
glctx.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)
|
||||
|
||||
glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
|
||||
glctx.DisableVertexAttribArray(glimage.pos)
|
||||
glctx.DisableVertexAttribArray(glimage.inUV)
|
||||
|
||||
glctx.Disable(gl.BLEND)
|
||||
}
|
||||
|
||||
var quadXYCoords = f32.Bytes(binary.LittleEndian,
|
||||
-1, +1, // top left
|
||||
+1, +1, // top right
|
||||
-1, -1, // bottom left
|
||||
+1, -1, // bottom right
|
||||
)
|
||||
|
||||
var quadUVCoords = f32.Bytes(binary.LittleEndian,
|
||||
0, 0, // top left
|
||||
1, 0, // top right
|
||||
0, 1, // bottom left
|
||||
1, 1, // bottom right
|
||||
)
|
||||
|
||||
const vertexShader = `#version 100
|
||||
uniform mat3 mvp;
|
||||
uniform mat3 uvp;
|
||||
attribute vec3 pos;
|
||||
attribute vec2 inUV;
|
||||
varying vec2 UV;
|
||||
void main() {
|
||||
vec3 p = pos;
|
||||
p.z = 1.0;
|
||||
gl_Position = vec4(mvp * p, 1);
|
||||
UV = (uvp * vec3(inUV, 1)).xy;
|
||||
}
|
||||
`
|
||||
|
||||
const fragmentShader = `#version 100
|
||||
precision mediump float;
|
||||
varying vec2 UV;
|
||||
uniform sampler2D textureSample;
|
||||
void main(){
|
||||
gl_FragColor = texture2D(textureSample, UV);
|
||||
}
|
||||
`
|
212
exp/gl/glutil/glimage_test.go
Normal file
212
exp/gl/glutil/glimage_test.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || (linux && !android)
|
||||
|
||||
// TODO(crawshaw): Run tests on other OSs when more contexts are supported.
|
||||
|
||||
package glutil
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// TODO: Re-enable test.
|
||||
/*func TestImage(t *testing.T) {
|
||||
done := make(chan error)
|
||||
defer close(done)
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
ctx, err := createContext()
|
||||
done <- err
|
||||
for {
|
||||
select {
|
||||
case <-gl.WorkAvailable:
|
||||
gl.DoWork()
|
||||
case <-done:
|
||||
ctx.destroy()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
if err := <-done; err != nil {
|
||||
t.Fatalf("cannot create GL context: %v", err)
|
||||
}
|
||||
|
||||
start()
|
||||
defer stop()
|
||||
|
||||
// GL testing strategy:
|
||||
// 1. Create an offscreen framebuffer object.
|
||||
// 2. Configure framebuffer to render to a GL texture.
|
||||
// 3. Run test code: use glimage to draw testdata.
|
||||
// 4. Copy GL texture back into system memory.
|
||||
// 5. Compare to a pre-computed image.
|
||||
|
||||
f, err := os.Open("../../../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
src, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const (
|
||||
pixW = 100
|
||||
pixH = 100
|
||||
ptW = geom.Pt(50)
|
||||
ptH = geom.Pt(50)
|
||||
)
|
||||
sz := size.Event{
|
||||
WidthPx: pixW,
|
||||
HeightPx: pixH,
|
||||
WidthPt: ptW,
|
||||
HeightPt: ptH,
|
||||
PixelsPerPt: float32(pixW) / float32(ptW),
|
||||
}
|
||||
|
||||
fBuf := gl.CreateFramebuffer()
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, fBuf)
|
||||
colorBuf := gl.CreateRenderbuffer()
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, colorBuf)
|
||||
// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml
|
||||
// says that the internalFormat "must be one of the following symbolic constants:
|
||||
// GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, or GL_STENCIL_INDEX8".
|
||||
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.RGB565, pixW, pixH)
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuf)
|
||||
|
||||
if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FRAMEBUFFER_COMPLETE {
|
||||
t.Fatalf("framebuffer create failed: %v", status)
|
||||
}
|
||||
|
||||
allocs := testing.AllocsPerRun(100, func() {
|
||||
gl.ClearColor(0, 0, 1, 1) // blue
|
||||
})
|
||||
if allocs != 0 {
|
||||
t.Errorf("unexpected allocations from calling gl.ClearColor: %f", allocs)
|
||||
}
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
gl.Viewport(0, 0, pixW, pixH)
|
||||
|
||||
m := NewImage(src.Bounds().Dx(), src.Bounds().Dy())
|
||||
b := m.RGBA.Bounds()
|
||||
draw.Draw(m.RGBA, b, src, src.Bounds().Min, draw.Src)
|
||||
m.Upload()
|
||||
b.Min.X += 10
|
||||
b.Max.Y /= 2
|
||||
|
||||
// All-integer right-angled triangles offsetting the
|
||||
// box: 24-32-40, 12-16-20.
|
||||
ptTopLeft := geom.Point{0, 24}
|
||||
ptTopRight := geom.Point{32, 0}
|
||||
ptBottomLeft := geom.Point{12, 24 + 16}
|
||||
ptBottomRight := geom.Point{12 + 32, 16}
|
||||
m.Draw(sz, ptTopLeft, ptTopRight, ptBottomLeft, b)
|
||||
|
||||
// For unknown reasons, a windowless OpenGL context renders upside-
|
||||
// down. That is, a quad covering the initial viewport spans:
|
||||
//
|
||||
// (-1, -1) ( 1, -1)
|
||||
// (-1, 1) ( 1, 1)
|
||||
//
|
||||
// To avoid modifying live code for tests, we flip the rows
|
||||
// recovered from the renderbuffer. We are not the first:
|
||||
//
|
||||
// http://lists.apple.com/archives/mac-opengl/2010/Jun/msg00080.html
|
||||
got := image.NewRGBA(image.Rect(0, 0, pixW, pixH))
|
||||
upsideDownPix := make([]byte, len(got.Pix))
|
||||
gl.ReadPixels(upsideDownPix, 0, 0, pixW, pixH, gl.RGBA, gl.UNSIGNED_BYTE)
|
||||
for y := 0; y < pixH; y++ {
|
||||
i0 := (pixH - 1 - y) * got.Stride
|
||||
i1 := i0 + pixW*4
|
||||
copy(got.Pix[y*got.Stride:], upsideDownPix[i0:i1])
|
||||
}
|
||||
|
||||
drawCross(got, 0, 0)
|
||||
drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt)))
|
||||
drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt)))
|
||||
drawCross(got, pixW-1, pixH-1)
|
||||
|
||||
const wantPath = "../../../testdata/testpattern-window.png"
|
||||
f, err = os.Open(wantPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
wantSrc, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, ok := wantSrc.(*image.RGBA)
|
||||
if !ok {
|
||||
b := wantSrc.Bounds()
|
||||
want = image.NewRGBA(b)
|
||||
draw.Draw(want, b, wantSrc, b.Min, draw.Src)
|
||||
}
|
||||
|
||||
if !imageEq(got, want) {
|
||||
// Write out the image we got.
|
||||
f, err = ioutil.TempFile("", "testpattern-window-got")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
gotPath := f.Name() + ".png"
|
||||
f, err = os.Create(gotPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := png.Encode(f, got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
||||
}
|
||||
}*/
|
||||
|
||||
func drawCross(m *image.RGBA, x, y int) {
|
||||
c := color.RGBA{0xff, 0, 0, 0xff} // red
|
||||
m.SetRGBA(x+0, y-2, c)
|
||||
m.SetRGBA(x+0, y-1, c)
|
||||
m.SetRGBA(x-2, y+0, c)
|
||||
m.SetRGBA(x-1, y+0, c)
|
||||
m.SetRGBA(x+0, y+0, c)
|
||||
m.SetRGBA(x+1, y+0, c)
|
||||
m.SetRGBA(x+2, y+0, c)
|
||||
m.SetRGBA(x+0, y+1, c)
|
||||
m.SetRGBA(x+0, y+2, c)
|
||||
}
|
||||
|
||||
func eqEpsilon(x, y uint8) bool {
|
||||
const epsilon = 9
|
||||
return x-y < epsilon || y-x < epsilon
|
||||
}
|
||||
|
||||
func colorEq(c0, c1 color.RGBA) bool {
|
||||
return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A)
|
||||
}
|
||||
|
||||
func imageEq(m0, m1 *image.RGBA) bool {
|
||||
b0 := m0.Bounds()
|
||||
b1 := m1.Bounds()
|
||||
if b0 != b1 {
|
||||
return false
|
||||
}
|
||||
badPx := 0
|
||||
for y := b0.Min.Y; y < b0.Max.Y; y++ {
|
||||
for x := b0.Min.X; x < b0.Max.X; x++ {
|
||||
c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA)
|
||||
if !colorEq(c0, c1) {
|
||||
badPx++
|
||||
}
|
||||
}
|
||||
}
|
||||
badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy())
|
||||
return badFrac < 0.01
|
||||
}
|
75
exp/gl/glutil/glutil.go
Normal file
75
exp/gl/glutil/glutil.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package glutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/gl"
|
||||
)
|
||||
|
||||
// CreateProgram creates, compiles, and links a gl.Program.
|
||||
func CreateProgram(glctx gl.Context, vertexSrc, fragmentSrc string) (gl.Program, error) {
|
||||
program := glctx.CreateProgram()
|
||||
if program.Value == 0 {
|
||||
return gl.Program{}, fmt.Errorf("glutil: no programs available")
|
||||
}
|
||||
|
||||
vertexShader, err := loadShader(glctx, gl.VERTEX_SHADER, vertexSrc)
|
||||
if err != nil {
|
||||
return gl.Program{}, err
|
||||
}
|
||||
fragmentShader, err := loadShader(glctx, gl.FRAGMENT_SHADER, fragmentSrc)
|
||||
if err != nil {
|
||||
glctx.DeleteShader(vertexShader)
|
||||
return gl.Program{}, err
|
||||
}
|
||||
|
||||
glctx.AttachShader(program, vertexShader)
|
||||
glctx.AttachShader(program, fragmentShader)
|
||||
glctx.LinkProgram(program)
|
||||
|
||||
// Flag shaders for deletion when program is unlinked.
|
||||
glctx.DeleteShader(vertexShader)
|
||||
glctx.DeleteShader(fragmentShader)
|
||||
|
||||
if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 {
|
||||
defer glctx.DeleteProgram(program)
|
||||
return gl.Program{}, fmt.Errorf("glutil: %s", glctx.GetProgramInfoLog(program))
|
||||
}
|
||||
return program, nil
|
||||
}
|
||||
|
||||
func loadShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) {
|
||||
shader := glctx.CreateShader(shaderType)
|
||||
if shader.Value == 0 {
|
||||
return gl.Shader{}, fmt.Errorf("glutil: could not create shader (type %v)", shaderType)
|
||||
}
|
||||
glctx.ShaderSource(shader, src)
|
||||
glctx.CompileShader(shader)
|
||||
if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
|
||||
defer glctx.DeleteShader(shader)
|
||||
return gl.Shader{}, fmt.Errorf("shader compile: %s", glctx.GetShaderInfoLog(shader))
|
||||
}
|
||||
return shader, nil
|
||||
}
|
||||
|
||||
// writeAffine writes the contents of an Affine to a 3x3 matrix GL uniform.
|
||||
func writeAffine(glctx gl.Context, u gl.Uniform, a *f32.Affine) {
|
||||
var m [9]float32
|
||||
m[0*3+0] = a[0][0]
|
||||
m[0*3+1] = a[1][0]
|
||||
m[0*3+2] = 0
|
||||
m[1*3+0] = a[0][1]
|
||||
m[1*3+1] = a[1][1]
|
||||
m[1*3+2] = 0
|
||||
m[2*3+0] = a[0][2]
|
||||
m[2*3+1] = a[1][2]
|
||||
m[2*3+2] = 1
|
||||
glctx.UniformMatrix3fv(u, m[:])
|
||||
}
|
85
exp/sensor/android.c
Normal file
85
exp/sensor/android.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build android
|
||||
// +build android
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include <android/sensor.h>
|
||||
|
||||
#define GO_ANDROID_SENSOR_LOOPER_ID 100
|
||||
|
||||
#define GO_ANDROID_READ_TIMEOUT_MS 1000
|
||||
|
||||
ASensorEventQueue* queue = NULL;
|
||||
ALooper* looper = NULL;
|
||||
|
||||
static ASensorManager* getSensorManager() {
|
||||
#pragma clang diagnostic push
|
||||
// Builders convert C warnings to errors, so suppress the
|
||||
// error from ASensorManager_getInstance being deprecated
|
||||
// in Android 26.
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
return ASensorManager_getInstance();
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void GoAndroid_createManager() {
|
||||
ASensorManager* manager = getSensorManager();
|
||||
looper = ALooper_forThread();
|
||||
if (looper == NULL) {
|
||||
looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
|
||||
}
|
||||
queue = ASensorManager_createEventQueue(manager, looper, GO_ANDROID_SENSOR_LOOPER_ID, NULL, NULL);
|
||||
}
|
||||
|
||||
int GoAndroid_enableSensor(int s, int32_t usec) {
|
||||
ASensorManager* manager = getSensorManager();
|
||||
const ASensor* sensor = ASensorManager_getDefaultSensor(manager, s);
|
||||
if (sensor == NULL) {
|
||||
return 1;
|
||||
}
|
||||
ASensorEventQueue_enableSensor(queue, sensor);
|
||||
ASensorEventQueue_setEventRate(queue, sensor, usec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GoAndroid_disableSensor(int s) {
|
||||
ASensorManager* manager = getSensorManager();
|
||||
const ASensor* sensor = ASensorManager_getDefaultSensor(manager, s);
|
||||
ASensorEventQueue_disableSensor(queue, sensor);
|
||||
}
|
||||
|
||||
int GoAndroid_readQueue(int n, int32_t* types, int64_t* timestamps, float* vectors) {
|
||||
int id;
|
||||
int events;
|
||||
ASensorEvent event;
|
||||
int i = 0;
|
||||
// Try n times read from the event queue.
|
||||
// If anytime timeout occurs, don't retry to read and immediately return.
|
||||
// Consume the event queue entirely between polls.
|
||||
while (i < n && (id = ALooper_pollOnce(GO_ANDROID_READ_TIMEOUT_MS, NULL, &events, NULL)) >= 0) {
|
||||
if (id != GO_ANDROID_SENSOR_LOOPER_ID) {
|
||||
continue;
|
||||
}
|
||||
while (i < n && ASensorEventQueue_getEvents(queue, &event, 1)) {
|
||||
types[i] = event.type;
|
||||
timestamps[i] = event.timestamp;
|
||||
vectors[i*3] = event.vector.x;
|
||||
vectors[i*3+1] = event.vector.y;
|
||||
vectors[i*3+2] = event.vector.z;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void GoAndroid_destroyManager() {
|
||||
ASensorManager* manager = getSensorManager();
|
||||
ASensorManager_destroyEventQueue(manager, queue);
|
||||
queue = NULL;
|
||||
looper = NULL;
|
||||
}
|
206
exp/sensor/android.go
Normal file
206
exp/sensor/android.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build android
|
||||
|
||||
package sensor
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -landroid
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <android/sensor.h>
|
||||
|
||||
void GoAndroid_createManager();
|
||||
void GoAndroid_destroyManager();
|
||||
int GoAndroid_enableSensor(int, int32_t);
|
||||
void GoAndroid_disableSensor(int);
|
||||
int GoAndroid_readQueue(int n, int32_t* types, int64_t* timestamps, float* vectors);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
collectingMu sync.Mutex // guards collecting
|
||||
|
||||
// collecting is true if sensor event collecting background
|
||||
// job has already started.
|
||||
collecting bool
|
||||
)
|
||||
|
||||
// closeSignal destroys the underlying looper and event queue.
|
||||
type closeSignal struct{}
|
||||
|
||||
// readSignal reads up to len(dst) events and mutates n with
|
||||
// the number of returned events.
|
||||
type readSignal struct {
|
||||
dst []Event
|
||||
n *int
|
||||
}
|
||||
|
||||
// enableSignal enables the sensors events on the underlying
|
||||
// event queue for the specified sensor type with the specified
|
||||
// latency criterion.
|
||||
type enableSignal struct {
|
||||
t Type
|
||||
delay time.Duration
|
||||
err *error
|
||||
}
|
||||
|
||||
// disableSignal disables the events on the underlying event queue
|
||||
// from the sensor specified.
|
||||
type disableSignal struct {
|
||||
t Type
|
||||
}
|
||||
|
||||
type inOut struct {
|
||||
in interface{}
|
||||
out chan struct{}
|
||||
}
|
||||
|
||||
var inout = make(chan inOut)
|
||||
|
||||
// init inits the manager and creates a goroutine to proxy the CGO calls.
|
||||
// All actions related to an ALooper needs to be performed from the same
|
||||
// OS thread. The goroutine proxy locks itself to an OS thread and handles the
|
||||
// CGO traffic on the same thread.
|
||||
func init() {
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
C.GoAndroid_createManager()
|
||||
|
||||
for {
|
||||
v := <-inout
|
||||
switch s := v.in.(type) {
|
||||
|
||||
case enableSignal:
|
||||
usecsDelay := s.delay.Nanoseconds() / 1000
|
||||
code := int(C.GoAndroid_enableSensor(typeToInt(s.t), C.int32_t(usecsDelay)))
|
||||
if code != 0 {
|
||||
*s.err = fmt.Errorf("sensor: no default %v sensor on the device", s.t)
|
||||
}
|
||||
case disableSignal:
|
||||
C.GoAndroid_disableSensor(typeToInt(s.t))
|
||||
case readSignal:
|
||||
n := readEvents(s.dst)
|
||||
*s.n = n
|
||||
case closeSignal:
|
||||
C.GoAndroid_destroyManager()
|
||||
close(v.out)
|
||||
return // we don't need this goroutine anymore
|
||||
}
|
||||
close(v.out)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// enable enables the sensor t on sender. A non-nil sender is
|
||||
// required before calling enable.
|
||||
func enable(t Type, delay time.Duration) error {
|
||||
startCollecting()
|
||||
|
||||
var err error
|
||||
done := make(chan struct{})
|
||||
inout <- inOut{
|
||||
in: enableSignal{t: t, delay: delay, err: &err},
|
||||
out: done,
|
||||
}
|
||||
<-done
|
||||
return err
|
||||
}
|
||||
|
||||
func startCollecting() {
|
||||
collectingMu.Lock()
|
||||
defer collectingMu.Unlock()
|
||||
|
||||
if collecting {
|
||||
// already collecting.
|
||||
return
|
||||
}
|
||||
collecting = true
|
||||
|
||||
go func() {
|
||||
ev := make([]Event, 8)
|
||||
var n int
|
||||
for {
|
||||
done := make(chan struct{})
|
||||
inout <- inOut{
|
||||
in: readSignal{dst: ev, n: &n},
|
||||
out: done,
|
||||
}
|
||||
<-done
|
||||
for i := 0; i < n; i++ {
|
||||
sender.Send(ev[i])
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func disable(t Type) error {
|
||||
done := make(chan struct{})
|
||||
inout <- inOut{
|
||||
in: disableSignal{t: t},
|
||||
out: done,
|
||||
}
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
|
||||
func readEvents(e []Event) int {
|
||||
num := len(e)
|
||||
types := make([]C.int32_t, num)
|
||||
timestamps := make([]C.int64_t, num)
|
||||
vectors := make([]C.float, 3*num)
|
||||
|
||||
n := int(C.GoAndroid_readQueue(
|
||||
C.int(num),
|
||||
(*C.int32_t)(unsafe.Pointer(&types[0])),
|
||||
(*C.int64_t)(unsafe.Pointer(×tamps[0])),
|
||||
(*C.float)(unsafe.Pointer(&vectors[0]))),
|
||||
)
|
||||
for i := 0; i < n; i++ {
|
||||
e[i] = Event{
|
||||
Sensor: intToType[int(types[i])],
|
||||
Timestamp: int64(timestamps[i]),
|
||||
Data: []float64{
|
||||
float64(vectors[i*3]),
|
||||
float64(vectors[i*3+1]),
|
||||
float64(vectors[i*3+2]),
|
||||
},
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// TODO(jbd): Remove destroy?
|
||||
func destroy() error {
|
||||
done := make(chan struct{})
|
||||
inout <- inOut{
|
||||
in: closeSignal{},
|
||||
out: done,
|
||||
}
|
||||
<-done
|
||||
return nil
|
||||
}
|
||||
|
||||
var intToType = map[int]Type{
|
||||
C.ASENSOR_TYPE_ACCELEROMETER: Accelerometer,
|
||||
C.ASENSOR_TYPE_GYROSCOPE: Gyroscope,
|
||||
C.ASENSOR_TYPE_MAGNETIC_FIELD: Magnetometer,
|
||||
}
|
||||
|
||||
func typeToInt(t Type) C.int {
|
||||
for k, v := range intToType {
|
||||
if v == t {
|
||||
return C.int(k)
|
||||
}
|
||||
}
|
||||
return C.int(-1)
|
||||
}
|
153
exp/sensor/darwin_armx.go
Normal file
153
exp/sensor/darwin_armx.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && (arm || arm64)
|
||||
|
||||
package sensor
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework CoreMotion
|
||||
|
||||
#import <stdlib.h>
|
||||
|
||||
void GoIOS_createManager();
|
||||
|
||||
void GoIOS_startAccelerometer(float interval);
|
||||
void GoIOS_stopAccelerometer();
|
||||
void GoIOS_readAccelerometer(int64_t* timestamp, float* vector);
|
||||
|
||||
void GoIOS_startGyro(float interval);
|
||||
void GoIOS_stopGyro();
|
||||
void GoIOS_readGyro(int64_t* timestamp, float* vector);
|
||||
|
||||
void GoIOS_startMagneto(float interval);
|
||||
void GoIOS_stopMagneto();
|
||||
void GoIOS_readMagneto(int64_t* timestamp, float* vector);
|
||||
|
||||
void GoIOS_destroyManager();
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var channels struct {
|
||||
sync.Mutex
|
||||
done [nTypes]chan struct{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
C.GoIOS_createManager()
|
||||
}
|
||||
|
||||
// minDelay is the minimum delay allowed.
|
||||
//
|
||||
// From Event Handling Guide for iOS:
|
||||
//
|
||||
// "You can set the reporting interval to be as small as 10
|
||||
// milliseconds (ms), which corresponds to a 100 Hz update rate,
|
||||
// but most app operate sufficiently with a larger interval."
|
||||
//
|
||||
// There is no need to poll more frequently than once every 10ms.
|
||||
//
|
||||
// https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/motion_event_basics/motion_event_basics.html
|
||||
|
||||
const minDelay = 10 * time.Millisecond
|
||||
|
||||
// enable enables the sensor t on sender. A non-nil sender is
|
||||
// required before calling enable.
|
||||
func enable(t Type, delay time.Duration) error {
|
||||
channels.Lock()
|
||||
defer channels.Unlock()
|
||||
|
||||
if channels.done[t] != nil {
|
||||
return fmt.Errorf("sensor: cannot enable; %v sensor is already enabled", t)
|
||||
}
|
||||
channels.done[t] = make(chan struct{})
|
||||
|
||||
if delay < minDelay {
|
||||
delay = minDelay
|
||||
}
|
||||
interval := C.float(float64(delay) / float64(time.Second))
|
||||
|
||||
switch t {
|
||||
case Accelerometer:
|
||||
C.GoIOS_startAccelerometer(interval)
|
||||
case Gyroscope:
|
||||
C.GoIOS_startGyro(interval)
|
||||
case Magnetometer:
|
||||
C.GoIOS_startMagneto(interval)
|
||||
}
|
||||
go pollSensor(t, delay, channels.done[t])
|
||||
return nil
|
||||
}
|
||||
|
||||
func disable(t Type) error {
|
||||
channels.Lock()
|
||||
defer channels.Unlock()
|
||||
|
||||
if channels.done[t] == nil {
|
||||
return fmt.Errorf("sensor: cannot disable; %v sensor is not enabled", t)
|
||||
}
|
||||
close(channels.done[t])
|
||||
channels.done[t] = nil
|
||||
|
||||
switch t {
|
||||
case Accelerometer:
|
||||
C.GoIOS_stopAccelerometer()
|
||||
case Gyroscope:
|
||||
C.GoIOS_stopGyro()
|
||||
case Magnetometer:
|
||||
C.GoIOS_stopMagneto()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pollSensor(t Type, d time.Duration, done chan struct{}) {
|
||||
var lastTimestamp int64
|
||||
|
||||
var timestamp C.int64_t
|
||||
var ev [3]C.float
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
tp := (*C.int64_t)(unsafe.Pointer(×tamp))
|
||||
vp := (*C.float)(unsafe.Pointer(&ev[0]))
|
||||
|
||||
switch t {
|
||||
case Accelerometer:
|
||||
C.GoIOS_readAccelerometer(tp, vp)
|
||||
case Gyroscope:
|
||||
C.GoIOS_readGyro(tp, vp)
|
||||
case Magnetometer:
|
||||
C.GoIOS_readMagneto(tp, vp)
|
||||
}
|
||||
ts := int64(timestamp)
|
||||
if ts > lastTimestamp {
|
||||
// TODO(jbd): Do we need to convert the values to another unit?
|
||||
// How does iOS units compare to the Android units.
|
||||
sender.Send(Event{
|
||||
Sensor: t,
|
||||
Timestamp: ts,
|
||||
Data: []float64{float64(ev[0]), float64(ev[1]), float64(ev[2])},
|
||||
})
|
||||
lastTimestamp = ts
|
||||
time.Sleep(d / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jbd): Remove destroy?
|
||||
func destroy() error {
|
||||
C.GoIOS_destroyManager()
|
||||
return nil
|
||||
}
|
71
exp/sensor/darwin_armx.m
Normal file
71
exp/sensor/darwin_armx.m
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && (arm || arm64)
|
||||
// +build darwin
|
||||
// +build arm arm64
|
||||
|
||||
#import <CoreMotion/CoreMotion.h>
|
||||
|
||||
CMMotionManager* manager = nil;
|
||||
|
||||
void GoIOS_createManager() {
|
||||
manager = [[CMMotionManager alloc] init];
|
||||
}
|
||||
|
||||
void GoIOS_startAccelerometer(float interval) {
|
||||
manager.accelerometerUpdateInterval = interval;
|
||||
[manager startAccelerometerUpdates];
|
||||
}
|
||||
|
||||
void GoIOS_stopAccelerometer() {
|
||||
[manager stopAccelerometerUpdates];
|
||||
}
|
||||
|
||||
void GoIOS_readAccelerometer(int64_t* timestamp, float* v) {
|
||||
CMAccelerometerData* data = manager.accelerometerData;
|
||||
*timestamp = (int64_t)(data.timestamp * 1000 * 1000);
|
||||
v[0] = data.acceleration.x;
|
||||
v[1] = data.acceleration.y;
|
||||
v[2] = data.acceleration.z;
|
||||
}
|
||||
|
||||
void GoIOS_startGyro(float interval) {
|
||||
manager.gyroUpdateInterval = interval;
|
||||
[manager startGyroUpdates];
|
||||
}
|
||||
|
||||
void GoIOS_stopGyro() {
|
||||
[manager stopGyroUpdates];
|
||||
}
|
||||
|
||||
void GoIOS_readGyro(int64_t* timestamp, float* v) {
|
||||
CMGyroData* data = manager.gyroData;
|
||||
*timestamp = (int64_t)(data.timestamp * 1000 * 1000);
|
||||
v[0] = data.rotationRate.x;
|
||||
v[1] = data.rotationRate.y;
|
||||
v[2] = data.rotationRate.z;
|
||||
}
|
||||
|
||||
void GoIOS_startMagneto(float interval) {
|
||||
manager.magnetometerUpdateInterval = interval;
|
||||
[manager startMagnetometerUpdates];
|
||||
}
|
||||
|
||||
void GoIOS_stopMagneto() {
|
||||
[manager stopMagnetometerUpdates];
|
||||
}
|
||||
|
||||
void GoIOS_readMagneto(int64_t* timestamp, float* v) {
|
||||
CMMagnetometerData* data = manager.magnetometerData;
|
||||
*timestamp = (int64_t)(data.timestamp * 1000 * 1000);
|
||||
v[0] = data.magneticField.x;
|
||||
v[1] = data.magneticField.y;
|
||||
v[2] = data.magneticField.z;
|
||||
}
|
||||
|
||||
void GoIOS_destroyManager() {
|
||||
[manager release];
|
||||
manager = nil;
|
||||
}
|
24
exp/sensor/notmobile.go
Normal file
24
exp/sensor/notmobile.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (linux && !android) || (darwin && !arm && !arm64) || windows
|
||||
|
||||
package sensor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func enable(t Type, delay time.Duration) error {
|
||||
return errors.New("sensor: no sensors available")
|
||||
}
|
||||
|
||||
func disable(t Type) error {
|
||||
return errors.New("sensor: no sensors available")
|
||||
}
|
||||
|
||||
func destroy() error {
|
||||
return nil
|
||||
}
|
130
exp/sensor/sensor.go
Normal file
130
exp/sensor/sensor.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sensor provides sensor events from various movement sensors.
|
||||
package sensor
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Type represents a sensor type.
|
||||
type Type int
|
||||
|
||||
var sensorNames = map[Type]string{
|
||||
Accelerometer: "Accelerometer",
|
||||
Gyroscope: "Gyroscope",
|
||||
Magnetometer: "Magnetometer",
|
||||
}
|
||||
|
||||
// String returns the string representation of the sensor type.
|
||||
func (t Type) String() string {
|
||||
if n, ok := sensorNames[t]; ok {
|
||||
return n
|
||||
}
|
||||
return "Unknown sensor"
|
||||
}
|
||||
|
||||
const (
|
||||
Accelerometer = Type(0)
|
||||
Gyroscope = Type(1)
|
||||
Magnetometer = Type(2)
|
||||
nTypes = Type(3)
|
||||
)
|
||||
|
||||
// Event represents a sensor event.
|
||||
type Event struct {
|
||||
// Sensor is the type of the sensor the event is coming from.
|
||||
Sensor Type
|
||||
|
||||
// Timestamp is a device specific event time in nanoseconds.
|
||||
// Timestamps are not Unix times, they represent a time that is
|
||||
// only valid for the device's default sensor.
|
||||
Timestamp int64
|
||||
|
||||
// Data is the event data.
|
||||
//
|
||||
// If the event source is Accelerometer,
|
||||
// - Data[0]: acceleration force in x axis in m/s^2
|
||||
// - Data[1]: acceleration force in y axis in m/s^2
|
||||
// - Data[2]: acceleration force in z axis in m/s^2
|
||||
//
|
||||
// If the event source is Gyroscope,
|
||||
// - Data[0]: rate of rotation around the x axis in rad/s
|
||||
// - Data[1]: rate of rotation around the y axis in rad/s
|
||||
// - Data[2]: rate of rotation around the z axis in rad/s
|
||||
//
|
||||
// If the event source is Magnetometer,
|
||||
// - Data[0]: force of gravity along the x axis in m/s^2
|
||||
// - Data[1]: force of gravity along the y axis in m/s^2
|
||||
// - Data[2]: force of gravity along the z axis in m/s^2
|
||||
//
|
||||
Data []float64
|
||||
}
|
||||
|
||||
// TODO(jbd): Move Sender interface definition to a top-level package.
|
||||
|
||||
var (
|
||||
// senderMu protects sender.
|
||||
senderMu sync.Mutex
|
||||
|
||||
// sender is notified with the sensor data each time a new event is available.
|
||||
sender Sender
|
||||
)
|
||||
|
||||
// Sender sends an event.
|
||||
type Sender interface {
|
||||
Send(event interface{})
|
||||
}
|
||||
|
||||
// Notify registers a Sender and sensor events will be sent to s.
|
||||
// A typical example of Sender implementations is app.App.
|
||||
// Once you call Notify, you are not allowed to call it again.
|
||||
// You cannot call Notify with a nil Sender.
|
||||
func Notify(s Sender) {
|
||||
senderMu.Lock()
|
||||
defer senderMu.Unlock()
|
||||
|
||||
if s == nil {
|
||||
panic("sensor: cannot set a nil sender")
|
||||
}
|
||||
if sender != nil {
|
||||
panic("sensor: another sender is being notified, cannot set s as the sender")
|
||||
}
|
||||
sender = s
|
||||
}
|
||||
|
||||
// Enable enables the specified sensor type with the given delay rate.
|
||||
// Users must set a non-nil Sender via Notify before enabling a sensor,
|
||||
// otherwise an error will be returned.
|
||||
func Enable(t Type, delay time.Duration) error {
|
||||
if t < 0 || int(t) >= len(sensorNames) {
|
||||
return errors.New("sensor: unknown sensor type")
|
||||
}
|
||||
if err := validSender(); err != nil {
|
||||
return err
|
||||
}
|
||||
return enable(t, delay)
|
||||
}
|
||||
|
||||
// Disable disables to feed the manager with the specified sensor.
|
||||
// Disable is not safe for concurrent use.
|
||||
func Disable(t Type) error {
|
||||
if t < 0 || int(t) >= len(sensorNames) {
|
||||
return errors.New("sensor: unknown sensor type")
|
||||
}
|
||||
return disable(t)
|
||||
}
|
||||
|
||||
func validSender() error {
|
||||
senderMu.Lock()
|
||||
defer senderMu.Unlock()
|
||||
|
||||
if sender == nil {
|
||||
return errors.New("sensor: no senders to be notified; cannot enable the sensor")
|
||||
}
|
||||
return nil
|
||||
}
|
26
exp/sprite/clock/clock.go
Normal file
26
exp/sprite/clock/clock.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package clock provides a clock and time functions for a sprite engine.
|
||||
package clock
|
||||
|
||||
// A Time represents an instant in sprite time.
|
||||
//
|
||||
// The application using the sprite engine is responsible for
|
||||
// determining sprite time.
|
||||
//
|
||||
// Typically time 0 is when the app is initialized and time is
|
||||
// quantized at the intended frame rate. For example, an app may
|
||||
// record wall time when it is initialized
|
||||
//
|
||||
// var start = time.Now()
|
||||
//
|
||||
// and then compute the current instant in time for 60 FPS:
|
||||
//
|
||||
// now := clock.Time(time.Since(start) * 60 / time.Second)
|
||||
//
|
||||
// An application can pause or reset sprite time, but it must be aware
|
||||
// of any stateful sprite.Arranger instances that expect time to
|
||||
// continue.
|
||||
type Time int32
|
83
exp/sprite/clock/tween.go
Normal file
83
exp/sprite/clock/tween.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package clock
|
||||
|
||||
// Standard tween functions.
|
||||
//
|
||||
// Easing means a slowing near the timing boundary, as defined by
|
||||
// a cubic bezier curve. Exact parameters match the CSS properties.
|
||||
var (
|
||||
EaseIn = CubicBezier(0.42, 0, 1, 1)
|
||||
EaseOut = CubicBezier(0, 0, 0.58, 1)
|
||||
EaseInOut = CubicBezier(0.42, 0, 0.58, 1)
|
||||
)
|
||||
|
||||
// Linear computes the fraction [0,1] that t lies between [t0,t1].
|
||||
func Linear(t0, t1, t Time) float32 {
|
||||
if t >= t1 {
|
||||
return 1
|
||||
}
|
||||
if t <= t0 {
|
||||
return 0
|
||||
}
|
||||
return float32(t-t0) / float32(t1-t0)
|
||||
}
|
||||
|
||||
// CubicBezier generates a tween function determined by a Cubic Bézier curve.
|
||||
//
|
||||
// The parameters are cubic control parameters. The curve starts at (0,0)
|
||||
// going toward (x0,y0), and arrives at (1,1) coming from (x1,y1).
|
||||
func CubicBezier(x0, y0, x1, y1 float32) func(t0, t1, t Time) float32 {
|
||||
return func(start, end, now Time) float32 {
|
||||
// A Cubic-Bezier curve restricted to starting at (0,0) and
|
||||
// ending at (1,1) is defined as
|
||||
//
|
||||
// B(t) = 3*(1-t)^2*t*P0 + 3*(1-t)*t^2*P1 + t^3
|
||||
//
|
||||
// with derivative
|
||||
//
|
||||
// B'(t) = 3*(1-t)^2*P0 + 6*(1-t)*t*(P1-P0) + 3*t^2*(1-P1)
|
||||
//
|
||||
// Given a value x ∈ [0,1], we solve for t using Newton's
|
||||
// method and solve for y using t.
|
||||
|
||||
x := Linear(start, end, now)
|
||||
|
||||
// Solve for t using x.
|
||||
t := x
|
||||
for i := 0; i < 5; i++ {
|
||||
t2 := t * t
|
||||
t3 := t2 * t
|
||||
d := 1 - t
|
||||
d2 := d * d
|
||||
|
||||
nx := 3*d2*t*x0 + 3*d*t2*x1 + t3
|
||||
dxdt := 3*d2*x0 + 6*d*t*(x1-x0) + 3*t2*(1-x1)
|
||||
if dxdt == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
t -= (nx - x) / dxdt
|
||||
if t <= 0 || t >= 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if t < 0 {
|
||||
t = 0
|
||||
}
|
||||
if t > 1 {
|
||||
t = 1
|
||||
}
|
||||
|
||||
// Solve for y using t.
|
||||
t2 := t * t
|
||||
t3 := t2 * t
|
||||
d := 1 - t
|
||||
d2 := d * d
|
||||
y := 3*d2*t*y0 + 3*d*t2*y1 + t3
|
||||
|
||||
return y
|
||||
}
|
||||
}
|
53
exp/sprite/clock/tween_test.go
Normal file
53
exp/sprite/clock/tween_test.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package clock
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLinear(t *testing.T) {
|
||||
t0 := Time(0)
|
||||
t1 := Time(6 * 60)
|
||||
now := Time(3 * 60)
|
||||
|
||||
if c := Linear(t0, t1, now); c != 0.5 {
|
||||
t.Errorf("c=%.2f, want 0.5", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCubicBezier(t *testing.T) {
|
||||
t0 := Time(0)
|
||||
t1 := Time(1e6)
|
||||
|
||||
tests := []struct {
|
||||
x0, y0, x1, y1 float32
|
||||
x, y float32
|
||||
}{
|
||||
{0.00, 0.1, 0.4, 1.00, 0.0, 0.00},
|
||||
{0.00, 0.1, 0.4, 1.00, 0.1, 0.26},
|
||||
{0.00, 0.1, 0.4, 1.00, 0.5, 0.79},
|
||||
{0.00, 0.1, 0.4, 1.00, 0.9, 0.99},
|
||||
{0.00, 0.1, 0.4, 1.00, 1.0, 1.00},
|
||||
{0.36, 0.2, 0.3, 0.85, 0.0, 0.0},
|
||||
{0.36, 0.2, 0.3, 0.85, 0.3059, 0.3952},
|
||||
{0.36, 0.2, 0.3, 0.85, 0.4493, 0.6408},
|
||||
{0.36, 0.2, 0.3, 0.85, 0.8116, 0.9410},
|
||||
{0.00, 0.0, 1.0, 1.00, 0.1, 0.1},
|
||||
{0.00, 0.0, 1.0, 1.00, 0.5, 0.5},
|
||||
{0.00, 0.0, 1.0, 1.00, 0.9, 0.9},
|
||||
{0.42, 0.0, 1.0, 1.00, 0.0, 0.0},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
cb := CubicBezier(test.x0, test.y0, test.x1, test.y1)
|
||||
now := t0 + Time(float32(t1-t0)*test.x)
|
||||
y := cb(t0, t1, now)
|
||||
|
||||
const epsilon = 0.01
|
||||
diff := y - test.y
|
||||
if diff < -epsilon || +epsilon < diff {
|
||||
t.Errorf("CubicBezier(%.2f,%.2f,%.2f,%.2f): for x=%.2f got y=%.2f, want %.2f", test.x0, test.y0, test.x1, test.y1, test.x, y, test.y)
|
||||
}
|
||||
}
|
||||
}
|
161
exp/sprite/glsprite/glsprite.go
Normal file
161
exp/sprite/glsprite/glsprite.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
// Package glsprite implements a sprite Engine using OpenGL ES 2.
|
||||
//
|
||||
// Each sprite.Texture is loaded as a GL texture object and drawn
|
||||
// to the screen via an affine transform done in a simple shader.
|
||||
package glsprite
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/exp/gl/glutil"
|
||||
"golang.org/x/mobile/exp/sprite"
|
||||
"golang.org/x/mobile/exp/sprite/clock"
|
||||
"golang.org/x/mobile/geom"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
// TODO: move this into package sprite as Node.EngineFields.RelTransform??
|
||||
relTransform f32.Affine
|
||||
}
|
||||
|
||||
type texture struct {
|
||||
e *engine
|
||||
glImage *glutil.Image
|
||||
b image.Rectangle
|
||||
}
|
||||
|
||||
func (t *texture) Bounds() (w, h int) { return t.b.Dx(), t.b.Dy() }
|
||||
|
||||
func (t *texture) Download(r image.Rectangle, dst draw.Image) {
|
||||
panic("TODO")
|
||||
}
|
||||
|
||||
func (t *texture) Upload(r image.Rectangle, src image.Image) {
|
||||
draw.Draw(t.glImage.RGBA, r, src, src.Bounds().Min, draw.Src)
|
||||
t.glImage.Upload()
|
||||
}
|
||||
|
||||
func (t *texture) Release() {
|
||||
t.glImage.Release()
|
||||
delete(t.e.textures, t)
|
||||
}
|
||||
|
||||
// Engine creates an OpenGL-based sprite.Engine.
|
||||
func Engine(images *glutil.Images) sprite.Engine {
|
||||
return &engine{
|
||||
nodes: []*node{nil},
|
||||
images: images,
|
||||
textures: make(map[*texture]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type engine struct {
|
||||
images *glutil.Images
|
||||
textures map[*texture]struct{}
|
||||
nodes []*node
|
||||
|
||||
absTransforms []f32.Affine
|
||||
}
|
||||
|
||||
func (e *engine) Register(n *sprite.Node) {
|
||||
if n.EngineFields.Index != 0 {
|
||||
panic("glsprite: sprite.Node already registered")
|
||||
}
|
||||
o := &node{}
|
||||
o.relTransform.Identity()
|
||||
|
||||
e.nodes = append(e.nodes, o)
|
||||
n.EngineFields.Index = int32(len(e.nodes) - 1)
|
||||
}
|
||||
|
||||
func (e *engine) Unregister(n *sprite.Node) {
|
||||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) LoadTexture(src image.Image) (sprite.Texture, error) {
|
||||
b := src.Bounds()
|
||||
t := &texture{
|
||||
e: e,
|
||||
glImage: e.images.NewImage(b.Dx(), b.Dy()),
|
||||
b: b,
|
||||
}
|
||||
e.textures[t] = struct{}{}
|
||||
t.Upload(b, src)
|
||||
// TODO: set "glImage.Pix = nil"?? We don't need the CPU-side image any more.
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
n.EngineFields.SubTex = x
|
||||
}
|
||||
|
||||
func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
e.nodes[n.EngineFields.Index].relTransform = m
|
||||
}
|
||||
|
||||
func (e *engine) Render(scene *sprite.Node, t clock.Time, sz size.Event) {
|
||||
e.absTransforms = append(e.absTransforms[:0], f32.Affine{
|
||||
{1, 0, 0},
|
||||
{0, 1, 0},
|
||||
})
|
||||
e.render(scene, t, sz)
|
||||
}
|
||||
|
||||
func (e *engine) render(n *sprite.Node, t clock.Time, sz size.Event) {
|
||||
if n.EngineFields.Index == 0 {
|
||||
panic("glsprite: sprite.Node not registered")
|
||||
}
|
||||
if n.Arranger != nil {
|
||||
n.Arranger.Arrange(e, n, t)
|
||||
}
|
||||
|
||||
// Push absTransforms.
|
||||
// TODO: cache absolute transforms and use EngineFields.Dirty?
|
||||
rel := &e.nodes[n.EngineFields.Index].relTransform
|
||||
m := f32.Affine{}
|
||||
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
|
||||
e.absTransforms = append(e.absTransforms, m)
|
||||
|
||||
if x := n.EngineFields.SubTex; x.T != nil {
|
||||
x.T.(*texture).glImage.Draw(
|
||||
sz,
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2]),
|
||||
geom.Pt(m[1][2]),
|
||||
},
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2] + m[0][0]),
|
||||
geom.Pt(m[1][2] + m[1][0]),
|
||||
},
|
||||
geom.Point{
|
||||
geom.Pt(m[0][2] + m[0][1]),
|
||||
geom.Pt(m[1][2] + m[1][1]),
|
||||
},
|
||||
x.R,
|
||||
)
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
e.render(c, t, sz)
|
||||
}
|
||||
|
||||
// Pop absTransforms.
|
||||
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
|
||||
}
|
||||
|
||||
func (e *engine) Release() {
|
||||
for img := range e.textures {
|
||||
img.Release()
|
||||
}
|
||||
}
|
205
exp/sprite/portable/affine_test.go
Normal file
205
exp/sprite/portable/affine_test.go
Normal file
|
@ -0,0 +1,205 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package portable
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/geom"
|
||||
)
|
||||
|
||||
func TestAffine(t *testing.T) {
|
||||
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
|
||||
t.Skipf("testdata not available on %s", runtime.GOOS)
|
||||
}
|
||||
f, err := os.Open("../../../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
srcOrig, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := image.NewRGBA(srcOrig.Bounds())
|
||||
draw.Draw(src, src.Rect, srcOrig, srcOrig.Bounds().Min, draw.Src)
|
||||
|
||||
const (
|
||||
pixW = 100
|
||||
pixH = 100
|
||||
ptW = geom.Pt(50)
|
||||
ptH = geom.Pt(50)
|
||||
)
|
||||
sz := size.Event{
|
||||
WidthPx: pixW,
|
||||
HeightPx: pixH,
|
||||
WidthPt: ptW,
|
||||
HeightPt: ptH,
|
||||
PixelsPerPt: float32(pixW) / float32(ptW),
|
||||
}
|
||||
|
||||
got := image.NewRGBA(image.Rect(0, 0, pixW, pixH))
|
||||
blue := image.NewUniform(color.RGBA{B: 0xff, A: 0xff})
|
||||
draw.Draw(got, got.Bounds(), blue, image.Point{}, draw.Src)
|
||||
|
||||
b := src.Bounds()
|
||||
b.Min.X += 10
|
||||
b.Max.Y /= 2
|
||||
|
||||
var a f32.Affine
|
||||
a.Identity()
|
||||
a.Scale(&a, sz.PixelsPerPt, sz.PixelsPerPt)
|
||||
a.Translate(&a, 0, 24)
|
||||
a.Rotate(&a, float32(math.Asin(12./20)))
|
||||
// See commentary in the render method defined in portable.go.
|
||||
a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy()))
|
||||
a.Inverse(&a)
|
||||
|
||||
affine(got, src, b, nil, &a, draw.Over)
|
||||
|
||||
ptTopLeft := geom.Point{0, 24}
|
||||
ptBottomRight := geom.Point{12 + 32, 16}
|
||||
|
||||
drawCross(got, 0, 0)
|
||||
drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt)))
|
||||
drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt)))
|
||||
drawCross(got, pixW-1, pixH-1)
|
||||
|
||||
const wantPath = "../../../testdata/testpattern-window.png"
|
||||
f, err = os.Open(wantPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
wantSrc, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want, ok := wantSrc.(*image.RGBA)
|
||||
if !ok {
|
||||
b := wantSrc.Bounds()
|
||||
want = image.NewRGBA(b)
|
||||
draw.Draw(want, b, wantSrc, b.Min, draw.Src)
|
||||
}
|
||||
|
||||
if !imageEq(got, want) {
|
||||
gotPath, err := writeTempPNG("testpattern-window-got", got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAffineMask(t *testing.T) {
|
||||
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
|
||||
t.Skipf("testdata not available on %s", runtime.GOOS)
|
||||
}
|
||||
f, err := os.Open("../../../testdata/testpattern.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
srcOrig, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := srcOrig.Bounds()
|
||||
src := image.NewRGBA(b)
|
||||
draw.Draw(src, src.Rect, srcOrig, b.Min, draw.Src)
|
||||
mask := image.NewAlpha(b)
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
mask.Set(x, y, color.Alpha{A: uint8(x - b.Min.X)})
|
||||
}
|
||||
}
|
||||
want := image.NewRGBA(b)
|
||||
draw.DrawMask(want, want.Rect, src, b.Min, mask, b.Min, draw.Src)
|
||||
|
||||
a := new(f32.Affine)
|
||||
a.Identity()
|
||||
got := image.NewRGBA(b)
|
||||
affine(got, src, b, mask, a, draw.Src)
|
||||
|
||||
if !imageEq(got, want) {
|
||||
gotPath, err := writeTempPNG("testpattern-mask-got", got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantPath, err := writeTempPNG("testpattern-mask-want", want)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
|
||||
}
|
||||
}
|
||||
|
||||
func writeTempPNG(prefix string, m image.Image) (string, error) {
|
||||
f, err := os.CreateTemp("", prefix+"-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f.Close()
|
||||
path := f.Name() + ".png"
|
||||
f, err = os.Create(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := png.Encode(f, m); err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
return path, f.Close()
|
||||
}
|
||||
|
||||
func drawCross(m *image.RGBA, x, y int) {
|
||||
c := color.RGBA{0xff, 0, 0, 0xff} // red
|
||||
m.SetRGBA(x+0, y-2, c)
|
||||
m.SetRGBA(x+0, y-1, c)
|
||||
m.SetRGBA(x-2, y+0, c)
|
||||
m.SetRGBA(x-1, y+0, c)
|
||||
m.SetRGBA(x+0, y+0, c)
|
||||
m.SetRGBA(x+1, y+0, c)
|
||||
m.SetRGBA(x+2, y+0, c)
|
||||
m.SetRGBA(x+0, y+1, c)
|
||||
m.SetRGBA(x+0, y+2, c)
|
||||
}
|
||||
|
||||
func eqEpsilon(x, y uint8) bool {
|
||||
const epsilon = 9
|
||||
return x-y < epsilon || y-x < epsilon
|
||||
}
|
||||
|
||||
func colorEq(c0, c1 color.RGBA) bool {
|
||||
return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A)
|
||||
}
|
||||
|
||||
func imageEq(m0, m1 *image.RGBA) bool {
|
||||
b0 := m0.Bounds()
|
||||
b1 := m1.Bounds()
|
||||
if b0 != b1 {
|
||||
return false
|
||||
}
|
||||
badPx := 0
|
||||
for y := b0.Min.Y; y < b0.Max.Y; y++ {
|
||||
for x := b0.Min.X; x < b0.Max.X; x++ {
|
||||
c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA)
|
||||
if !colorEq(c0, c1) {
|
||||
badPx++
|
||||
}
|
||||
}
|
||||
}
|
||||
badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy())
|
||||
return badFrac < 0.01
|
||||
}
|
193
exp/sprite/portable/portable.go
Normal file
193
exp/sprite/portable/portable.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package portable implements a sprite Engine using the image package.
|
||||
//
|
||||
// It is intended to serve as a reference implementation for testing
|
||||
// other sprite Engines written against OpenGL, or other more exotic
|
||||
// modern hardware interfaces.
|
||||
package portable
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
xdraw "golang.org/x/image/draw"
|
||||
"golang.org/x/image/math/f64"
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/exp/sprite"
|
||||
"golang.org/x/mobile/exp/sprite/clock"
|
||||
)
|
||||
|
||||
// Engine builds a sprite Engine that renders onto dst.
|
||||
func Engine(dst *image.RGBA) sprite.Engine {
|
||||
return &engine{
|
||||
dst: dst,
|
||||
nodes: []*node{nil},
|
||||
}
|
||||
}
|
||||
|
||||
type node struct {
|
||||
// TODO: move this into package sprite as Node.EngineFields.RelTransform??
|
||||
relTransform f32.Affine
|
||||
}
|
||||
|
||||
type texture struct {
|
||||
m *image.RGBA
|
||||
}
|
||||
|
||||
func (t *texture) Bounds() (w, h int) {
|
||||
b := t.m.Bounds()
|
||||
return b.Dx(), b.Dy()
|
||||
}
|
||||
|
||||
func (t *texture) Download(r image.Rectangle, dst draw.Image) {
|
||||
draw.Draw(dst, r, t.m, t.m.Bounds().Min, draw.Src)
|
||||
}
|
||||
|
||||
func (t *texture) Upload(r image.Rectangle, src image.Image) {
|
||||
draw.Draw(t.m, r, src, src.Bounds().Min, draw.Src)
|
||||
}
|
||||
|
||||
func (t *texture) Release() {}
|
||||
|
||||
type engine struct {
|
||||
dst *image.RGBA
|
||||
nodes []*node
|
||||
absTransforms []f32.Affine
|
||||
}
|
||||
|
||||
func (e *engine) Register(n *sprite.Node) {
|
||||
if n.EngineFields.Index != 0 {
|
||||
panic("portable: sprite.Node already registered")
|
||||
}
|
||||
|
||||
o := &node{}
|
||||
o.relTransform.Identity()
|
||||
|
||||
e.nodes = append(e.nodes, o)
|
||||
n.EngineFields.Index = int32(len(e.nodes) - 1)
|
||||
}
|
||||
|
||||
func (e *engine) Unregister(n *sprite.Node) {
|
||||
panic("todo")
|
||||
}
|
||||
|
||||
func (e *engine) LoadTexture(m image.Image) (sprite.Texture, error) {
|
||||
b := m.Bounds()
|
||||
w, h := b.Dx(), b.Dy()
|
||||
|
||||
t := &texture{m: image.NewRGBA(image.Rect(0, 0, w, h))}
|
||||
t.Upload(b, m)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
n.EngineFields.SubTex = x
|
||||
}
|
||||
|
||||
func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
|
||||
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
|
||||
e.nodes[n.EngineFields.Index].relTransform = m
|
||||
}
|
||||
|
||||
func (e *engine) Render(scene *sprite.Node, t clock.Time, sz size.Event) {
|
||||
// Affine transforms are done in geom.Pt. When finally drawing
|
||||
// the geom.Pt onto an image.Image we need to convert to system
|
||||
// pixels. We scale by sz.PixelsPerPt to do this.
|
||||
e.absTransforms = append(e.absTransforms[:0], f32.Affine{
|
||||
{sz.PixelsPerPt, 0, 0},
|
||||
{0, sz.PixelsPerPt, 0},
|
||||
})
|
||||
e.render(scene, t)
|
||||
}
|
||||
|
||||
func (e *engine) render(n *sprite.Node, t clock.Time) {
|
||||
if n.EngineFields.Index == 0 {
|
||||
panic("portable: sprite.Node not registered")
|
||||
}
|
||||
if n.Arranger != nil {
|
||||
n.Arranger.Arrange(e, n, t)
|
||||
}
|
||||
|
||||
// Push absTransforms.
|
||||
// TODO: cache absolute transforms and use EngineFields.Dirty?
|
||||
rel := &e.nodes[n.EngineFields.Index].relTransform
|
||||
m := f32.Affine{}
|
||||
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
|
||||
e.absTransforms = append(e.absTransforms, m)
|
||||
|
||||
if x := n.EngineFields.SubTex; x.T != nil {
|
||||
// Affine transforms work in geom.Pt, which is entirely
|
||||
// independent of the number of pixels in a texture. A texture
|
||||
// of any image.Rectangle bounds rendered with
|
||||
//
|
||||
// Affine{{1, 0, 0}, {0, 1, 0}}
|
||||
//
|
||||
// should have the dimensions (1pt, 1pt). To do this we divide
|
||||
// by the pixel width and height, reducing the texture to
|
||||
// (1px, 1px) of the destination image. Multiplying by
|
||||
// sz.PixelsPerPt, done in Render above, makes it (1pt, 1pt).
|
||||
dx, dy := x.R.Dx(), x.R.Dy()
|
||||
if dx > 0 && dy > 0 {
|
||||
m.Scale(&m, 1/float32(dx), 1/float32(dy))
|
||||
// TODO(nigeltao): delete the double-inverse: one here and one
|
||||
// inside func affine.
|
||||
m.Inverse(&m) // See the documentation on the affine function.
|
||||
affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over)
|
||||
}
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
e.render(c, t)
|
||||
}
|
||||
|
||||
// Pop absTransforms.
|
||||
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
|
||||
}
|
||||
|
||||
func (e *engine) Release() {}
|
||||
|
||||
// affine draws each pixel of dst using bilinear interpolation of the
|
||||
// affine-transformed position in src. This is equivalent to:
|
||||
//
|
||||
// for each (x,y) in dst:
|
||||
// dst(x,y) = bilinear interpolation of src(a*(x,y))
|
||||
//
|
||||
// While this is the simpler implementation, it can be counter-
|
||||
// intuitive as an affine transformation is usually described in terms
|
||||
// of the source, not the destination. For example, a scale transform
|
||||
//
|
||||
// Affine{{2, 0, 0}, {0, 2, 0}}
|
||||
//
|
||||
// will produce a dst that is half the size of src. To perform a
|
||||
// traditional affine transform, use the inverse of the affine matrix.
|
||||
func affine(dst *image.RGBA, src image.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) {
|
||||
// For legacy compatibility reasons, the matrix a transforms from dst-space
|
||||
// to src-space. The golang.org/x/image/draw package's matrices transform
|
||||
// from src-space to dst-space, so we invert (and adjust for different
|
||||
// origins).
|
||||
i := *a
|
||||
i[0][2] += float32(srcb.Min.X)
|
||||
i[1][2] += float32(srcb.Min.Y)
|
||||
i.Inverse(&i)
|
||||
i[0][2] += float32(dst.Rect.Min.X)
|
||||
i[1][2] += float32(dst.Rect.Min.Y)
|
||||
m := f64.Aff3{
|
||||
float64(i[0][0]),
|
||||
float64(i[0][1]),
|
||||
float64(i[0][2]),
|
||||
float64(i[1][0]),
|
||||
float64(i[1][1]),
|
||||
float64(i[1][2]),
|
||||
}
|
||||
// TODO(nigeltao): is the caller or callee responsible for detecting
|
||||
// transforms that are simple copies or scales, for which there are faster
|
||||
// implementations in the xdraw package.
|
||||
xdraw.ApproxBiLinear.Transform(dst, m, src, srcb, op, &xdraw.Options{
|
||||
SrcMask: mask,
|
||||
})
|
||||
}
|
126
exp/sprite/sprite.go
Normal file
126
exp/sprite/sprite.go
Normal file
|
@ -0,0 +1,126 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sprite provides a 2D scene graph for rendering and animation.
|
||||
//
|
||||
// A tree of nodes is drawn by a rendering Engine, provided by another
|
||||
// package. The OS-independent Go version based on the image package is:
|
||||
//
|
||||
// golang.org/x/mobile/exp/sprite/portable
|
||||
//
|
||||
// An Engine draws a screen starting at a root Node. The tree is walked
|
||||
// depth-first, with affine transformations applied at each level.
|
||||
//
|
||||
// Nodes are rendered relative to their parent.
|
||||
//
|
||||
// Typical main loop:
|
||||
//
|
||||
// for each frame {
|
||||
// quantize time.Now() to a clock.Time
|
||||
// process UI events
|
||||
// modify the scene's nodes and animations (Arranger values)
|
||||
// e.Render(scene, t, sz)
|
||||
// }
|
||||
package sprite
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
|
||||
"golang.org/x/mobile/event/size"
|
||||
"golang.org/x/mobile/exp/f32"
|
||||
"golang.org/x/mobile/exp/sprite/clock"
|
||||
)
|
||||
|
||||
type Arranger interface {
|
||||
Arrange(e Engine, n *Node, t clock.Time)
|
||||
}
|
||||
|
||||
type Texture interface {
|
||||
Bounds() (w, h int)
|
||||
Download(r image.Rectangle, dst draw.Image)
|
||||
Upload(r image.Rectangle, src image.Image)
|
||||
Release()
|
||||
}
|
||||
|
||||
type SubTex struct {
|
||||
T Texture
|
||||
R image.Rectangle
|
||||
}
|
||||
|
||||
type Engine interface {
|
||||
Register(n *Node)
|
||||
Unregister(n *Node)
|
||||
|
||||
LoadTexture(a image.Image) (Texture, error)
|
||||
|
||||
SetSubTex(n *Node, x SubTex)
|
||||
SetTransform(n *Node, m f32.Affine) // sets transform relative to parent.
|
||||
|
||||
// Render renders the scene arranged at the given time, for the given
|
||||
// window configuration (dimensions and resolution).
|
||||
Render(scene *Node, t clock.Time, sz size.Event)
|
||||
|
||||
Release()
|
||||
}
|
||||
|
||||
// A Node is a renderable element and forms a tree of Nodes.
|
||||
type Node struct {
|
||||
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
|
||||
|
||||
Arranger Arranger
|
||||
|
||||
// EngineFields contains fields that should only be accessed by Engine
|
||||
// implementations. It is exported because such implementations can be
|
||||
// in other packages.
|
||||
EngineFields struct {
|
||||
// TODO: separate TexDirty and TransformDirty bits?
|
||||
Dirty bool
|
||||
Index int32
|
||||
SubTex SubTex
|
||||
}
|
||||
}
|
||||
|
||||
// AppendChild adds a node c as a child of n.
|
||||
//
|
||||
// It will panic if c already has a parent or siblings.
|
||||
func (n *Node) AppendChild(c *Node) {
|
||||
if c.Parent != nil || c.PrevSibling != nil || c.NextSibling != nil {
|
||||
panic("sprite: AppendChild called for an attached child Node")
|
||||
}
|
||||
last := n.LastChild
|
||||
if last != nil {
|
||||
last.NextSibling = c
|
||||
} else {
|
||||
n.FirstChild = c
|
||||
}
|
||||
n.LastChild = c
|
||||
c.Parent = n
|
||||
c.PrevSibling = last
|
||||
}
|
||||
|
||||
// RemoveChild removes a node c that is a child of n. Afterwards, c will have
|
||||
// no parent and no siblings.
|
||||
//
|
||||
// It will panic if c's parent is not n.
|
||||
func (n *Node) RemoveChild(c *Node) {
|
||||
if c.Parent != n {
|
||||
panic("sprite: RemoveChild called for a non-child Node")
|
||||
}
|
||||
if n.FirstChild == c {
|
||||
n.FirstChild = c.NextSibling
|
||||
}
|
||||
if c.NextSibling != nil {
|
||||
c.NextSibling.PrevSibling = c.PrevSibling
|
||||
}
|
||||
if n.LastChild == c {
|
||||
n.LastChild = c.PrevSibling
|
||||
}
|
||||
if c.PrevSibling != nil {
|
||||
c.PrevSibling.NextSibling = c.NextSibling
|
||||
}
|
||||
c.Parent = nil
|
||||
c.PrevSibling = nil
|
||||
c.NextSibling = nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue