func (sc *sceneCompiler) createLayeredMaterialTrees() error {
	start := time.Now()
	sc.logger.Noticef("processing %d materials", len(sc.parsedScene.Materials))
	sc.matIndexToMatRoot = make(map[int]int32, 0)
	sc.texIndexCache = make(map[string]int32, 0)
	sc.emissiveIndexCache = make(map[int]int32, 0)
	sc.optimizedScene.MaterialNodeList = make([]scene.MaterialNode, 0)
	sc.optimizedScene.TextureData = make([]byte, 0)
	sc.optimizedScene.TextureMetadata = make([]scene.TextureMetadata, 0)
	var err error
	for matIndex, mat := range sc.parsedScene.Materials {
		if !mat.Used {
			continue
		}
		sc.logger.Infof(`processing material "%s"`, mat.Name)
		sc.matRefList = make([]string, 0)
		sc.matIndexToMatRoot[matIndex], err = sc.generateMaterial(mat)
		if err != nil {
			return err
		}
		sc.emissiveIndexCache[matIndex] = sc.findMaterialNodeByBxdf(uint32(sc.matIndexToMatRoot[matIndex]), material.BxdfEmissive)
		if mat.Name == SceneDiffuseMaterialName {
			sc.optimizedScene.SceneDiffuseMatIndex = sc.matIndexToMatRoot[matIndex]
		} else if mat.Name == SceneEmissiveMaterialName {
			sc.optimizedScene.SceneEmissiveMatIndex = sc.matIndexToMatRoot[matIndex]
		}
	}
	sc.logger.Noticef("processed %d materials in %d ms", len(sc.parsedScene.Materials), time.Since(start).Nanoseconds()/1e6)
	return nil
}
Parse material definitions into a node-based structure that models a layered material.
package compiler
import (
	"fmt"
	"strings"
	"time"
	"github.com/achilleasa/polaris/asset"
	"github.com/achilleasa/polaris/asset/compiler/bvh"
	"github.com/achilleasa/polaris/asset/compiler/input"
	"github.com/achilleasa/polaris/asset/material"
	"github.com/achilleasa/polaris/asset/scene"
	"github.com/achilleasa/polaris/asset/texure"
	"github.com/achilleasa/polaris/log"
	"github.com/achilleasa/polaris/types"
)
const (
	minPrimitivesPerLeaf      = 10
	SceneDiffuseMaterialName  = "scene_diffuse_material"
	SceneEmissiveMaterialName = "scene_emissive_material"
)
type sceneCompiler struct {
	parsedScene    *input.Scene
	optimizedScene *scene.Scene
	logger         log.Logger
	matIndexToMatRoot map[int]int32
	texIndexCache map[string]int32
	emissiveIndexCache map[int]int32
	matRefList []string
}
func Compile(parsedScene *input.Scene) (*scene.Scene, error) {
	compiler := &sceneCompiler{
		parsedScene: parsedScene,
		optimizedScene: &scene.Scene{
			SceneDiffuseMatIndex:  -1,
			SceneEmissiveMatIndex: -1,
		},
		logger: log.New("scene compiler"),
	}
	start := time.Now()
	compiler.logger.Noticef("compiling scene")
	var err error
	err = compiler.createLayeredMaterialTrees()
	if err != nil {
		return nil, err
	}
	err = compiler.partitionGeometry()
	if err != nil {
		return nil, err
	}
	err = compiler.setupCamera()
	if err != nil {
		return nil, err
	}
	compiler.logger.Noticef("compiled scene in %d ms", time.Since(start).Nanoseconds()/1e6)
	return compiler.optimizedScene, nil
}
func (sc *sceneCompiler) partitionGeometry() error {
	start := time.Now()
	sc.logger.Notice("partitioning geometry")
	sc.logger.Infof("building scene BVH tree (%d meshes, %d mesh instances)", len(sc.parsedScene.Meshes), len(sc.parsedScene.MeshInstances))
	volList := make([]bvh.BoundedVolume, len(sc.parsedScene.MeshInstances))
	for index, mi := range sc.parsedScene.MeshInstances {
		volList[index] = mi
	}
	sc.optimizedScene.BvhNodeList = bvh.Build(volList, 1, func(node *scene.BvhNode, workList []bvh.BoundedVolume) {
		pmi := workList[0].(*input.MeshInstance)
		for index, mi := range sc.parsedScene.MeshInstances {
			if pmi == mi {
				node.SetMeshIndex(uint32(index))
				break
			}
		}
	}, bvh.SurfaceAreaHeuristic)
	totalVertices := 0
	for _, pm := range sc.parsedScene.Meshes {
		totalVertices += 3 * len(pm.Primitives)
	}
	sc.optimizedScene.VertexList = make([]types.Vec4, totalVertices)
	sc.optimizedScene.NormalList = make([]types.Vec4, totalVertices)
	sc.optimizedScene.UvList = make([]types.Vec2, totalVertices)
	sc.optimizedScene.MaterialIndex = make([]uint32, totalVertices/3)
	var vertexOffset uint32 = 0
	var primOffset uint32 = 0
	meshBvhRoots := make([]uint32, len(sc.parsedScene.Meshes))
	meshEmissivePrimitives := make([]*scene.EmissivePrimitive, 0)
	emissiveIndexToMeshIndexMap := make(map[int]uint32, 0)
	for mIndex, pm := range sc.parsedScene.Meshes {
		volList := make([]bvh.BoundedVolume, len(pm.Primitives))
		for index, prim := range pm.Primitives {
			volList[index] = prim
		}
		sc.logger.Infof(`building BVH tree for "%s" (%d primitives)`, pm.Name, len(pm.Primitives))
		bvhNodes := bvh.Build(volList, minPrimitivesPerLeaf, func(node *scene.BvhNode, workList []bvh.BoundedVolume) {
			node.SetPrimitives(primOffset, uint32(len(workList)))
			for _, workItem := range workList {
				prim := workItem.(*input.Primitive)
				sc.optimizedScene.VertexList[vertexOffset+0] = prim.Vertices[0].Vec4(0)
				sc.optimizedScene.VertexList[vertexOffset+1] = prim.Vertices[1].Vec4(0)
				sc.optimizedScene.VertexList[vertexOffset+2] = prim.Vertices[2].Vec4(0)
				sc.optimizedScene.NormalList[vertexOffset+0] = prim.Normals[0].Vec4(0)
				sc.optimizedScene.NormalList[vertexOffset+1] = prim.Normals[1].Vec4(0)
				sc.optimizedScene.NormalList[vertexOffset+2] = prim.Normals[2].Vec4(0)
				sc.optimizedScene.UvList[vertexOffset+0] = prim.UVs[0]
				sc.optimizedScene.UvList[vertexOffset+1] = prim.UVs[1]
				sc.optimizedScene.UvList[vertexOffset+2] = prim.UVs[2]
				matNodeIndex := sc.matIndexToMatRoot[prim.MaterialIndex]
				sc.optimizedScene.MaterialIndex[primOffset] = uint32(matNodeIndex)
				if emissiveNodeIndex := sc.emissiveIndexCache[prim.MaterialIndex]; emissiveNodeIndex != -1 {
					meshEmissivePrimitives = append(meshEmissivePrimitives, &scene.EmissivePrimitive{
						Area:              0.5 * prim.Vertices[2].Sub(prim.Vertices[0]).Cross(prim.Vertices[2].Sub(prim.Vertices[1])).Len(),
						PrimitiveIndex:    primOffset,
						MaterialNodeIndex: uint32(emissiveNodeIndex),
						Type:              scene.AreaLight,
					})
					emissiveIndexToMeshIndexMap[len(meshEmissivePrimitives)-1] = uint32(mIndex)
				}
				vertexOffset += 3
				primOffset++
			}
		}, bvh.SurfaceAreaHeuristic)
		offset := int32(len(sc.optimizedScene.BvhNodeList))
		meshBvhRoots[mIndex] = uint32(offset)
		for index, _ := range bvhNodes {
			bvhNodes[index].OffsetChildNodes(offset)
		}
		sc.optimizedScene.BvhNodeList = append(sc.optimizedScene.BvhNodeList, bvhNodes...)
	}
	sc.logger.Infof("processing %d mesh instances", len(sc.parsedScene.MeshInstances))
	sc.optimizedScene.MeshInstanceList = make([]scene.MeshInstance, len(sc.parsedScene.MeshInstances))
	for index, pmi := range sc.parsedScene.MeshInstances {
		mi := &sc.optimizedScene.MeshInstanceList[index]
		mi.MeshIndex = pmi.MeshIndex
		mi.BvhRoot = meshBvhRoots[pmi.MeshIndex]
		mi.Transform = pmi.Transform.Inv()
	}
	sc.logger.Info("creating emissive primitive copies for mesh instances")
	sc.optimizedScene.EmissivePrimitives = make([]scene.EmissivePrimitive, 0)
	for _, mi := range sc.optimizedScene.MeshInstanceList {
		for emissiveIndex, meshIndex := range emissiveIndexToMeshIndexMap {
			if mi.MeshIndex != meshIndex {
				continue
			}
			emp := *meshEmissivePrimitives[emissiveIndex]
			emp.Transform = mi.Transform
			sc.optimizedScene.EmissivePrimitives = append(sc.optimizedScene.EmissivePrimitives, emp)
		}
	}
	if sc.optimizedScene.SceneEmissiveMatIndex != -1 && sc.emissiveIndexCache[int(sc.optimizedScene.SceneEmissiveMatIndex)] != -1 {
		emp := scene.EmissivePrimitive{
			MaterialNodeIndex: uint32(sc.emissiveIndexCache[int(sc.optimizedScene.SceneEmissiveMatIndex)]),
			Type:              scene.EnvironmentLight,
		}
		sc.optimizedScene.EmissivePrimitives = append(sc.optimizedScene.EmissivePrimitives, emp)
	}
	if len(sc.optimizedScene.EmissivePrimitives) > 0 {
		sc.logger.Infof("emitted %d emissive primitives for all mesh instances (%d unique mesh emissives)", len(sc.optimizedScene.EmissivePrimitives), len(meshEmissivePrimitives))
	} else {
		sc.logger.Warning("the scene contains no emissive primitives or a global environment light; output will appear black!")
	}
	sc.logger.Noticef("partitioned geometry in %d ms", time.Since(start).Nanoseconds()/1e6)
	return nil
}
func (sc *sceneCompiler) setupCamera() error {
	sc.optimizedScene.Camera = scene.NewCamera(sc.parsedScene.Camera.FOV)
	sc.optimizedScene.Camera.Position = sc.parsedScene.Camera.Eye
	sc.optimizedScene.Camera.LookAt = sc.parsedScene.Camera.Look
	sc.optimizedScene.Camera.Up = sc.parsedScene.Camera.Up
	return nil
}
func (sc *sceneCompiler) findMaterialNodeByBxdf(nodeIndex uint32, bxdf material.BxdfType) int32 {
	node := sc.optimizedScene.MaterialNodeList[nodeIndex]
	nodeType := uint32(node.Union1[0])
	if material.IsBxdfType(nodeType) {
		if nodeType == uint32(bxdf) {
			return int32(nodeIndex)
		}
		return -1
	}
	out := sc.findMaterialNodeByBxdf(uint32(node.Union1[1]), bxdf)
	if out != -1 {
		return out
	}
	if nodeType == uint32(material.OpMix) {
		out = sc.findMaterialNodeByBxdf(uint32(node.Union1[2]), bxdf)
	}
	return out
}