Updating Tetra3D addon for Blender 4.

The version of the GLTF exporter included with Blender 4 only supports separate GLTF + BIN + Textures, or GLB, so I'm updating Tetra3D to work with separate BIN files and removing the embedded GLTF option from the Tetra3D addon.
Adding Material-level visibility.
Updating a variety of demos to use separate GLTFs or GLBs.
Adding Camera.RenderCube() to easily render a cube on-screen.
Camera Debug frametimes have been adjusted to no longer be averaged over time, as this broken if a Camera was used to render multiple objects in a single frame.
pull/31/head
solarlune 2023-11-19 13:28:20 -08:00
parent 15f16a2b35
commit 4ed95eee36
55 changed files with 613 additions and 8022 deletions

100
camera.go
View File

@ -15,21 +15,20 @@ import (
// DebugInfo is a struct that holds debugging information for a Camera's render pass. These values are reset when Camera.Clear() is called.
type DebugInfo struct {
AvgFrameTime time.Duration // Amount of CPU frame time spent transforming vertices and calling Image.DrawTriangles. Doesn't include time ebitengine spends flushing the command queue.
AvgAnimationTime time.Duration // Amount of CPU frame time spent animating vertices.
AvgLightTime time.Duration // Amount of CPU frame time spent lighting vertices.
animationTime time.Duration
lightTime time.Duration
frameTime time.Duration
frameCount int
tickTime time.Time
DrawnParts int // Number of draw calls, excluding those invisible or culled based on distance
TotalParts int // Total number of draw calls
BatchedParts int // Total batched number of draw calls
DrawnTris int // Number of drawn triangles, excluding those hidden from backface culling
TotalTris int // Total number of triangles
LightCount int // Total number of lights
ActiveLightCount int // Total active number of lights
FrameTime time.Duration // Amount of CPU frame time spent transforming vertices and calling Image.DrawTriangles. Doesn't include time ebitengine spends flushing the command queue.
AnimationTime time.Duration // Amount of CPU frame time spent animating vertices.
LightTime time.Duration // Amount of CPU frame time spent lighting vertices.
currentAnimationTime time.Duration
currentLightTime time.Duration
currentFrameTime time.Duration
tickTime time.Time
DrawnParts int // Number of draw calls, excluding those invisible or culled based on distance
TotalParts int // Total number of draw calls
BatchedParts int // Total batched number of draw calls
DrawnTris int // Number of drawn triangles, excluding those hidden from backface culling
TotalTris int // Total number of triangles
LightCount int // Total number of lights
ActiveLightCount int // Total active number of lights
}
const (
@ -762,25 +761,16 @@ func (camera *Camera) Clear() {
camera.resultNormalTexture.Clear()
}
if camera.DebugInfo.frameCount > 0 && time.Since(camera.DebugInfo.tickTime).Milliseconds() >= 100 {
if !camera.DebugInfo.tickTime.IsZero() {
camera.DebugInfo.AvgFrameTime = camera.DebugInfo.frameTime / time.Duration(camera.DebugInfo.frameCount)
camera.DebugInfo.AvgAnimationTime = camera.DebugInfo.animationTime / time.Duration(camera.DebugInfo.frameCount)
camera.DebugInfo.AvgLightTime = camera.DebugInfo.lightTime / time.Duration(camera.DebugInfo.frameCount)
}
if time.Since(camera.DebugInfo.tickTime).Milliseconds() >= 1000 {
camera.DebugInfo.FrameTime = camera.DebugInfo.currentFrameTime
camera.DebugInfo.AnimationTime = camera.DebugInfo.currentAnimationTime
camera.DebugInfo.LightTime = camera.DebugInfo.currentLightTime
camera.DebugInfo.tickTime = time.Now()
camera.DebugInfo.frameTime = 0
camera.DebugInfo.animationTime = 0
camera.DebugInfo.lightTime = 0
camera.DebugInfo.frameCount = 0
}
camera.DebugInfo.currentFrameTime = 0
camera.DebugInfo.currentAnimationTime = 0
camera.DebugInfo.currentLightTime = 0
camera.DebugInfo.DrawnParts = 0
camera.DebugInfo.BatchedParts = 0
camera.DebugInfo.TotalParts = 0
@ -1017,6 +1007,10 @@ func (camera *Camera) Render(scene *Scene, lights []ILight, models ...*Model) {
for meshPart, modelSlice := range model.DynamicBatchModels {
if !meshPart.isVisible() {
continue
}
for _, child := range modelSlice {
if !child.visible {
@ -1063,6 +1057,11 @@ func (camera *Camera) Render(scene *Scene, lights []ILight, models ...*Model) {
modelIsTransparent := false
for _, mp := range model.Mesh.MeshParts {
if !mp.isVisible() {
continue
}
if model.isTransparent(mp) {
transparents = append(transparents, renderPair{model, mp})
modelIsTransparent = true
@ -1166,7 +1165,7 @@ func (camera *Camera) Render(scene *Scene, lights []ILight, models ...*Model) {
light.beginModel(model)
}
camera.DebugInfo.lightTime += time.Since(t)
camera.DebugInfo.currentLightTime += time.Since(t)
}
@ -1210,7 +1209,7 @@ func (camera *Camera) Render(scene *Scene, lights []ILight, models ...*Model) {
}
camera.DebugInfo.lightTime += time.Since(t)
camera.DebugInfo.currentLightTime += time.Since(t)
}
@ -1700,9 +1699,7 @@ func (camera *Camera) Render(scene *Scene, lights []ILight, models ...*Model) {
}
camera.DebugInfo.frameTime += time.Since(frametimeStart)
camera.DebugInfo.frameCount++
camera.DebugInfo.currentFrameTime += time.Since(frametimeStart)
}
@ -1716,8 +1713,6 @@ func packFloat(input1, input2, precision float32) float32 {
type DrawSprite3dSettings struct {
// The image to render
Image *ebiten.Image
// Options for drawing the sprite; defaults to nil, which is default settings.
Options *ebiten.DrawTrianglesShaderOptions
// How much to offset the depth - useful if you want the object to appear at a position,
// but in front of or behind other objects. Negative is towards the camera, positive is away.
// The offset ranges from 0 to 1.
@ -1737,12 +1732,12 @@ var spriteRender3DIndices = []uint16{
2, 3, 0,
}
// DrawSprite3D draws an image on the screen in 2D, but at the screen position of the 3D world position provided,
// RenderSprite3D draws an image on the screen in 2D, but at the screen position of the 3D world position provided,
// and with depth intersection.
// This allows you to render 2D elements "at" a 3D position, and can be very useful in situations where you want
// a sprite to render at 100% size and no perspective or skewing, but still look like it's in the 3D space (like in
// a game with a fixed camera viewpoint).
func (camera *Camera) DrawSprite3D(screen *ebiten.Image, renderSettings ...DrawSprite3dSettings) {
func (camera *Camera) RenderSprite3D(screen *ebiten.Image, renderSettings ...DrawSprite3dSettings) {
// TODO: Replace this with a more performant alternative, where we minimize shader / texture switches.
@ -1800,6 +1795,25 @@ func (camera *Camera) DrawSprite3D(screen *ebiten.Image, renderSettings ...DrawS
}
var renderCube = NewModel("render cube", NewCubeMesh())
// RenderCube quickly and easily renders a cube with the position and scale desired.
// The Camera must be present in a scene to perform this function.
func (camera *Camera) RenderCube(position Vector, scale Vector, color Color, shadelessness bool) {
// TODO: Optimize this with perhaps background cubes that are dynamically batched so they could be rendered in one
// Render call.
if scene := camera.Scene(); scene != nil {
renderCube.SetLocalPositionVec(position)
renderCube.SetLocalScaleVec(scale.Scale(0.5))
renderCube.Color = color
renderCube.Mesh.Materials()[0].Shadeless = shadelessness
camera.Render(scene, nil, renderCube)
}
}
// DrawDebugRenderInfo draws render debug information (like number of drawn objects, number of drawn triangles, frame time, etc)
// at the top-left of the provided screen *ebiten.Image, using the textScale and color provided.
// Note that the frame-time mentioned here is purely the time that Tetra3D spends sending render commands to the command queue.
@ -1807,13 +1821,13 @@ func (camera *Camera) DrawSprite3D(screen *ebiten.Image, renderSettings ...DrawS
// visible outside of debugging and profiling, like with pprof.
func (camera *Camera) DrawDebugRenderInfo(screen *ebiten.Image, textScale float64, color Color) {
m := camera.DebugInfo.AvgFrameTime.Round(time.Microsecond).Microseconds()
m := camera.DebugInfo.FrameTime.Round(time.Microsecond).Microseconds()
ft := fmt.Sprintf("%.2fms", float32(m)/1000)
m = camera.DebugInfo.AvgAnimationTime.Round(time.Microsecond).Microseconds()
m = camera.DebugInfo.AnimationTime.Round(time.Microsecond).Microseconds()
at := fmt.Sprintf("%.2fms", float32(m)/1000)
m = camera.DebugInfo.AvgLightTime.Round(time.Microsecond).Microseconds()
m = camera.DebugInfo.LightTime.Round(time.Microsecond).Microseconds()
lt := fmt.Sprintf("%.2fms", float32(m)/1000)
sectorName := "<Sector Rendering Off>"

10
dae.go
View File

@ -135,8 +135,9 @@ func DefaultDaeLoadOptions() *DaeLoadOptions {
// LoadDAEFile takes a filepath to a .dae model file, and returns a *Library populated with the .dae file's objects and meshes.
// Animations will not be loaded from DAE files, as DAE exports through Blender only support one animation per object (so it's generally
// advised to use the GLTF or GLB format instead). Cameras exported in the DAE file will be turned into simple Nodes in Tetra3D, as
// there's not enough information to instantiate a tetra3d.Camera. If the call couldn't complete for any reason, like due to a malformed DAE file,
// advised to use the GLTF or GLB format instead).
// Cameras exported in the DAE file will be turned into simple Nodes in Tetra3D, as there's not enough information to instantiate a tetra3d.Camera.
// If the call couldn't complete for any reason, like due to a malformed DAE file,
// it will return an error.
func LoadDAEFile(path string, options *DaeLoadOptions) (*Library, error) {
@ -150,8 +151,9 @@ func LoadDAEFile(path string, options *DaeLoadOptions) (*Library, error) {
// LoadDAEData takes a []byte consisting of the contents of a DAE file, and returns a *Library populated with the .dae file's objects and meshes.
// Animations will not be loaded from DAE files, as DAE exports through Blender only support one animation per object (so it's generally
// advised to use the GLTF or GLB format instead). Cameras exported in the DAE file will be turned into simple Nodes in Tetra3D, as
// there's not enough information to instantiate a tetra3d.Camera. If the call couldn't complete for any reason, like due to a malformed DAE file,
// advised to use the GLTF or GLB format instead).
// Cameras exported in the DAE file will be turned into simple Nodes in Tetra3D, as there's not enough information to instantiate a tetra3d.Camera.
// If the call couldn't complete for any reason, like due to a malformed DAE file,
// it will return an error.
func LoadDAEData(data []byte, options *DaeLoadOptions) (*Library, error) {

View File

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

View File

@ -0,0 +1,416 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v4.0.43",
"version":"2.0"
},
"extensionsUsed":[
"KHR_lights_punctual"
],
"extensionsRequired":[
"KHR_lights_punctual"
],
"extensions":{
"KHR_lights_punctual":{
"lights":[
{
"color":[
1,
1,
1
],
"intensity":7.957747154594767,
"type":"point",
"name":"Light"
}
]
}
},
"scene":0,
"scenes":[
{
"extras":{
"t3dExportOnSave__":1,
"t3dExportFormat__":0,
"t3dCameraResolution__":[
640,
360
],
"t3dExportFilepath__":"",
"t3dPackTextures__":false,
"t3dExportCameras__":true,
"t3dExportLights__":true,
"t3dRenderResolutionW__":640,
"t3dRenderResolutionH__":360,
"t3dSectorRendering__":false,
"t3dSectorRenderDepth__":0,
"t3dPlaybackFPS__":60,
"t3dAnimationSampling__":true,
"t3dAnimationInterpolation__":true,
"t3dPerspectiveCorrectedTextureMapping__":false,
"t3dMaxLightCount__":0,
"t3dSectorDetection__":0,
"t3dCollections__":{
"Collection":{
"objects":[
"Room",
"Light",
"Camera",
"Heart"
],
"offset":[
0.0,
0.0,
0.0
]
}
},
"t3dWorlds__":{
"World":{
"ambient color":[
0.05087608844041824,
0.05087608844041824,
0.05087608844041824,
1.0
],
"ambient energy":1.0,
"clear color":[
0.007000000216066837,
0.00800000037997961,
0.009999999776482582,
1.0
],
"fog mode":"OFF",
"dithered transparency":0.0,
"fog curve":"LINEAR",
"fog color":[
0.0,
0.0,
0.0,
1.0
],
"fog range start":0.0,
"fog range end":1.0
}
},
"t3dCurrentWorld__":"World",
"t3dView3DCameraData__":[
{
"clip_start":0.009999999776482582,
"clip_end":1000.0,
"location":[
0.0,
14.718420028686523,
12.718420028686523
],
"rotation":[
[
1.0,
0.0,
0.0
],
[
0.0,
0.7071068286895752,
0.7071067690849304
],
[
0.0,
-0.7071067690849304,
0.7071068286895752
]
],
"fovY":39.597752709049864,
"perspective":true
}
]
},
"name":"Scene",
"nodes":[
0,
1,
2,
3
]
}
],
"nodes":[
{
"extras":{
"t3dOriginalLocalPosition__":[
0.0,
0.0,
2.0
]
},
"mesh":0,
"name":"Room",
"translation":[
0,
2,
0
]
},
{
"extensions":{
"KHR_lights_punctual":{
"light":0
}
},
"extras":{
"t3dOriginalLocalPosition__":[
0.0,
0.0,
2.0
]
},
"name":"Light",
"rotation":[
-0.28416627645492554,
0.7269423007965088,
0.34203392267227173,
0.5232754945755005
],
"translation":[
0,
2,
0
]
},
{
"camera":0,
"extras":{
"t3dOriginalLocalPosition__":[
0.0,
-12.718420028686523,
14.718420028686523
]
},
"name":"Camera",
"rotation":[
-0.3826834261417389,
0,
0,
0.9238795638084412
],
"translation":[
0,
14.718420028686523,
12.718420028686523
]
},
{
"extras":{
"t3dVisible__":0,
"t3dBoundsType__":1,
"t3dOriginalLocalPosition__":[
0.5,
-0.5,
0.5
]
},
"mesh":1,
"name":"Heart",
"translation":[
0.5,
0.5,
0.5
]
}
],
"cameras":[
{
"name":"Camera",
"perspective":{
"aspectRatio":1.7777777777777777,
"yfov":0.39959652046304894,
"zfar":100,
"znear":0.10000000149011612
},
"type":"perspective"
}
],
"materials":[
{
"name":"Room",
"pbrMetallicRoughness":{
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"extras":{
"t3dVertexColorNames__":[
"Col"
],
"t3dActiveVertexColorIndex__":0
},
"name":"Cube",
"primitives":[
{
"attributes":{
"COLOR_0":0,
"POSITION":1,
"NORMAL":2,
"TEXCOORD_0":3
},
"indices":4,
"material":0
}
]
},
{
"name":"Cube.001",
"primitives":[
{
"attributes":{
"POSITION":5,
"NORMAL":6,
"TEXCOORD_0":7
},
"indices":8
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5123,
"count":196,
"normalized":true,
"type":"VEC4"
},
{
"bufferView":1,
"componentType":5126,
"count":196,
"max":[
2,
2,
2
],
"min":[
-2,
-2,
-2
],
"type":"VEC3"
},
{
"bufferView":2,
"componentType":5126,
"count":196,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5126,
"count":196,
"type":"VEC2"
},
{
"bufferView":4,
"componentType":5123,
"count":636,
"type":"SCALAR"
},
{
"bufferView":5,
"componentType":5126,
"count":24,
"max":[
0.25,
0.25,
0.25
],
"min":[
-0.25,
-0.25,
-0.25
],
"type":"VEC3"
},
{
"bufferView":6,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":7,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":8,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":1568,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":2352,
"byteOffset":1568,
"target":34962
},
{
"buffer":0,
"byteLength":2352,
"byteOffset":3920,
"target":34962
},
{
"buffer":0,
"byteLength":1568,
"byteOffset":6272,
"target":34962
},
{
"buffer":0,
"byteLength":1272,
"byteOffset":7840,
"target":34963
},
{
"buffer":0,
"byteLength":288,
"byteOffset":9112,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":9400,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":9688,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":9880,
"target":34963
}
],
"buffers":[
{
"byteLength":9952,
"uri":"scene.bin"
}
]
}

View File

@ -2,6 +2,7 @@ package main
import (
"bytes"
"embed"
_ "embed"
@ -14,11 +15,8 @@ import (
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
//go:embed heart.png
var heartImg []byte
//go:embed scene.gltf
var scene []byte
//go:embed assets
var assets embed.FS
type Game struct {
Scene *tetra3d.Scene
@ -41,7 +39,7 @@ func NewGame() *Game {
func (g *Game) Init() {
scene, err := tetra3d.LoadGLTFData(scene, nil)
scene, err := tetra3d.LoadGLTFFile(assets, "assets/scene.gltf", nil)
if err != nil {
panic(err)
}
@ -49,6 +47,11 @@ func (g *Game) Init() {
g.Camera = g.Scene.Root.Get("Camera").(*tetra3d.Camera)
heartImg, err := assets.ReadFile("assets/heart.png")
if err != nil {
panic(err)
}
reader := bytes.NewReader(heartImg)
newImg, _, err := ebitenutil.NewImageFromReader(reader)
if err != nil {
@ -104,7 +107,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
g.Camera.RenderScene(g.Scene)
// Draw the sprite after the rest of the scene.
g.Camera.DrawSprite3D(
g.Camera.RenderSprite3D(
g.Camera.ColorTexture(),
tetra3d.DrawSprite3dSettings{
Image: g.HeartSprite,

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 288 B

View File

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 295 B

View File

@ -1,6 +1,7 @@
package main
import (
"embed"
_ "embed"
"github.com/solarlune/tetra3d"
@ -23,8 +24,8 @@ type Game struct {
Character *tetra3d.Model
}
//go:embed animatedTextures.gltf
var libraryData []byte
//go:embed assets
var assets embed.FS
func NewGame() *Game {
game := &Game{}
@ -36,7 +37,7 @@ func NewGame() *Game {
func (g *Game) Init() {
library, err := tetra3d.LoadGLTFData(libraryData, nil)
library, err := tetra3d.LoadGLTFFile(assets, "assets/animatedTextures.glb", nil)
if err != nil {
panic(err)
}

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 195 B

After

Width:  |  Height:  |  Size: 195 B

View File

@ -1,6 +1,7 @@
package main
import (
"embed"
_ "embed"
"github.com/solarlune/tetra3d"
@ -19,8 +20,8 @@ type Game struct {
System examples.BasicSystemHandler
}
//go:embed animations.gltf
var gltf []byte
//go:embed assets
var assets embed.FS
func NewGame() *Game {
game := &Game{}
@ -30,7 +31,7 @@ func NewGame() *Game {
func (g *Game) Init() {
library, err := tetra3d.LoadGLTFData(gltf, nil)
library, err := tetra3d.LoadGLTFFile(assets, "assets/animations.glb", nil)
if err != nil {
panic(err)
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ type Game struct {
System examples.BasicSystemHandler
}
//go:embed autobatch.gltf
//go:embed autobatch.glb
var libraryData []byte
func NewGame() *Game {

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 459 B

After

Width:  |  Height:  |  Size: 459 B

View File

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 670 B

View File

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,6 +1,7 @@
package main
import (
"embed"
_ "embed"
"github.com/solarlune/tetra3d"
@ -11,8 +12,8 @@ import (
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
//go:embed baking.gltf
var gltfData []byte
//go:embed assets
var assetData embed.FS
type Game struct {
Library *tetra3d.Library
@ -39,7 +40,7 @@ const (
func (g *Game) Init() {
library, err := tetra3d.LoadGLTFData(gltfData, nil)
library, err := tetra3d.LoadGLTFFile(assetData, "assets/baking.glb", nil)
if err != nil {
panic(err)
}

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -22,7 +22,7 @@ type Game struct {
BG *ebiten.Image
}
//go:embed blendmodes.gltf
//go:embed blendmodes.glb
var gltfData []byte
//go:embed bg.png

Binary file not shown.

BIN
examples/bounds/bounds.glb Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -13,7 +13,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
//go:embed bounds.gltf
//go:embed bounds.glb
var gltfData []byte
type Game struct {

Binary file not shown.

Binary file not shown.

View File

@ -1,542 +0,0 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.5.21",
"version":"2.0"
},
"extensionsUsed":[
"KHR_lights_punctual"
],
"extensionsRequired":[
"KHR_lights_punctual"
],
"extensions":{
"KHR_lights_punctual":{
"lights":[
{
"color":[
1,
1,
1
],
"intensity":79.57747154594767,
"type":"point",
"name":"Light"
}
]
}
},
"scene":0,
"scenes":[
{
"extras":{
"t3dExportFormat__":2,
"t3dExportOnSave__":1,
"t3dRenderResolutionH__":360,
"t3dCollections__":{
"Collection":{
"objects":[
"Cube",
"Light",
"Shallow",
"Cube.001",
"Cube.002",
"Orthographic",
"Wide Angle",
"Cube.003",
"Cube.004",
"Cube.005",
"Super Wide Angle",
"Cube.006",
"Cube.007"
],
"offset":[
0.0,
0.0,
0.0
]
}
},
"t3dWorlds__":{
"World":{
"ambient color":[
0.05087608844041824,
0.05087608844041824,
0.05087608844041824,
1.0
],
"ambient energy":1.0
}
},
"t3dCurrentWorld__":"World"
},
"name":"Scene",
"nodes":[
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12
]
}
],
"nodes":[
{
"extras":{
"t3dOriginalLocalPosition__":[
4.25,
0.0,
0.0
]
},
"mesh":0,
"name":"Cube",
"translation":[
4.25,
0,
0
]
},
{
"extensions":{
"KHR_lights_punctual":{
"light":0
}
},
"extras":{
"t3dOriginalLocalPosition__":[
-0.03555774688720703,
-3.2212681770324707,
1.8544291257858276
]
},
"name":"Light",
"rotation":[
-0.28416627645492554,
0.7269423007965088,
0.34203392267227173,
0.5232754945755005
],
"translation":[
-0.03555774688720703,
1.8544291257858276,
3.2212681770324707
]
},
{
"camera":0,
"extras":{
"t3dFovY__":0.6911112070083618,
"t3dOriginalLocalPosition__":[
0.0,
-15.501559257507324,
-2.416654069747892e-06
]
},
"name":"Shallow",
"translation":[
0,
-2.416654069747892e-06,
15.501559257507324
]
},
{
"extras":{
"t3dOriginalLocalPosition__":[
-4.25,
0.0,
0.0
]
},
"mesh":0,
"name":"Cube.001",
"translation":[
-4.25,
0,
0
]
},
{
"extras":{
"t3dOriginalLocalPosition__":[
0.0,
0.0,
0.0
]
},
"mesh":0,
"name":"Cube.002"
},
{
"camera":1,
"extras":{
"t3dFovY__":0.6911112070083618,
"t3dOriginalLocalPosition__":[
0.0,
-15.986564636230469,
-2.416654069747892e-06
]
},
"name":"Orthographic",
"translation":[
0,
-2.416654069747892e-06,
15.986564636230469
]
},
{
"camera":2,
"extras":{
"t3dFovY__":1.8901524543762207,
"t3dOriginalLocalPosition__":[
0.0,
-5.786786079406738,
-2.416654069747892e-06
]
},
"name":"Wide Angle",
"translation":[
0,
-2.416654069747892e-06,
5.786786079406738
]
},
{
"extras":{
"t3dOriginalLocalPosition__":[
3.0,
-3.0,
0.0
]
},
"mesh":0,
"name":"Cube.003",
"scale":[
0.5,
0.5,
0.5
],
"translation":[
3,
0,
3
]
},
{
"extras":{
"t3dOriginalLocalPosition__":[
-3.0,
-3.0,
0.0
]
},
"mesh":0,
"name":"Cube.004",
"scale":[
0.5,
0.5,
0.5
],
"translation":[
-3,
0,
3
]
},
{
"extras":{
"t3dOriginalLocalPosition__":[
0.0,
0.0,
3.0
]
},
"mesh":1,
"name":"Cube.005",
"translation":[
0,
3,
0
]
},
{
"camera":3,
"extras":{
"t3dFovY__":1.8901524543762207,
"t3dOriginalLocalPosition__":[
0.0,
-4.786786079406738,
-2.416654069747892e-06
]
},
"name":"Super Wide Angle",
"translation":[
0,
-2.416654069747892e-06,
4.786786079406738
]
},
{
"extras":{
"t3dOriginalLocalPosition__":[
-2.0,
-5.0,
0.0
]
},
"mesh":0,
"name":"Cube.006",
"scale":[
0.5,
0.5,
0.5
],
"translation":[
-2,
0,
5
]
},
{
"extras":{
"t3dOriginalLocalPosition__":[
1.9999998807907104,
-5.0,
0.0
]
},
"mesh":0,
"name":"Cube.007",
"scale":[
0.5,
0.5,
0.5
],
"translation":[
1.9999998807907104,
0,
5
]
}
],
"cameras":[
{
"extras":{
"t3dFOV__":23
},
"name":"Shallow",
"perspective":{
"aspectRatio":1.7777777777777777,
"yfov":0.40142571174612823,
"zfar":100,
"znear":0.10000000149011612
},
"type":"perspective"
},
{
"name":"Orthographic",
"orthographic":{
"xmag":5.25,
"ymag":2.953125,
"zfar":21.70000457763672,
"znear":0.10000000149011612
},
"type":"orthographic"
},
{
"extras":{
"t3dFOV__":90
},
"name":"Wide",
"perspective":{
"aspectRatio":1.7777777777777777,
"yfov":1.5707962410233096,
"zfar":100,
"znear":0.10000000149011612
},
"type":"perspective"
},
{
"extras":{
"t3dFOV__":90
},
"name":"Super Wide Angle",
"perspective":{
"aspectRatio":1.7777777777777777,
"yfov":2.6354470672831827,
"zfar":100,
"znear":0.10000000149011612
},
"type":"perspective"
}
],
"materials":[
{
"extras":{
"t3dMaterialShadeless__":0
},
"name":"Material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
},
{
"name":"Cube.001",
"primitives":[
{
"attributes":{
"POSITION":4,
"TEXCOORD_0":5,
"NORMAL":6
},
"indices":3,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
},
{
"bufferView":4,
"componentType":5126,
"count":24,
"max":[
0.5,
0.5,
0.5
],
"min":[
-0.5,
-0.5,
-0.5
],
"type":"VEC3"
},
{
"bufferView":5,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":6,
"componentType":5126,
"count":24,
"type":"VEC3"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
},
{
"buffer":0,
"byteLength":288,
"byteOffset":840,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":1128,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":1320,
"target":34962
}
],
"buffers":[
{
"byteLength":1608,
"uri":"data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAQAOABQAAQAUAAcACgAGABIACgASABYAFwATAAwAFwAMABAADwADAAkADwAJABUABQACAAgABQAIAAsAEQANAAAAEQAAAAQAAAAAPwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACA"
}
]
}

View File

@ -13,7 +13,7 @@ import (
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
//go:embed cameraTest.gltf
//go:embed cameraTest.glb
var shapes []byte
type Game struct {

72
gltf.go
View File

@ -4,9 +4,11 @@ import (
"bytes"
"encoding/json"
"image"
"io"
"io/fs"
"log"
"math"
"os"
"path"
"strconv"
"strings"
@ -36,6 +38,9 @@ type GLTFLoadOptions struct {
// If top-level objects in collections should be renamed according to their instance objects.
RenameCollectionObjects bool
rootFilename string
externalBufferFileSystem fs.FS // The file system to use for loading external buffers; automatically set if you use LoadGLTFFile().
}
// DefaultGLTFLoadOptions creates an instance of GLTFLoadOptions with some sensible defaults.
@ -48,32 +53,48 @@ func DefaultGLTFLoadOptions() *GLTFLoadOptions {
}
}
// LoadGLTFFile loads a .gltf or .glb file from the filepath given, using a provided GLTFLoadOptions struct to alter how the file is loaded.
// Passing nil for loadOptions will load the file using default load options. Unlike with DAE files, Animations (including armature-based
// animations) and Cameras (assuming they are exported in the GLTF file) will be parsed properly.
// LoadGLTFFile loads a .gltf or .glb file from the file system given using the filename provided.
// The provided GLTFLoadOptions struct alters how the file is loaded.
// Passing nil for gltfLoadOptions will load the file using default load options.
// LoadGLTFFile properly handles .gltf + .bin file pairs.
// LoadGLTFFile will return a Library, and an error if the process fails.
func LoadGLTFFile(path string, loadOptions *GLTFLoadOptions) (*Library, error) {
func LoadGLTFFile(fileSystem fs.FS, filename string, gltfLoadOptions *GLTFLoadOptions) (*Library, error) {
fileData, err := os.ReadFile(path)
file, err := fileSystem.Open(filename)
if err != nil {
return nil, err
}
return LoadGLTFData(fileData, loadOptions)
byteString, err := io.ReadAll(file)
if err != nil {
return nil, err
}
if gltfLoadOptions == nil {
gltfLoadOptions = DefaultGLTFLoadOptions()
}
gltfLoadOptions.externalBufferFileSystem = fileSystem
gltfLoadOptions.rootFilename = filename
return LoadGLTFData(byteString, gltfLoadOptions)
}
// LoadGLTFData loads a .gltf or .glb file from the byte data given, using a provided GLTFLoadOptions struct to alter how the file is loaded.
// Passing nil for loadOptions will load the file using default load options. Unlike with DAE files, Animations (including armature-based
// animations) and Cameras (assuming they are exported in the GLTF file) will be parsed properly.
// LoadGLTFFile will not work by default with external byte information buffers (i.e. .gltf and .glb file pairs)
// as the buffer is referenced in .gltf as just a filename. To handle this properly, load the .gltf file using LoadGLTFFile().
// LoadGLTFFile will return a Library, and an error if the process fails.
func LoadGLTFData(data []byte, gltfLoadOptions *GLTFLoadOptions) (*Library, error) {
decoder := gltf.NewDecoder(bytes.NewReader(data))
doc := gltf.NewDocument()
decoder := gltf.NewDecoder(bytes.NewReader(data))
err := decoder.Decode(doc)
if err != nil {
@ -82,6 +103,31 @@ func LoadGLTFData(data []byte, gltfLoadOptions *GLTFLoadOptions) (*Library, erro
if gltfLoadOptions == nil {
gltfLoadOptions = DefaultGLTFLoadOptions()
} else if gltfLoadOptions.externalBufferFileSystem != nil {
for _, buffer := range doc.Buffers {
// We need to load this buffer
if buffer.URI != "" && len(buffer.Data) == 0 {
// We get the base directory for the loaded filename
dir, _ := path.Split(gltfLoadOptions.rootFilename)
openedFile, err := gltfLoadOptions.externalBufferFileSystem.Open(path.Join(dir, buffer.URI))
if err != nil {
panic(err)
}
bytesData, err := io.ReadAll(openedFile)
if err != nil {
panic(err)
}
buffer.Data = bytesData
continue
// decoder = gltf.NewDecoderFS(bytes.NewReader(data), gltfLoadOptions.externalBufferFileSystem)
}
}
}
library := NewLibrary()
@ -139,9 +185,15 @@ func LoadGLTFData(data []byte, gltfLoadOptions *GLTFLoadOptions) (*Library, erro
}
exportFormat := 0 // 0 = GLB, 1 = GLTF Separate
if format, exists := globalExporterSettings["t3dExportFormat__"]; exists {
exportFormat = int(format.(float64))
}
if et, exists := globalExporterSettings["t3dPackTextures__"]; exists {
t3dExport = true
exportedTextures = et.(bool)
exportedTextures = et.(bool) && exportFormat == 0
}
if col, exists := globalExporterSettings["t3dCollections__"]; exists {

View File

@ -54,6 +54,7 @@ type Material struct {
Fogless bool // If the material should be fogless or not
Blend ebiten.Blend // Blend mode to use when rendering the material (i.e. additive, multiplicative, etc)
BillboardMode int // Billboard mode
Visible bool // Whether the material is visible or not
// fragmentShader represents a shader used to render the material with. This shader is activated after rendering
// to the depth texture, but before compositing the finished render to the screen after fog.
@ -101,6 +102,7 @@ func NewMaterial(name string) *Material {
FragmentShaderOptions: &ebiten.DrawTrianglesShaderOptions{},
FragmentShaderOn: true,
Blend: ebiten.BlendSourceOver,
Visible: true,
}
}
@ -132,6 +134,7 @@ func (material *Material) Clone() *Material {
for k, v := range newMat.FragmentShaderOptions.Uniforms {
newMat.FragmentShaderOptions.Uniforms[k] = v
}
newMat.Visible = material.Visible
return newMat
}

View File

@ -228,6 +228,7 @@ func (matrix Matrix4) Transposed() Matrix4 {
// }
// Inverted returns an inverted version of the Matrix4.
// The ultimate sin; I'm just going to copy this code for inverting a 4x4 Matrix and call it a day.
// This code was obtained from https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix,
// and is like 200x faster than my old inversion code, whaaaaa

View File

@ -736,7 +736,7 @@ func (vs *VertexSelection) ForEachIndex(forEach func(index int)) {
}
}
// NewCubeMesh creates a new Cube Mesh and gives it a new material (suitably named "Cube").
// NewCubeMesh creates a new 2x2x2 Cube Mesh and gives it a new material (suitably named "Cube").
func NewCubeMesh() *Mesh {
mesh := NewMesh("Cube",
@ -1513,6 +1513,13 @@ func (part *MeshPart) primaryDimensions() (float64, float64) {
}
func (p *MeshPart) isVisible() bool {
if p.Material != nil {
return p.Material.Visible
}
return true
}
type VertexInfo struct {
ID int
X, Y, Z float64

View File

@ -584,7 +584,7 @@ func (model *Model) ProcessVertices(vpMatrix Matrix4, camera *Camera, meshPart *
mesh.vertexTransforms[tri.VertexIndices[i]] = vpMatrix.MultVecW(vertPos)
camera.DebugInfo.animationTime += time.Since(t)
camera.DebugInfo.currentAnimationTime += time.Since(t)
skinnedTriCenter = skinnedTriCenter.Add(vertPos)

View File

@ -303,6 +303,7 @@ The following is a rough to-do list (tasks with checks have been implemented):
- [x] -- Sectors - The general idea is that the camera can be set up to only render sectors that it's in / neighboring (up to a customizeable depth)
- [ ] -- -- Some method to have objects appear in multiple Sectors, but not others?
- [ ] -- Occlusion culling - this should be possible using octrees to determine if an object is visible before rendering it; see: https://www.gamedeveloper.com/programming/occlusion-culling-algorithms
- [ ] -- -- Something to reduce overdraw
- [X] **Debug**
- [X] -- Debug text: overall render time, FPS, render call count, vertex count, triangle count, skipped triangle count
- [X] -- Wireframe debug rendering

View File

@ -20,22 +20,21 @@ bl_info = {
}
objectTypes = [
("MESH", "Mesh", "A standard, visible mesh object", 0, 0),
("GRID", "Grid", "A grid object; not visualized or 'physically present'. The vertices in Blender become grid points in Tetra3D; the edges become their connections", 0, 1),
("MESH", "Mesh", "A standard, visible mesh object.", 0, 0),
("GRID", "Grid", "A grid object; not visualized or 'physically present'. The vertices in Blender become grid points in Tetra3D; the edges become their connections.", 0, 1),
]
boundsTypes = [
("NONE", "No Bounds", "No collision will be created for this object.", 0, 0),
("AABB", "AABB", "An AABB (axis-aligned bounding box). If the size isn't customized, it will be big enough to fully contain the mesh of the current object. Currently buggy when resolving intersections between AABB or other Triangle Nodes", 0, 1),
("CAPSULE", "Capsule", "A capsule, which can rotate. If the radius and height are not set, it will have a radius and height to fully contain the current object", 0, 2),
("SPHERE", "Sphere", "A sphere. If the radius is not custom set, it will have a large enough radius to fully contain the provided object", 0, 3),
("TRIANGLES", "Triangle Mesh", "A triangle mesh bounds type. Only works on mesh-type objects (i.e. an Empty won't generate a BoundingTriangles). Accurate, but slow. Currently buggy when resolving intersections between AABB or other Triangle Nodes", 0, 4),
("AABB", "AABB", "An AABB (axis-aligned bounding box). If the size isn't customized, it will be big enough to fully contain the mesh of the current object. Currently buggy when resolving intersections between AABB or other Triangle Nodes.", 0, 1),
("CAPSULE", "Capsule", "A capsule, which can rotate. If the radius and height are not set, it will have a radius and height to fully contain the current object.", 0, 2),
("SPHERE", "Sphere", "A sphere. If the radius is not custom set, it will have a large enough radius to fully contain the provided object.", 0, 3),
("TRIANGLES", "Triangle Mesh", "A triangle mesh bounds type. Only works on mesh-type objects (i.e. an Empty won't generate a BoundingTriangles). Accurate, but slow. Currently buggy when resolving intersections between AABB or other Triangle Nodes.", 0, 4),
]
gltfExportTypes = [
("GLB", ".glb", "Exports a single file, with all data packed in binary form. Most efficient and portable, but more difficult to edit later", 0, 0),
("GLTF_SEPARATE", ".gltf + .bin + textures", "Exports multiple files, with separate JSON, binary and texture data. Easiest to edit later - Note that Tetra3D doesn't support this properly currently", 0, 1),
("GLTF_EMBEDDED", ".gltf", "Exports a single file, with all data packed in JSON. Less efficient than binary, but easier to edit later", 0, 2),
("GLB", ".glb", "Exports a single file, with all data packed in binary form. Textures can be packed into the file. Most efficient and portable, but more difficult to edit later.", 0, 0),
("GLTF_SEPARATE", ".gltf + .bin + external textures", "Exports multiple files, with separate JSON, binary and texture data. Easiest to edit later.", 0, 1),
]
sectorDetectionType = [
@ -44,10 +43,10 @@ sectorDetectionType = [
]
materialBlendModes = [
("DEFAULT", "Default", "Blends the destination by the material's color modulated by the material's alpha value. The default alpha-blending composite mode. Also known as BlendSourceOver", 0, 0),
("ADDITIVE", "Additive", "Adds the material's color to the destination. Also known as BlendLighter", 0, 1),
("MULTIPLY", "Multiply", "Multiplies the material's color by the destination. Known as Multiply compositing using a custom Blend object", 0, 2),
("CLEAR", "Clear", "Anywhere the material draws is cleared instead; useful to 'punch through' a scene to show the blank alpha zero. Also known as BlendClear", 0, 3),
("DEFAULT", "Default", "Blends the destination by the material's color modulated by the material's alpha value. The default alpha-blending composite mode. Also known as BlendSourceOver.", 0, 0),
("ADDITIVE", "Additive", "Adds the material's color to the destination. Also known as BlendLighter.", 0, 1),
("MULTIPLY", "Multiply", "Multiplies the material's color by the destination. Known as Multiply compositing using a custom Blend object.", 0, 2),
("CLEAR", "Clear", "Anywhere the material draws is cleared instead; useful to 'punch through' a scene to show the blank alpha zero. Also known as BlendClear.", 0, 3),
]
materialBillboardModes = [
@ -59,22 +58,22 @@ materialBillboardModes = [
materialLightingModes = [
("DEFAULT", "Default", "Default lighting; light is dependent on normal of lit faces.", 0, 0),
("NORMAL", "Point Towards Lights", "Lighting acts as though faces always face light sources. Particularly useful for billboarded 2D sprites", 0, 1),
("NORMAL", "Point Towards Lights", "Lighting acts as though faces always face light sources. Particularly useful for billboarded 2D sprites.", 0, 1),
("DOUBLE", "Double-Sided", "Double-sided lighting; lighting is dependent on normal of lit faces, but on both sides of a face.", 0, 2),
]
worldFogCompositeModes = [
("OFF", "Off", "No fog. Object colors aren't changed with distance from the camera", 0, 0),
("ADDITIVE", "Additive", "Additive fog - this fog mode brightens objects in the distance, with full effect being adding the color given to the object's color at maximum distance (according to the camera's far range)", 0, 1),
("SUBTRACT", "Subtractive", "Subtractive fog - this fog mode darkens objects in the distance, with full effect being subtracting the object's color by the fog color at maximum distance (according to the camera's far range)", 0, 2),
("OVERWRITE", "Overwrite", "Overwrite fog - this fog mode overwrites the object's color with the fog color, with maximum distance being the camera's far distance", 0, 3),
("OFF", "Off", "No fog. Object colors aren't changed with distance from the camera.", 0, 0),
("ADDITIVE", "Additive", "Additive fog - this fog mode brightens objects in the distance, with full effect being adding the color given to the object's color at maximum distance (according to the camera's far range).", 0, 1),
("SUBTRACT", "Subtractive", "Subtractive fog - this fog mode darkens objects in the distance, with full effect being subtracting the object's color by the fog color at maximum distance (according to the camera's far range).", 0, 2),
("OVERWRITE", "Overwrite", "Overwrite fog - this fog mode overwrites the object's color with the fog color, with maximum distance being the camera's far distance.", 0, 3),
("TRANSPARENT", "Transparent", "Transparent fog - this fog mode fades the object out over distance, such that at maximum distance / fog range, the object is wholly transparent.", 0, 4),
]
worldFogCurveTypes = [
("LINEAR", "Smooth", "Smooth fog (Ease: Linear); this goes from 0% in the near range to 100% in the far range evenly", "LINCURVE", 0),
("OUTCIRC", "Dense", "Dense fog (Ease: Out Circ); fog will increase aggressively in the near range, ramping up to 100% at the far range", "SPHERECURVE", 1),
("INCIRC", "Light", "Light fog (Ease: In Circ); fog will increase aggressively towards the far range, ramping up to 100% at the far range", "SHARPCURVE", 2),
("LINEAR", "Smooth", "Smooth fog (Ease: Linear); this goes from 0% in the near range to 100% in the far range evenly.", "LINCURVE", 0),
("OUTCIRC", "Dense", "Dense fog (Ease: Out Circ); fog will increase aggressively in the near range, ramping up to 100% at the far range.", "SPHERECURVE", 1),
("INCIRC", "Light", "Light fog (Ease: In Circ); fog will increase aggressively towards the far range, ramping up to 100% at the far range.", "SHARPCURVE", 2),
]
gamePropTypes = [
@ -89,9 +88,9 @@ gamePropTypes = [
]
batchModes = [
("OFF", "Off", "No automatic batching", 0, 0),
("DYNAMIC", "Dynamic Batching", "Dynamic batching based off of one material (the first one)", 0, 1),
("STATIC", "Static Merging", "Static merging; merged objects cannot move or deviate in any way. After automatic static merging, the merged models will be automatically set to invisible", 0, 2),
("OFF", "Off", "No automatic batching.", 0, 0),
("DYNAMIC", "Dynamic Batching", "Dynamic batching based off of one material (the first one).", 0, 1),
("STATIC", "Static Merging", "Static merging; merged objects cannot move or deviate in any way. After automatic static merging, the merged models will be automatically set to invisible.", 0, 2),
]
def filepathSet(self, value):
@ -798,6 +797,7 @@ class MATERIAL_PT_tetra3d(bpy.types.Panel):
row.prop(context.material, "t3dMaterialFogless__")
row = self.layout.row()
row.prop(context.material, "use_backface_culling")
row.prop(context.material, "t3dVisible__")
row = self.layout.row()
row.label(text="Transparency Mode:")
row.prop(context.material, "blend_method", text="")
@ -931,7 +931,10 @@ class RENDER_PT_tetra3d(bpy.types.Panel):
row.prop(context.scene, "t3dExportFormat__")
box = self.layout.box()
box.prop(context.scene, "t3dPackTextures__")
row = box.row()
row.active = context.scene.t3dExportFormat__ == "GLB"
row.prop(context.scene, "t3dPackTextures__")
box.prop(context.scene, "t3dExportCameras__")
box.prop(context.scene, "t3dExportLights__")
box.prop(context.scene, "t3dPerspectiveCorrectedTextureMapping__")
@ -1017,7 +1020,7 @@ def export():
if scene.t3dExportFormat__ == "GLB":
ending = ".glb"
elif scene.t3dExportFormat__ == "GLTF_SEPARATE" or scene.t3dExportFormat__ == "GLTF_EMBEDDED":
elif scene.t3dExportFormat__ == "GLTF_SEPARATE":
ending = ".gltf"
newPath = os.path.splitext(blendPath)[0] + ending
@ -1921,7 +1924,7 @@ def register():
bpy.types.Scene.t3dExportFilepath__ = bpy.props.StringProperty(name="Export Filepath", description="Filepath to export GLTF file. If left blank, it will export to the same directory as the blend file and will have the same filename; in this case, if the blend file has not been saved, nothing will happen",
default="", subtype="FILE_PATH", get=getExportFilepath, set=setExportFilepath)
bpy.types.Scene.t3dExportFormat__ = bpy.props.EnumProperty(items=gltfExportTypes, name="Export Format", description="What format to export the file in", default="GLTF_EMBEDDED",
bpy.types.Scene.t3dExportFormat__ = bpy.props.EnumProperty(items=gltfExportTypes, name="Export Format", description="What format to export the file in", default="GLB",
get=getExportFormat, set=setExportFormat)
bpy.types.Scene.t3dExportCameras__ = bpy.props.BoolProperty(name="Export Cameras", description="Whether Blender should export cameras to the GLTF file", default=True,
@ -1978,6 +1981,7 @@ def register():
bpy.types.Material.t3dCustomDepthOn__ = bpy.props.BoolProperty(name="Custom Depth", description="Whether custom depth offsetting should be enabled", default=False)
bpy.types.Material.t3dCustomDepthValue__ = bpy.props.FloatProperty(name="Depth Offset Value", description="How far in world units the material should offset when rendering (negative values are closer to the camera, positive values are further)")
bpy.types.Material.t3dMaterialLightingMode__ = bpy.props.EnumProperty(items=materialLightingModes, name="Lighting mode", description="How materials should be lit", default="DEFAULT")
bpy.types.Material.t3dVisible__ = bpy.props.BoolProperty(name="Visible", description="Whether this material is visible", default=True)
bpy.types.Material.t3dAutoUV__ = bpy.props.BoolProperty(name="Auto UV-Map", description="If the UV map of the faces that use this material should automatically be Cube Projection UV mapped when exiting edit mode")
bpy.types.Material.t3dAutoUVUnitSize__ = bpy.props.FloatProperty(name="Unit Size", description="How many Blender Units equates to one texture size", default=4.0, update=autoUVChange, step=5)
@ -2084,6 +2088,7 @@ def unregister():
del bpy.types.Material.t3dAutoUV__
del bpy.types.Material.t3dAutoUVUnitSize__
del bpy.types.Material.t3dAutoUVRotation__
del bpy.types.Material.t3dVisible__
del bpy.types.World.t3dClearColor__
del bpy.types.World.t3dFogColor__