mirror of
https://github.com/bloeys/assimp-go.git
synced 2025-12-29 08:28:20 +00:00
622 lines
15 KiB
Go
Executable File
622 lines
15 KiB
Go
Executable File
package asig
|
|
|
|
/*
|
|
#cgo CFLAGS: -I .
|
|
#cgo LDFLAGS: -L libs
|
|
#cgo windows,amd64 LDFLAGS: -l assimp_windows_amd64
|
|
#cgo darwin,amd64 LDFLAGS: -l assimp_darwin_amd64
|
|
#cgo darwin,arm64 LDFLAGS: -l assimp_darwin_arm64
|
|
|
|
#include "wrap.c"
|
|
#include <stdlib.h>
|
|
*/
|
|
import "C"
|
|
import (
|
|
"errors"
|
|
"unsafe"
|
|
|
|
"github.com/bloeys/gglm/gglm"
|
|
)
|
|
|
|
type Node struct {
|
|
Name string
|
|
|
|
//The transformation relative to the node's parent
|
|
Transformation *gglm.Mat4
|
|
|
|
//Parent node. NULL if this node is the root node
|
|
Parent *Node
|
|
|
|
//The child nodes of this node. NULL if mNumChildren is 0
|
|
Children []*Node
|
|
|
|
//Each entry is an index into the mesh list of the scene
|
|
MeshIndicies []uint
|
|
|
|
/** Metadata associated with this node or NULL if there is no metadata.
|
|
* Whether any metadata is generated depends on the source file format. See the
|
|
* @link importer_notes @endlink page for more information on every source file
|
|
* format. Importers that don't document any metadata don't write any.
|
|
*/
|
|
Metadata map[string]Metadata
|
|
}
|
|
|
|
type Animation 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
|
|
* like JPEG. In this case mWidth specifies the size of the
|
|
* memory area pcData is pointing to, in bytes.
|
|
*/
|
|
Width uint
|
|
|
|
/** Height of the texture, in pixels
|
|
*
|
|
* If this value is zero, pcData points to an compressed texture
|
|
* in any format (e.g. JPEG).
|
|
*/
|
|
Height uint
|
|
|
|
/** A hint from the loader to make it easier for applications
|
|
* to determine the type of embedded textures.
|
|
*
|
|
* If Height != 0 this member is show how data is packed. Hint will consist of
|
|
* two parts: channel order and channel bitness (count of the bits for every
|
|
* color channel). For simple parsing by the viewer it's better to not omit
|
|
* absent color channel and just use 0 for bitness. For example:
|
|
* 1. Image contain RGBA and 8 bit per channel, achFormatHint == "rgba8888";
|
|
* 2. Image contain ARGB and 8 bit per channel, achFormatHint == "argb8888";
|
|
* 3. Image contain RGB and 5 bit for R and B channels and 6 bit for G channel, achFormatHint == "rgba5650";
|
|
* 4. One color image with B channel and 1 bit for it, achFormatHint == "rgba0010";
|
|
* If mHeight == 0 then achFormatHint is set set to '\\0\\0\\0\\0' if the loader has no additional
|
|
* information about the texture file format used OR the
|
|
* file extension of the format without a trailing dot. If there
|
|
* are multiple file extensions for a format, the shortest
|
|
* extension is chosen (JPEG maps to 'jpg', not to 'jpeg').
|
|
* E.g. 'dds\\0', 'pcx\\0', 'jpg\\0'. All characters are lower-case.
|
|
* The fourth character will always be '\\0'.
|
|
*/
|
|
FormatHint string
|
|
|
|
/** Data of the texture.
|
|
* Points to an array of Width * Height (or just len=Width if Height=0, which happens when data is compressed, like if the data is a PNG).
|
|
* The format of the texture data is always ARGB8888.
|
|
*/
|
|
Data []byte
|
|
|
|
IsCompressed bool
|
|
Filename string
|
|
}
|
|
|
|
type Light struct {
|
|
}
|
|
|
|
type Camera struct {
|
|
}
|
|
|
|
type Metadata struct {
|
|
Type MetadataType
|
|
Value interface{}
|
|
}
|
|
|
|
type MetadataEntry struct {
|
|
Data []byte
|
|
}
|
|
|
|
type Scene struct {
|
|
cScene *C.struct_aiScene
|
|
Flags SceneFlag
|
|
|
|
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() {
|
|
C.aiReleaseImport(s.cScene)
|
|
}
|
|
|
|
//
|
|
// Assimp API
|
|
//
|
|
|
|
func ImportFile(file string, postProcessFlags PostProcess) (s *Scene, release func(), err error) {
|
|
|
|
cstr := C.CString(file)
|
|
defer C.free(unsafe.Pointer(cstr))
|
|
|
|
cs := C.aiImportFile(cstr, C.uint(postProcessFlags))
|
|
if cs == nil {
|
|
return nil, func() {}, getAiErr()
|
|
}
|
|
|
|
s = parseScene(cs)
|
|
return s, func() { s.releaseCResources() }, nil
|
|
}
|
|
|
|
func getAiErr() error {
|
|
return errors.New("asig error: " + C.GoString(C.aiGetErrorString()))
|
|
}
|
|
|
|
//
|
|
// Parsers
|
|
//
|
|
|
|
func parseScene(cs *C.struct_aiScene) *Scene {
|
|
|
|
s := &Scene{cScene: cs}
|
|
s.Flags = SceneFlag(cs.mFlags)
|
|
s.RootNode = parseRootNode(cs.mRootNode)
|
|
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 parseRootNode(cNodesIn *C.struct_aiNode) *Node {
|
|
|
|
rn := &Node{
|
|
Name: parseAiString(cNodesIn.mName),
|
|
Transformation: parseMat4(&cNodesIn.mTransformation),
|
|
Parent: nil,
|
|
MeshIndicies: parseUInts(cNodesIn.mMeshes, uint(cNodesIn.mNumMeshes)),
|
|
Metadata: parseMetadata(cNodesIn.mMetaData),
|
|
}
|
|
|
|
rn.Children = parseNodes(cNodesIn.mChildren, rn, uint(cNodesIn.mNumChildren))
|
|
return rn
|
|
}
|
|
|
|
func parseNodes(cNodesIn **C.struct_aiNode, parent *Node, parentChildrenCount uint) []*Node {
|
|
|
|
if cNodesIn == nil {
|
|
return []*Node{}
|
|
}
|
|
|
|
nodes := make([]*Node, parentChildrenCount)
|
|
cNodes := unsafe.Slice(cNodesIn, parentChildrenCount)
|
|
|
|
for i := 0; i < len(nodes); i++ {
|
|
|
|
n := cNodes[i]
|
|
|
|
//Fill basic node info
|
|
nodes[i] = &Node{
|
|
Name: parseAiString(n.mName),
|
|
Transformation: parseMat4(&n.mTransformation),
|
|
Parent: parent,
|
|
MeshIndicies: parseUInts(n.mMeshes, uint(n.mNumMeshes)),
|
|
Metadata: parseMetadata(n.mMetaData),
|
|
}
|
|
|
|
//Parse node's children
|
|
nodes[i].Children = parseNodes(n.mChildren, nodes[i], parentChildrenCount)
|
|
}
|
|
|
|
return nodes
|
|
}
|
|
|
|
func parseMetadata(cMetaIn *C.struct_aiMetadata) map[string]Metadata {
|
|
|
|
if cMetaIn == nil {
|
|
return map[string]Metadata{}
|
|
}
|
|
|
|
meta := make(map[string]Metadata, cMetaIn.mNumProperties)
|
|
cKeys := unsafe.Slice(cMetaIn.mKeys, cMetaIn.mNumProperties)
|
|
cVals := unsafe.Slice(cMetaIn.mValues, cMetaIn.mNumProperties)
|
|
|
|
for i := 0; i < int(cMetaIn.mNumProperties); i++ {
|
|
|
|
meta[parseAiString(cKeys[i])] = parseMetadataEntry(cVals[i])
|
|
}
|
|
|
|
return meta
|
|
}
|
|
|
|
func parseMetadataEntry(cv C.struct_aiMetadataEntry) Metadata {
|
|
|
|
m := Metadata{Type: MetadataType(cv.mType)}
|
|
|
|
if cv.mData == nil {
|
|
return m
|
|
}
|
|
|
|
switch m.Type {
|
|
case MetadataTypeBool:
|
|
m.Value = *(*bool)(cv.mData)
|
|
case MetadataTypeFloat32:
|
|
m.Value = *(*float32)(cv.mData)
|
|
case MetadataTypeFloat64:
|
|
m.Value = *(*float64)(cv.mData)
|
|
case MetadataTypeInt32:
|
|
m.Value = *(*int32)(cv.mData)
|
|
case MetadataTypeUint64:
|
|
m.Value = *(*uint64)(cv.mData)
|
|
case MetadataTypeString:
|
|
m.Value = parseAiString(*(*C.struct_aiString)(cv.mData))
|
|
case MetadataTypeVec3:
|
|
m.Value = parseVec3((*C.struct_aiVector3D)(cv.mData))
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
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 {
|
|
return []*Mesh{}
|
|
}
|
|
|
|
meshes := make([]*Mesh, count)
|
|
cmeshes := unsafe.Slice(cm, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
|
|
m := &Mesh{}
|
|
|
|
cmesh := cmeshes[i]
|
|
vertCount := uint(cmesh.mNumVertices)
|
|
|
|
m.Vertices = parseVec3s(cmesh.mVertices, vertCount)
|
|
m.Normals = parseVec3s(cmesh.mNormals, vertCount)
|
|
m.Tangents = parseVec3s(cmesh.mTangents, vertCount)
|
|
m.BitTangents = parseVec3s(cmesh.mBitangents, vertCount)
|
|
|
|
//Color sets
|
|
m.ColorSets = parseColorSet(cmesh.mColors, vertCount)
|
|
|
|
//Tex coords
|
|
m.TexCoords = parseTexCoords(cmesh.mTextureCoords, vertCount)
|
|
m.TexCoordChannelCount = [8]uint{}
|
|
for j := 0; j < len(cmesh.mTextureCoords); j++ {
|
|
|
|
//If a color set isn't available then it is nil
|
|
if cmesh.mTextureCoords[j] == nil {
|
|
continue
|
|
}
|
|
|
|
m.TexCoordChannelCount[j] = uint(cmeshes[j].mNumUVComponents[j])
|
|
}
|
|
|
|
//Faces
|
|
cFaces := unsafe.Slice(cmesh.mFaces, cmesh.mNumFaces)
|
|
m.Faces = make([]Face, cmesh.mNumFaces)
|
|
for j := 0; j < len(m.Faces); j++ {
|
|
|
|
m.Faces[j] = Face{
|
|
Indices: parseUInts(cFaces[j].mIndices, uint(cFaces[j].mNumIndices)),
|
|
}
|
|
}
|
|
|
|
//Other
|
|
m.Bones = parseBones(cmesh.mBones, uint(cmesh.mNumBones))
|
|
m.AnimMeshes = parseAnimMeshes(cmesh.mAnimMeshes, uint(cmesh.mNumAnimMeshes))
|
|
m.AABB = AABB{
|
|
Min: parseVec3(&cmesh.mAABB.mMin),
|
|
Max: parseVec3(&cmesh.mAABB.mMax),
|
|
}
|
|
|
|
m.MorphMethod = MorphMethod(cmesh.mMethod)
|
|
m.MaterialIndex = uint(cmesh.mMaterialIndex)
|
|
m.Name = parseAiString(cmesh.mName)
|
|
|
|
meshes[i] = m
|
|
}
|
|
|
|
return meshes
|
|
}
|
|
|
|
func parseVec3(cv *C.struct_aiVector3D) gglm.Vec3 {
|
|
|
|
if cv == nil {
|
|
return gglm.Vec3{}
|
|
}
|
|
|
|
return gglm.Vec3{
|
|
Data: [3]float32{
|
|
float32(cv.x),
|
|
float32(cv.y),
|
|
float32(cv.z),
|
|
},
|
|
}
|
|
}
|
|
|
|
func parseAnimMeshes(cam **C.struct_aiAnimMesh, count uint) []*AnimMesh {
|
|
|
|
if cam == nil {
|
|
return []*AnimMesh{}
|
|
}
|
|
|
|
animMeshes := make([]*AnimMesh, count)
|
|
cAnimMeshes := unsafe.Slice(cam, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
|
|
m := cAnimMeshes[i]
|
|
animMeshes[i] = &AnimMesh{
|
|
Name: parseAiString(m.mName),
|
|
Vertices: parseVec3s(m.mVertices, uint(m.mNumVertices)),
|
|
Normals: parseVec3s(m.mNormals, uint(m.mNumVertices)),
|
|
Tangents: parseVec3s(m.mTangents, uint(m.mNumVertices)),
|
|
BitTangents: parseVec3s(m.mBitangents, uint(m.mNumVertices)),
|
|
Colors: parseColorSet(m.mColors, uint(m.mNumVertices)),
|
|
TexCoords: parseTexCoords(m.mTextureCoords, uint(m.mNumVertices)),
|
|
Weight: float32(m.mWeight),
|
|
}
|
|
}
|
|
|
|
return animMeshes
|
|
}
|
|
|
|
func parseTexCoords(ctc [MaxTexCoords]*C.struct_aiVector3D, vertCount uint) [MaxTexCoords][]gglm.Vec3 {
|
|
|
|
texCoords := [MaxTexCoords][]gglm.Vec3{}
|
|
|
|
for j := 0; j < len(ctc); j++ {
|
|
|
|
//If a color set isn't available then it is nil
|
|
if ctc[j] == nil {
|
|
continue
|
|
}
|
|
|
|
texCoords[j] = parseVec3s(ctc[j], vertCount)
|
|
}
|
|
|
|
return texCoords
|
|
}
|
|
|
|
func parseColorSet(cc [MaxColorSets]*C.struct_aiColor4D, vertCount uint) [MaxColorSets][]gglm.Vec4 {
|
|
|
|
colorSet := [MaxColorSets][]gglm.Vec4{}
|
|
for j := 0; j < len(cc); j++ {
|
|
|
|
//If a color set isn't available then it is nil
|
|
if cc[j] == nil {
|
|
continue
|
|
}
|
|
|
|
colorSet[j] = parseColors(cc[j], vertCount)
|
|
}
|
|
|
|
return colorSet
|
|
}
|
|
|
|
func parseBones(cbs **C.struct_aiBone, count uint) []*Bone {
|
|
|
|
if cbs == nil {
|
|
return []*Bone{}
|
|
}
|
|
|
|
bones := make([]*Bone, count)
|
|
cbones := unsafe.Slice(cbs, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
|
|
cBone := cbones[i]
|
|
bones[i] = &Bone{
|
|
Name: parseAiString(cBone.mName),
|
|
Weights: parseVertexWeights(cBone.mWeights, uint(cBone.mNumWeights)),
|
|
OffsetMatrix: *parseMat4(&cBone.mOffsetMatrix),
|
|
}
|
|
}
|
|
|
|
return bones
|
|
}
|
|
|
|
func parseMat4(cm4 *C.struct_aiMatrix4x4) *gglm.Mat4 {
|
|
|
|
if cm4 == nil {
|
|
return &gglm.Mat4{}
|
|
}
|
|
|
|
return &gglm.Mat4{
|
|
Data: [4][4]float32{
|
|
{float32(cm4.a1), float32(cm4.b1), float32(cm4.c1), float32(cm4.d1)},
|
|
{float32(cm4.a2), float32(cm4.b2), float32(cm4.c2), float32(cm4.d2)},
|
|
{float32(cm4.a3), float32(cm4.b3), float32(cm4.c3), float32(cm4.d3)},
|
|
{float32(cm4.a4), float32(cm4.b4), float32(cm4.c4), float32(cm4.d4)},
|
|
},
|
|
}
|
|
}
|
|
|
|
func parseVertexWeights(cWeights *C.struct_aiVertexWeight, count uint) []VertexWeight {
|
|
|
|
if cWeights == nil {
|
|
return []VertexWeight{}
|
|
}
|
|
|
|
vw := make([]VertexWeight, count)
|
|
cvw := unsafe.Slice(cWeights, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
|
|
vw[i] = VertexWeight{
|
|
VertIndex: uint(cvw[i].mVertexId),
|
|
Weight: float32(cvw[i].mWeight),
|
|
}
|
|
}
|
|
|
|
return vw
|
|
}
|
|
|
|
func parseAiString(aiString C.struct_aiString) string {
|
|
return C.GoStringN(&aiString.data[0], C.int(aiString.length))
|
|
}
|
|
|
|
func parseUInts(cui *C.uint, count uint) []uint {
|
|
|
|
if cui == nil {
|
|
return []uint{}
|
|
}
|
|
|
|
uints := make([]uint, count)
|
|
cUInts := unsafe.Slice(cui, count)
|
|
for i := 0; i < len(cUInts); i++ {
|
|
uints[i] = uint(cUInts[i])
|
|
}
|
|
|
|
return uints
|
|
}
|
|
|
|
func parseVec3s(cv *C.struct_aiVector3D, count uint) []gglm.Vec3 {
|
|
|
|
if cv == nil {
|
|
return []gglm.Vec3{}
|
|
}
|
|
|
|
carr := unsafe.Slice(cv, count)
|
|
verts := make([]gglm.Vec3, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
verts[i] = gglm.Vec3{
|
|
Data: [3]float32{
|
|
float32(carr[i].x),
|
|
float32(carr[i].y),
|
|
float32(carr[i].z),
|
|
},
|
|
}
|
|
}
|
|
|
|
return verts
|
|
}
|
|
|
|
func parseColors(cv *C.struct_aiColor4D, count uint) []gglm.Vec4 {
|
|
|
|
if cv == nil {
|
|
return []gglm.Vec4{}
|
|
}
|
|
|
|
carr := unsafe.Slice(cv, count)
|
|
verts := make([]gglm.Vec4, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
verts[i] = gglm.Vec4{
|
|
Data: [4]float32{
|
|
float32(carr[i].r),
|
|
float32(carr[i].g),
|
|
float32(carr[i].b),
|
|
float32(carr[i].a),
|
|
},
|
|
}
|
|
}
|
|
|
|
return verts
|
|
}
|
|
|
|
func parseMaterials(cMatsIn **C.struct_aiMaterial, count uint) []*Material {
|
|
|
|
mats := make([]*Material, count)
|
|
cMats := unsafe.Slice(cMatsIn, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
|
|
mats[i] = &Material{
|
|
cMat: cMats[i],
|
|
Properties: parseMatProperties(cMats[i].mProperties, uint(cMats[i].mNumProperties)),
|
|
AllocatedStorage: uint(cMats[i].mNumAllocated),
|
|
}
|
|
}
|
|
|
|
return mats
|
|
}
|
|
|
|
func parseMatProperties(cMatPropsIn **C.struct_aiMaterialProperty, count uint) []*MaterialProperty {
|
|
|
|
matProps := make([]*MaterialProperty, count)
|
|
cMatProps := unsafe.Slice(cMatPropsIn, count)
|
|
|
|
for i := 0; i < int(count); i++ {
|
|
|
|
cmp := cMatProps[i]
|
|
|
|
matProps[i] = &MaterialProperty{
|
|
name: parseAiString(cmp.mKey),
|
|
Semantic: TextureType(cmp.mSemantic),
|
|
Index: uint(cmp.mIndex),
|
|
TypeInfo: MatPropertyTypeInfo(cmp.mType),
|
|
Data: C.GoBytes(unsafe.Pointer(cmp.mData), C.int(cmp.mDataLength)),
|
|
}
|
|
}
|
|
|
|
return matProps
|
|
}
|