FIX: Clip alpha texture tiling is now working.

INTERNAL: Change ebiten.Image.Size() usage to ebiten.Image.Bounds().
Adding the new lighting mode property. The lighting mode changes how a face is lit - currently, it can be lit either as normal, by the face's / vertices' normals, by the normals on both sides of the face (useful when the face has backface culling disabled), and as though the normals were always facing all light sources (which is useful for 2D / billboarded sprites).
ILight now exposes color and energy as functions.
FIX: Text cursor now always displays if set, rather than disappearing when the text object's typewriter effect ends.
perspectiveCorrected
solarlune 2023-10-08 23:11:59 -07:00
parent 44e57ce85d
commit 01f3de14db
8 changed files with 249 additions and 105 deletions

View File

@ -153,7 +153,8 @@ func NewCamera(w, h int) *Camera {
}
clipAlphaShaderText := []byte(
`package main
`//kage:unit pixels
package main
func encodeDepth(depth float) vec4 {
r := floor(depth * 255) / 255
@ -162,9 +163,28 @@ func NewCamera(w, h int) *Camera {
return vec4(r, g, b, 1);
}
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
tex := imageSrc0UnsafeAt(texCoord)
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
srcOrigin := imageSrc0Origin()
srcSize := imageSrc0Size()
// There's atlassing going on behind the scenes here, so:
// Subtract the source position by the src texture's origin on the atlas.
// This gives us the actual pixel coordinates.
tx := srcPos - srcOrigin
// Divide by the source image size to get the UV coordinates.
tx /= srcSize
// Apply fract() to loop the UV coords around [0-1].
tx = fract(tx)
// Multiply by the size to get the pixel coordinates again.
tx *= srcSize
// Add the origin back in to get the texture coordinates that Kage expects.
tex := imageSrc0UnsafeAt(tx + srcOrigin)
if (tex.a == 0) {
discard()
}
@ -1500,7 +1520,8 @@ func (camera *Camera) Render(scene *Scene, lights []ILight, models ...*Model) {
camera.clipAlphaIntermediate.DrawTrianglesShader(depthVertexList[:vertexListIndex], indexList[:indexListIndex], camera.clipAlphaRenderShader, &ebiten.DrawTrianglesShaderOptions{Images: [4]*ebiten.Image{img}})
w, h := camera.depthIntermediate.Size()
w := camera.depthIntermediate.Bounds().Dx()
h := camera.depthIntermediate.Bounds().Dy()
camera.depthIntermediate.DrawRectShader(w, h, camera.clipAlphaCompositeShader, &ebiten.DrawRectShaderOptions{Images: [4]*ebiten.Image{camera.resultDepthTexture, camera.clipAlphaIntermediate}})

16
gltf.go
View File

@ -310,8 +310,16 @@ func LoadGLTFData(data []byte, gltfLoadOptions *GLTFLoadOptions) (*Library, erro
if s, exists := dataMap["t3dCustomDepthValue__"]; exists {
newMat.CustomDepthOffsetValue = s.(float64)
}
if s, exists := dataMap["t3dNormalFacesLights__"]; exists {
newMat.NormalsAlwaysFaceLights = s.(float64) > 0
if s, exists := dataMap["t3dMaterialLightingMode__"]; exists {
// newMat.NormalsAlwaysFaceLights = s.(float64) > 0
switch int(s.(float64)) {
case 0:
newMat.LightingMode = LightingModeDefault
case 1:
newMat.LightingMode = LightingModeFixedNormals
case 2:
newMat.LightingMode = LightingModeDoubleSided
}
}
// At this point, parenting should be set up.
@ -1285,11 +1293,11 @@ func LoadGLTFData(data []byte, gltfLoadOptions *GLTFLoadOptions) (*Library, erro
if wc, exists := props["ambient color"]; exists {
wcc := wc.([]interface{})
worldColor := NewColor(float32(wcc[0].(float64)), float32(wcc[1].(float64)), float32(wcc[2].(float64)), 1).ConvertTosRGB()
world.AmbientLight.Color = worldColor
world.AmbientLight.color = worldColor
}
if wc, exists := props["ambient energy"]; exists {
world.AmbientLight.Energy = float32(wc.(float64))
world.AmbientLight.energy = float32(wc.(float64))
}
if cc, exists := props["clear color"]; exists {

239
light.go
View File

@ -1,5 +1,7 @@
package tetra3d
import "math"
// ILight represents an interface that is fulfilled by an object that emits light, returning the color a vertex should be given that Vertex and its model matrix.
type ILight interface {
INode
@ -15,6 +17,11 @@ type ILight interface {
// color buffer. If onlyVisible is true, only the visible vertices will be lit; if it's false, they will all be lit.
IsOn() bool // isOn is simply used tfo tell if a "generic" Light is on or not.
SetOn(on bool) // SetOn sets whether the light is on or not
Color() Color
SetColor(c Color)
Energy() float32
SetEnergy(energy float32)
}
//---------------//
@ -22,11 +29,11 @@ type ILight interface {
// AmbientLight represents an ambient light that colors the entire Scene.
type AmbientLight struct {
*Node
Color Color // Color is the color of the PointLight.
// Energy is the overall energy of the Light. Internally, technically there's no difference between a brighter color and a
color Color // Color is the color of the PointLight.
// energy is the overall energy of the Light. Internally, technically there's no difference between a brighter color and a
// higher energy, but this is here for convenience / adherance to GLTF / 3D modelers.
Energy float32
On bool // If the light is on and contributing to the scene.
energy float32
on bool // If the light is on and contributing to the scene.
result [3]float32
}
@ -35,16 +42,16 @@ type AmbientLight struct {
func NewAmbientLight(name string, r, g, b, energy float32) *AmbientLight {
return &AmbientLight{
Node: NewNode(name),
Color: NewColor(r, g, b, 1),
Energy: energy,
On: true,
color: NewColor(r, g, b, 1),
energy: energy,
on: true,
}
}
func (amb *AmbientLight) Clone() INode {
clone := NewAmbientLight(amb.name, amb.Color.R, amb.Color.G, amb.Color.B, amb.Energy)
clone.On = amb.On
clone := NewAmbientLight(amb.name, amb.color.R, amb.color.G, amb.color.B, amb.energy)
clone.on = amb.on
clone.Node = amb.Node.Clone().(*Node)
for _, child := range amb.children {
@ -56,7 +63,7 @@ func (amb *AmbientLight) Clone() INode {
}
func (amb *AmbientLight) beginRender() {
amb.result = [3]float32{amb.Color.R * amb.Energy, amb.Color.G * amb.Energy, amb.Color.B * amb.Energy}
amb.result = [3]float32{amb.color.R * amb.energy, amb.color.G * amb.energy, amb.color.B * amb.energy}
}
func (amb *AmbientLight) beginModel(model *Model) {}
@ -82,11 +89,27 @@ func (amb *AmbientLight) Unparent() {
}
func (amb *AmbientLight) IsOn() bool {
return amb.On && amb.Energy > 0
return amb.on && amb.energy > 0
}
func (amb *AmbientLight) SetOn(on bool) {
amb.On = on
amb.on = on
}
func (amb *AmbientLight) Color() Color {
return amb.color
}
func (amb *AmbientLight) SetColor(color Color) {
amb.color = color
}
func (amb *AmbientLight) Energy() float32 {
return amb.energy
}
func (amb *AmbientLight) SetEnergy(energy float32) {
amb.energy = energy
}
// Type returns the NodeType for this object.
@ -115,12 +138,12 @@ type PointLight struct {
// Range represents the distance after which the light fully attenuates. If this is 0 (the default),
// it falls off using something akin to the inverse square law.
Range float64
// Color is the color of the PointLight.
Color Color
// Energy is the overall energy of the Light, with 1.0 being full brightness. Internally, technically there's no
// color is the color of the PointLight.
color Color
// energy is the overall energy of the Light, with 1.0 being full brightness. Internally, technically there's no
// difference between a brighter color and a higher energy, but this is here for convenience / adherance to the
// GLTF spec and 3D modelers.
Energy float32
energy float32
// If the light is on and contributing to the scene.
On bool
@ -132,50 +155,50 @@ type PointLight struct {
func NewPointLight(name string, r, g, b, energy float32) *PointLight {
return &PointLight{
Node: NewNode(name),
Energy: energy,
Color: NewColor(r, g, b, 1),
energy: energy,
color: NewColor(r, g, b, 1),
On: true,
}
}
// Clone returns a new clone of the given point light.
func (point *PointLight) Clone() INode {
func (p *PointLight) Clone() INode {
clone := NewPointLight(point.name, point.Color.R, point.Color.G, point.Color.B, point.Energy)
clone.On = point.On
clone.Range = point.Range
clone := NewPointLight(p.name, p.color.R, p.color.G, p.color.B, p.energy)
clone.On = p.On
clone.Range = p.Range
clone.Node = point.Node.Clone().(*Node)
for _, child := range point.children {
child.setParent(point)
clone.Node = p.Node.Clone().(*Node)
for _, child := range p.children {
child.setParent(p)
}
return clone
}
func (point *PointLight) beginRender() {
point.rangeSquared = point.Range * point.Range
func (p *PointLight) beginRender() {
p.rangeSquared = p.Range * p.Range
}
func (point *PointLight) beginModel(model *Model) {
func (p *PointLight) beginModel(model *Model) {
p, s, r := model.Transform().Inverted().Decompose()
pos, sca, rot := model.Transform().Inverted().Decompose()
// Rather than transforming all vertices of all triangles of a mesh, we can just transform the
// point light's position by the inversion of the model's transform to get the same effect and save processing time.
// The same technique is used for Sphere - Triangle collision in bounds.go.
if model.skinned {
point.workingPosition = point.WorldPosition()
p.workingPosition = p.WorldPosition()
} else {
point.workingPosition = r.MultVec(point.WorldPosition()).Add(p.Mult(Vector{1 / s.X, 1 / s.Y, 1 / s.Z, s.W}))
p.workingPosition = rot.MultVec(p.WorldPosition()).Add(pos.Mult(Vector{1 / sca.X, 1 / sca.Y, 1 / sca.Z, sca.W}))
}
}
// Light returns the R, G, and B values for the PointLight for all vertices of a given Triangle.
func (point *PointLight) Light(meshPart *MeshPart, model *Model, targetColors []Color, onlyVisible bool) {
func (p *PointLight) Light(meshPart *MeshPart, model *Model, targetColors []Color, onlyVisible bool) {
// We calculate both the eye vector as well as the light vector so that if the camera passes behind the
// lit face and backface culling is off, the triangle can still be lit or unlit from the other side. Otherwise,
@ -195,11 +218,11 @@ func (point *PointLight) Light(meshPart *MeshPart, model *Model, targetColors []
vertNormal = model.Mesh.VertexNormals[index]
}
distance := point.workingPosition.DistanceSquared(vertPos)
distance := p.workingPosition.DistanceSquared(vertPos)
if point.Range > 0 {
if p.Range > 0 {
if distance > point.rangeSquared {
if distance > p.rangeSquared {
return
}
@ -220,7 +243,7 @@ func (point *PointLight) Light(meshPart *MeshPart, model *Model, targetColors []
// }
} else {
if 1/distance*float64(point.Energy) < 0.001 {
if 1/distance*float64(p.energy) < 0.001 {
return
}
}
@ -230,26 +253,30 @@ func (point *PointLight) Light(meshPart *MeshPart, model *Model, targetColors []
// return light
// }
lightVec := point.workingPosition.Sub(vertPos).Unit()
if mat := meshPart.Material; mat != nil && mat.NormalsAlwaysFaceLights {
lightVec := p.workingPosition.Sub(vertPos).Unit()
if mat := meshPart.Material; mat != nil && mat.LightingMode == LightingModeFixedNormals {
vertNormal = lightVec
}
diffuse := vertNormal.Dot(lightVec)
if mat := meshPart.Material; mat != nil && mat.LightingMode == LightingModeDoubleSided {
diffuse = math.Abs(diffuse)
}
if diffuse > 0 {
diffuseFactor := diffuse * (1.0 / (1.0 + (0.1 * distance))) * 2
if point.Range > 0 {
distClamp := clamp((point.rangeSquared-distance)/distance, 0, 1)
if p.Range > 0 {
distClamp := clamp((p.rangeSquared-distance)/distance, 0, 1)
diffuseFactor *= distClamp
}
targetColors[index] = targetColors[index].AddRGBA(
point.Color.R*float32(diffuseFactor)*point.Energy,
point.Color.G*float32(diffuseFactor)*point.Energy,
point.Color.B*float32(diffuseFactor)*point.Energy,
p.color.R*float32(diffuseFactor)*p.energy,
p.color.G*float32(diffuseFactor)*p.energy,
p.color.B*float32(diffuseFactor)*p.energy,
0,
)
@ -261,31 +288,47 @@ func (point *PointLight) Light(meshPart *MeshPart, model *Model, targetColors []
// AddChildren parents the provided children Nodes to the passed parent Node, inheriting its transformations and being under it in the scenegraph
// hierarchy. If the children are already parented to other Nodes, they are unparented before doing so.
func (point *PointLight) AddChildren(children ...INode) {
point.addChildren(point, children...)
func (p *PointLight) AddChildren(children ...INode) {
p.addChildren(p, children...)
}
// Unparent unparents the PointLight from its parent, removing it from the scenegraph.
func (point *PointLight) Unparent() {
if point.parent != nil {
point.parent.RemoveChildren(point)
func (p *PointLight) Unparent() {
if p.parent != nil {
p.parent.RemoveChildren(p)
}
}
func (point *PointLight) IsOn() bool {
return point.On && point.Energy > 0
func (p *PointLight) IsOn() bool {
return p.On && p.energy > 0
}
func (point *PointLight) SetOn(on bool) {
point.On = on
func (p *PointLight) SetOn(on bool) {
p.On = on
}
func (p *PointLight) Color() Color {
return p.color
}
func (p *PointLight) SetColor(color Color) {
p.color = color
}
func (p *PointLight) Energy() float32 {
return p.energy
}
func (p *PointLight) SetEnergy(energy float32) {
p.energy = energy
}
// Index returns the index of the Node in its parent's children list.
// If the node doesn't have a parent, its index will be -1.
func (point *PointLight) Index() int {
if point.parent != nil {
for i, c := range point.parent.Children() {
if c == point {
func (p *PointLight) Index() int {
if p.parent != nil {
for i, c := range p.parent.Children() {
if c == p {
return i
}
}
@ -294,7 +337,7 @@ func (point *PointLight) Index() int {
}
// Type returns the NodeType for this object.
func (point *PointLight) Type() NodeType {
func (p *PointLight) Type() NodeType {
return NodeTypePointLight
}
@ -303,10 +346,10 @@ func (point *PointLight) Type() NodeType {
// DirectionalLight represents a directional light of infinite distance.
type DirectionalLight struct {
*Node
Color Color // Color is the color of the light.
// Energy is the overall energy of the light. Internally, technically there's no difference between a brighter color and a
color Color // Color is the color of the light.
// energy is the overall energy of the light. Internally, technically there's no difference between a brighter color and a
// higher energy, but this is here for convenience / adherance to GLTF / 3D modelers.
Energy float32
energy float32
On bool // If the light is on and contributing to the scene.
workingForward Vector // Internal forward vector so we don't have to calculate it for every triangle for every model using this light.
@ -317,8 +360,8 @@ type DirectionalLight struct {
func NewDirectionalLight(name string, r, g, b, energy float32) *DirectionalLight {
return &DirectionalLight{
Node: NewNode(name),
Color: NewColor(r, g, b, 1),
Energy: energy,
color: NewColor(r, g, b, 1),
energy: energy,
On: true,
}
}
@ -326,7 +369,7 @@ func NewDirectionalLight(name string, r, g, b, energy float32) *DirectionalLight
// Clone returns a new DirectionalLight clone from the given DirectionalLight.
func (sun *DirectionalLight) Clone() INode {
clone := NewDirectionalLight(sun.name, sun.Color.R, sun.Color.G, sun.Color.B, sun.Energy)
clone := NewDirectionalLight(sun.name, sun.color.R, sun.color.G, sun.color.B, sun.energy)
clone.On = sun.On
@ -362,20 +405,24 @@ func (sun *DirectionalLight) Light(meshPart *MeshPart, model *Model, targetColor
normal = sun.workingModelRotation.MultVec(model.Mesh.VertexNormals[index])
}
if mat := meshPart.Material; mat != nil && mat.NormalsAlwaysFaceLights {
if mat := meshPart.Material; mat != nil && mat.LightingMode == LightingModeFixedNormals {
normal = sun.workingForward
}
diffuseFactor := normal.Dot(sun.workingForward)
if mat := meshPart.Material; mat != nil && mat.LightingMode == LightingModeDoubleSided {
diffuseFactor = math.Abs(diffuseFactor)
}
if diffuseFactor <= 0 {
return
}
targetColors[index] = targetColors[index].AddRGBA(
sun.Color.R*float32(diffuseFactor)*sun.Energy,
sun.Color.G*float32(diffuseFactor)*sun.Energy,
sun.Color.B*float32(diffuseFactor)*sun.Energy,
sun.color.R*float32(diffuseFactor)*sun.energy,
sun.color.G*float32(diffuseFactor)*sun.energy,
sun.color.B*float32(diffuseFactor)*sun.energy,
0,
)
@ -397,13 +444,29 @@ func (sun *DirectionalLight) Unparent() {
}
func (sun *DirectionalLight) IsOn() bool {
return sun.On && sun.Energy > 0
return sun.On && sun.energy > 0
}
func (sun *DirectionalLight) SetOn(on bool) {
sun.On = on
}
func (d *DirectionalLight) Color() Color {
return d.color
}
func (d *DirectionalLight) SetColor(color Color) {
d.color = color
}
func (d *DirectionalLight) Energy() float32 {
return d.energy
}
func (d *DirectionalLight) SetEnergy(energy float32) {
d.energy = energy
}
// Index returns the index of the Node in its parent's children list.
// If the node doesn't have a parent, its index will be -1.
func (sun *DirectionalLight) Index() int {
@ -426,8 +489,8 @@ func (sun *DirectionalLight) Type() NodeType {
type CubeLight struct {
*Node
Dimensions Dimensions // The overall dimensions of the CubeLight.
Energy float32 // The overall energy of the CubeLight
Color Color // The color of the CubeLight
energy float32 // The overall energy of the CubeLight
color Color // The color of the CubeLight
On bool // If the CubeLight is on or not
// A value between 0 and 1 indicating how much opposite faces are still lit within the volume (i.e. at LightBleed = 0.0,
// faces away from the light are dark; at 1.0, faces away from the light are fully illuminated)
@ -444,8 +507,8 @@ func NewCubeLight(name string, dimensions Dimensions) *CubeLight {
Node: NewNode(name),
// Dimensions: Dimensions{{-w / 2, -h / 2, -d / 2}, {w / 2, h / 2, d / 2}},
Dimensions: dimensions,
Energy: 1,
Color: NewColor(1, 1, 1, 1),
energy: 1,
color: NewColor(1, 1, 1, 1),
On: true,
LightingAngle: Vector{0, -1, 0, 0},
}
@ -465,8 +528,8 @@ func NewCubeLightFromModel(name string, model *Model) *CubeLight {
// Clone clones the CubeLight, returning a deep copy.
func (cube *CubeLight) Clone() INode {
newCube := NewCubeLight(cube.name, cube.Dimensions)
newCube.Energy = cube.Energy
newCube.Color = cube.Color
newCube.energy = cube.energy
newCube.color = cube.color
newCube.On = cube.On
newCube.Bleed = cube.Bleed
newCube.LightingAngle = cube.LightingAngle
@ -623,12 +686,16 @@ func (cube *CubeLight) Light(meshPart *MeshPart, model *Model, targetColors []Co
var diffuse, diffuseFactor float64
if mat := meshPart.Material; mat != nil && mat.NormalsAlwaysFaceLights {
if mat := meshPart.Material; mat != nil && mat.LightingMode == LightingModeFixedNormals {
vertNormal = cube.workingAngle
}
diffuse = vertNormal.Dot(cube.workingAngle)
if mat := meshPart.Material; mat != nil && mat.LightingMode == LightingModeDoubleSided {
diffuse = math.Abs(diffuse)
}
if cube.Bleed > 0 {
if diffuse < 0 {
@ -661,9 +728,9 @@ func (cube *CubeLight) Light(meshPart *MeshPart, model *Model, targetColors []Co
}
targetColors[index] = targetColors[index].AddRGBA(
cube.Color.R*float32(diffuseFactor)*cube.Energy,
cube.Color.G*float32(diffuseFactor)*cube.Energy,
cube.Color.B*float32(diffuseFactor)*cube.Energy,
cube.color.R*float32(diffuseFactor)*cube.energy,
cube.color.G*float32(diffuseFactor)*cube.energy,
cube.color.B*float32(diffuseFactor)*cube.energy,
0,
)
@ -681,6 +748,22 @@ func (cube *CubeLight) SetOn(on bool) {
cube.On = on
}
func (cube *CubeLight) Color() Color {
return cube.color
}
func (cube *CubeLight) SetColor(color Color) {
cube.color = color
}
func (cube *CubeLight) Energy() float32 {
return cube.energy
}
func (cube *CubeLight) SetEnergy(energy float32) {
cube.energy = energy
}
// AddChildren parents the provided children Nodes to the passed parent Node, inheriting its transformations and being under it in the scenegraph
// hierarchy. If the children are already parented to other Nodes, they are unparented before doing so.
func (cube *CubeLight) AddChildren(children ...INode) {

View File

@ -33,6 +33,12 @@ const (
BillboardModeAll // Billboard on all axes
)
const (
LightingModeDefault = iota // Default lighting mode
LightingModeFixedNormals // Lighting applies as though faces always point towards light sources; good for 2D sprites
LightingModeDoubleSided // Lighting applies for double-sided faces
)
type Material struct {
library *Library // library is a reference to the Library that this Material came from.
Name string // Name is the name of the Material.
@ -64,9 +70,9 @@ type Material struct {
// all non-transparent materials.
TransparencyMode int
CustomDepthOffsetOn bool // Whether custom depth offset is on or not.
CustomDepthOffsetValue float64 // How many world units to offset the depth of the material by.
NormalsAlwaysFaceLights bool // Whether normals should always face light sources for lighting or not.
CustomDepthOffsetOn bool // Whether custom depth offset is on or not.
CustomDepthOffsetValue float64 // How many world units to offset the depth of the material by.
LightingMode int // How materials are lit
// CustomDepthFunction is a customizeable function that takes the depth value of each vertex of a rendered MeshPart and
// transforms it, returning a different value.

View File

@ -401,6 +401,7 @@ The following is a rough to-do list (tasks with checks have been implemented):
- [ ] **3D Sound** (adjusting panning of sound sources based on 3D location?)
- [ ] **Optimization**
- [ ] -- It might be possible to not have to write depth manually (5/22/23, SolarLune: Not sure what past me meant by this)
- [ ] -- Minimize texture-swapping - should be possible to do now that Kage shaders can handle images of multiple sizes.
- [X] -- Make NodeFilters work lazily, rather than gathering all nodes in the filter at once
- [X] -- Reusing vertex indices for adjacent triangles
- [ ] -- Multithreading (particularly for vertex transformations)

View File

@ -57,6 +57,12 @@ materialBillboardModes = [
("FULL", "Full", "Full billboarding - the (unskinned) object rotates fully to face the camera.", 0, 3),
]
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),
("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),
@ -792,11 +798,14 @@ class MATERIAL_PT_tetra3d(bpy.types.Panel):
row = self.layout.row()
row.prop(context.material, "use_backface_culling")
row = self.layout.row()
row.prop(context.material, "blend_method")
row.label(text="Blend Mode:")
row.prop(context.material, "blend_method", text="")
row = self.layout.row()
row.prop(context.material, "t3dCompositeMode__")
row.label(text="Composite Mode:")
row.prop(context.material, "t3dCompositeMode__", text="")
row = self.layout.row()
row.prop(context.material, "t3dBillboardMode__")
row.label(text="Billboard Mode:")
row.prop(context.material, "t3dBillboardMode__", text="")
box = self.layout.box()
row = box.row()
@ -816,7 +825,8 @@ class MATERIAL_PT_tetra3d(bpy.types.Panel):
row.enabled = context.material.t3dCustomDepthOn__
row.prop(context.material, "t3dCustomDepthValue__")
row = box.row()
row.prop(context.material, "t3dNormalFacesLights__")
row.label(text="Lighting Mode:")
row.prop(context.material, "t3dMaterialLightingMode__", text="")
if context.object.active_material != None:
@ -1912,7 +1922,7 @@ def register():
bpy.types.Material.t3dBillboardMode__ = bpy.props.EnumProperty(items=materialBillboardModes, name="Billboarding Mode", description="Billboard mode (i.e. if the object with this material should rotate to face the camera) for this material", default="NONE")
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.t3dNormalFacesLights__ = bpy.props.BoolProperty(name="Normals Always Face Lights", description="Whether normals that use this material should always face light sources or not; particularly useful for billboarded 2D sprites", default=False)
bpy.types.Material.t3dMaterialLightingMode__ = bpy.props.EnumProperty(items=materialLightingModes, name="Lighting mode", description="How materials should be lit", default="DEFAULT")
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)
@ -2008,10 +2018,10 @@ def unregister():
del bpy.types.Material.t3dMaterialFogless__
del bpy.types.Material.t3dCompositeMode__
del bpy.types.Material.t3dBillboardMode__
del bpy.types.Material.t3dMaterialLightingMode__
del bpy.types.Material.t3dCustomDepthOn__
del bpy.types.Material.t3dCustomDepthValue__
del bpy.types.Material.t3dNormalFacesLights__
del bpy.types.Material.t3dGameProperties__
del bpy.types.Material.t3dAutoUV__
del bpy.types.Material.t3dAutoUVUnitSize__

18
text.go
View File

@ -225,12 +225,12 @@ func (textObj *Text) SetText(txt string, arguments ...interface{}) *Text {
// Some fonts have space characters that are basically empty somehow...?
spaceAdd := 0
if measureText(" ", textObj.style.Font) <= 0 {
spaceAdd = measureText("M", textObj.style.Font)
if measureText(" ", textObj.style.Font).Dx() <= 0 {
spaceAdd = measureText("M", textObj.style.Font).Dx()
}
for i, word := range split {
wordSpace := measureText(word, textObj.style.Font)
wordSpace := measureText(word, textObj.style.Font).Dx()
runningMeasure += wordSpace + spaceAdd
if runningMeasure >= textureWidth-safetyMargin {
@ -256,6 +256,10 @@ func (textObj *Text) SetText(txt string, arguments ...interface{}) *Text {
textObj.parsedText = parsedText
textObj.UpdateTexture()
if textObj.typewriterIndex >= 0 {
textObj.typewriterIndex = 0
}
}
return textObj
@ -293,7 +297,7 @@ func (textObj *Text) UpdateTexture() {
for lineIndex, line := range textObj.parsedText {
measure := text.BoundString(textObj.style.Font, line)
measure := measureText(line, textObj.style.Font)
if textObj.typewriterOn && typewriterIndex >= 0 {
@ -304,7 +308,7 @@ func (textObj *Text) UpdateTexture() {
if typewriterIndex > len(line) {
typewriterIndex -= len(line)
} else if typing {
line = line[:typewriterIndex] + textObj.style.Cursor
line = line[:typewriterIndex]
typing = false
}
@ -328,6 +332,10 @@ func (textObj *Text) UpdateTexture() {
x += textObj.style.MarginHorizontal
}
if textObj.typewriterOn && (len(line) < len(textObj.parsedText[lineIndex]) || lineIndex == len(textObj.parsedText)-1) {
line += textObj.style.Cursor
}
text.Draw(textObj.Texture, line, textObj.style.Font, x, y, color.RGBA{255, 255, 255, 255})
// text.Draw(textObj.Texture, line, textObj.style.Font, x, y, textObj.style.FGColor.ToRGBA64())

View File

@ -1,6 +1,7 @@
package tetra3d
import (
"image"
"math"
"golang.org/x/image/font"
@ -69,8 +70,14 @@ func round(value float64) float64 {
}
func measureText(text string, fontFace font.Face) int {
// bounds, _ := font.BoundString(fontFace, text)
advance := font.MeasureString(fontFace, text)
return int(advance >> 6)
// type TextMeasure struct {
// X, Y int
// }
func measureText(text string, fontFace font.Face) image.Rectangle {
bounds, _ := font.BoundString(fontFace, text)
// advance := font.MeasureString(fontFace, text)
newBounds := image.Rect(int(bounds.Min.X)>>6, int(bounds.Min.Y)>>6, int(bounds.Max.X)>>6, int(bounds.Max.Y)>>6)
// newBounds := TextMeasure{int((bounds.Max.X - bounds.Min.X) >> 6), int((bounds.Max.Y - bounds.Min.Y) >> 6)}
return newBounds
}