diff --git a/asig/asig.go b/asig/asig.go index 237a9af..70802fe 100755 --- a/asig/asig.go +++ b/asig/asig.go @@ -32,7 +32,9 @@ type Texel struct { R, G, B, A byte } -type Texture struct { +type EmbeddedTexture struct { + cTex *C.struct_aiTexture + /** Width of the texture, in pixels * * If mHeight is zero the texture is compressed in a format @@ -70,17 +72,13 @@ type Texture struct { FormatHint string /** Data of the texture. - * - * Points to an array of mWidth * mHeight aiTexel's. - * The format of the texture data is always ARGB8888 to - * make the implementation for user of the library as easy - * as possible. If mHeight = 0 this is a pointer to a memory - * buffer of size mWidth containing the compressed texture - * data. Good luck, have fun! + * Points to an array of mWidth * mHeight aiTexel's (or just len=Width if Height=0, which happens when data is compressed). + * The format of the texture data is always RGBA8888. */ - Texels []Texel + Data []byte - Filename string + IsCompressed bool + Filename string } type Light struct { @@ -96,13 +94,28 @@ type Scene struct { cScene *C.struct_aiScene Flags SceneFlag - RootNode *Node - Meshes []*Mesh - Materials []*Material - Animations []*Animation - Textures []*Texture - Lights []*Light - Cameras []*Camera + RootNode *Node + Meshes []*Mesh + Materials []*Material + + /** Helper structure to describe an embedded texture + * + * Normally textures are contained in external files but some file formats embed + * them directly in the model file. There are two types of embedded textures: + * 1. Uncompressed textures. The color data is given in an uncompressed format. + * 2. Compressed textures stored in a file format like png or jpg. The raw file + * bytes are given so the application must utilize an image decoder (e.g. DevIL) to + * get access to the actual color data. + * + * Embedded textures are referenced from materials using strings like "*0", "*1", etc. + * as the texture paths (a single asterisk character followed by the + * zero-based index of the texture in the aiScene::mTextures array). + */ + Textures []*EmbeddedTexture + + // Animations []*Animation + // Lights []*Light + // Cameras []*Camera } func (s *Scene) releaseCResources() { @@ -141,10 +154,64 @@ func parseScene(cs *C.struct_aiScene) *Scene { s.Flags = SceneFlag(cs.mFlags) s.Meshes = parseMeshes(cs.mMeshes, uint(cs.mNumMeshes)) s.Materials = parseMaterials(cs.mMaterials, uint(cs.mNumMaterials)) + s.Textures = parseTextures(cs.mTextures, uint(s.cScene.mNumTextures)) return s } +func parseTextures(cTexIn **C.struct_aiTexture, count uint) []*EmbeddedTexture { + + if cTexIn == nil { + return []*EmbeddedTexture{} + } + + textures := make([]*EmbeddedTexture, count) + cTex := unsafe.Slice(cTexIn, count) + + for i := 0; i < int(count); i++ { + + textures[i] = &EmbeddedTexture{ + cTex: cTex[i], + Width: uint(cTex[i].mWidth), + Height: uint(cTex[i].mHeight), + FormatHint: C.GoString(&cTex[i].achFormatHint[0]), + Filename: parseAiString(cTex[i].mFilename), + Data: parseTexels(cTex[i].pcData, uint(cTex[i].mWidth), uint(cTex[i].mHeight)), + IsCompressed: cTex[i].mHeight == 0, + } + } + + return textures +} + +func parseTexels(cTexelsIn *C.struct_aiTexel, width, height uint) []byte { + + //e.g. like a png. Otherwise we have pure color data + isCompressed := height == 0 + + texelCount := width + if !isCompressed { + texelCount *= height + } + texelCount /= 4 + + data := make([]byte, texelCount*4) + cTexels := unsafe.Slice(cTexelsIn, texelCount) + + for i := 0; i < int(texelCount); i++ { + + //Order here is important as in a compressed format the order will represent arbitrary bytes, not colors. + //In aiTexel the struct field order is {b,g,r,a}, which puts A in the high bits and leads to a format of ARGB8888, therefore it must be maintained here + index := i * 4 + data[index] = byte(cTexels[i].b) + data[index+1] = byte(cTexels[i].g) + data[index+2] = byte(cTexels[i].r) + data[index+3] = byte(cTexels[i].a) + } + + return data +} + func parseMeshes(cm **C.struct_aiMesh, count uint) []*Mesh { if cm == nil { diff --git a/asig/enums.go b/asig/enums.go index 6d22e82..e97e906 100755 --- a/asig/enums.go +++ b/asig/enums.go @@ -1,5 +1,18 @@ package asig +type aiReturn int32 + +const ( + //Indicates that a function was successful + aiReturnSuccess = 0x0 + + //Indicates that a function failed + aiReturnFailure = -0x1 + + //Indicates that not enough memory was available to perform the requested operation + aiReturnOutofMemory = -0x3 +) + type SceneFlag int32 const ( diff --git a/asig/material.go b/asig/material.go index a669d15..16a9b89 100755 --- a/asig/material.go +++ b/asig/material.go @@ -24,6 +24,10 @@ enum aiReturn aiGetMaterialTexture( unsigned int* flags); */ import "C" +import ( + "errors" + "fmt" +) type Material struct { cMat *C.struct_aiMaterial @@ -59,12 +63,35 @@ type MaterialProperty struct { */ TypeInfo MatPropertyTypeInfo - /** Binary buffer to hold the property's value. - * The size of the buffer is always mDataLength. - */ + //Binary buffer to hold the property's value. Data []byte } func GetMaterialTextureCount(m *Material, texType TextureType) int { return int(C.aiGetMaterialTextureCount(m.cMat, uint32(texType))) } + +type GetMatTexInfo struct { + Path string +} + +func GetMaterialTexture(m *Material, texType TextureType, texIndex uint) (*GetMatTexInfo, error) { + + outCPath := &C.struct_aiString{} + status := aiReturn(C.aiGetMaterialTexture(m.cMat, uint32(texType), C.uint(texIndex), outCPath, nil, nil, nil, nil, nil, nil)) + if status == aiReturnSuccess { + return &GetMatTexInfo{ + Path: parseAiString(*outCPath), + }, nil + } + + if status == aiReturnFailure { + return nil, errors.New("get texture failed: " + getAiErr().Error()) + } + + if status == aiReturnOutofMemory { + return nil, errors.New("get texture failed: out of memory") + } + + return nil, errors.New("get texture failed: unknown error with code " + fmt.Sprintf("%v", status)) +} diff --git a/go.mod b/go.mod index d5ae879..7b0edf2 100755 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/bloeys/assimp-go go 1.17 -require github.com/bloeys/gglm v0.2.6 // indirect +require github.com/bloeys/gglm v0.2.6 diff --git a/main.go b/main.go index 21d42e5..d0684d3 100755 --- a/main.go +++ b/main.go @@ -1,14 +1,16 @@ package main import ( + "bytes" "fmt" + "image/png" "github.com/bloeys/assimp-go/asig" ) func main() { - scene, release, err := asig.ImportFile("tex-cube.fbx", asig.PostProcessTriangulate) + scene, release, err := asig.ImportFile("tex-cube.glb", asig.PostProcessTriangulate) defer release() if err != nil { @@ -17,7 +19,7 @@ func main() { for i := 0; i < len(scene.Meshes); i++ { - println("Mesh:", i, "; Verts:", len(scene.Meshes[i].Vertices), "; Normals:", len(scene.Meshes[i].Normals)) + println("Mesh:", i, "; Verts:", len(scene.Meshes[i].Vertices), "; Normals:", len(scene.Meshes[i].Normals), "; MatIndex:", scene.Meshes[i].MaterialIndex) for j := 0; j < len(scene.Meshes[i].Vertices); j++ { fmt.Printf("V(%v): (%v, %v, %v)\n", j, scene.Meshes[i].Vertices[j].X(), scene.Meshes[i].Vertices[j].Y(), scene.Meshes[i].Vertices[j].Z()) } @@ -25,13 +27,40 @@ func main() { for i := 0; i < len(scene.Materials); i++ { + m := scene.Materials[i] println("Mesh:", i, "; Props:", len(scene.Materials[i].Properties)) - for j := 0; j < len(scene.Materials[i].Properties); j++ { + texCount := asig.GetMaterialTextureCount(m, asig.TextureTypeDiffuse) + fmt.Println("Texture count:", texCount) - p := scene.Materials[i].Properties[j] - fmt.Printf("Data Type: %v; Len Bytes: %v; Texture Type: %v\n", p.TypeInfo.String(), len(p.Data), p.Semantic.String()) + if texCount > 0 { - fmt.Println("Texture count:", asig.GetMaterialTextureCount(scene.Materials[i], asig.TextureTypeDiffuse)) + texInfo, err := asig.GetMaterialTexture(m, asig.TextureTypeDiffuse, 0) + if err != nil { + panic(err) + } + + fmt.Printf("%v", texInfo) + } + } + + ts := scene.Textures + for i := 0; i < len(ts); i++ { + t := ts[i] + + fmt.Printf("T(%v): Name=%v, Hint=%v, Width=%v, Height=%v, NumTexels=%v", i, t.Filename, t.FormatHint, t.Width, t.Height, len(t.Data)) + + if t.FormatHint == "png" { + decodePNG(t.Data) } } } + +func decodePNG(texels []byte) { + + img, err := png.Decode(bytes.NewReader(texels)) + if err != nil { + panic("wow2: " + err.Error()) + } + + println("C:", img.At(100, 100)) +}