Renaming Broadphase.TrianglesFromBounding() > Broadphase.TrianglesFromBoundingObject().

Reworking NewBroadphase() to take a mesh and world position, rather than a BoundingTriangles specifically, since we can use it for other purposes this way.
Adding new Set utility class for sets of objects.
Reworking Broadphase to use Sets of triangles.
Rewording Camera.WorldToScreen() functions.
Removing neighborSet object.
FIX: Sector.NeighborsWithinRange() should not include the calling Sector itself.
FIX: Unique objects that use Text objects that share the same material now create cloned Materials, allowing them to both have unique text outputs.
Propagation
solarlune 2023-11-01 13:42:54 -07:00
parent d73ebea74f
commit c1dead5f26
8 changed files with 201 additions and 262 deletions

View File

@ -258,7 +258,7 @@ func btSphereTriangles(sphere *BoundingSphere, triangles *BoundingTriangles) *Co
result := newCollision(triangles)
tris := triangles.Broadphase.TrianglesFromBounding(sphere)
tris := triangles.Broadphase.TrianglesFromBoundingObject(sphere)
for triID := range tris {
@ -394,7 +394,7 @@ func btAABBTriangles(box *BoundingAABB, triangles *BoundingTriangles) *Collision
result := newCollision(triangles)
tris := triangles.Broadphase.TrianglesFromBounding(box)
tris := triangles.Broadphase.TrianglesFromBoundingObject(box)
for triID := range tris {
@ -705,7 +705,7 @@ func btCapsuleTriangles(capsule *BoundingCapsule, triangles *BoundingTriangles)
result := newCollision(triangles)
tris := triangles.Broadphase.TrianglesFromBounding(capsule)
tris := triangles.Broadphase.TrianglesFromBoundingObject(capsule)
spherePos := NewVectorZero()

View File

@ -38,7 +38,7 @@ func NewBoundingTriangles(name string, mesh *Mesh, broadphaseGridSize float64) *
gridSize = int(math.Ceil(maxDim / broadphaseGridSize))
}
bt.Broadphase = NewBroadphase(gridSize, bt)
bt.Broadphase = NewBroadphase(gridSize, bt.WorldPosition(), mesh)
return bt
}
@ -62,9 +62,9 @@ func (bt *BoundingTriangles) UpdateTransform() {
bt.BoundingAABB.Transform()
if bt.Broadphase != nil {
bt.Broadphase.Center.SetWorldTransform(transform)
bt.Broadphase.Center.MoveVec(rot)
bt.Broadphase.Center.Transform() // Update the transform
bt.Broadphase.center.SetWorldTransform(transform)
bt.Broadphase.center.MoveVec(rot)
bt.Broadphase.center.Transform() // Update the transform
}
}

View File

@ -1,117 +1,121 @@
package tetra3d
// Broadphase is a utility object specifically created to assist with quickly ruling out
// triangles when doing Triangle-Sphere/Capsule/AABB collision detection. This works
// largely automatically; you should generally not have to tweak this too much. The general
// idea is that the broadphase is composed of AABBs in a grid layout, completely covering
// the owning BoundingTriangles instance. When you check for a collision against the
// BoundingTriangles instance, it first checks to see if the colliding object is within the
// BoundingTriangles' overall AABB bounding box. If so, it proceeds to the broadphase check,
// where it sees which set(s) of triangles the colliding object could be colliding with, and
// then returns that set for finer examination.
// Broadphase is a utility object specifically created to assist with quickly ruling out triangles
// for collision detection or mesh rendering. This works largely automatically; you should generally
// not have to tweak this too much.
// The general idea is that the broadphase is composed of AABBs in a grid layout, completely covering
// the owning mesh. When you, say, check for a collision against a BoundingTriangles object, it first
// checks to see if the colliding object is within the BoundingTriangles' overall AABB bounding box.
// If so, it proceeds to the broadphase check, where it sees which set(s) of triangles the colliding
// object could be colliding with, and then returns that set for finer examination.
type Broadphase struct {
GridSize int
cellSize float64
Center *Node
aabb *BoundingAABB
TriSets [][][][]uint16
allTriSet map[uint16]bool
BoundingTriangles *BoundingTriangles
GridCellCount int // The number of cells in the broadphase grid
cellSize float64 // The size of each cell in the grid
center *Node // The center node of the broadphase
workingAABB *BoundingAABB // The working-process AABB used to determine which triangles belong in which cells
TriSets [][][][]uint16 // The sets of triangles
allTriSet Set[uint16] // A set containing all triangles
mesh *Mesh // The mesh used for the Broadphase object
}
// NewBroadphase returns a new Broadphase instance.
func NewBroadphase(gridSize int, bt *BoundingTriangles) *Broadphase {
bp := &Broadphase{
BoundingTriangles: bt,
Center: NewNode("broadphase center"),
aabb: NewBoundingAABB("bounding aabb", 1, 1, 1),
// NewBroadphase returns a new Broadphase object.
// gridCellCount is number of cells in the grid.
// worldPosition is the world position of the broadphase object; this should be a zero vector if
// it's being used for a mesh, and should be the position of the BoundingTriangles if it's being used for
// collision testing.
// mesh is the Mesh to be used for broadphase testing.
func NewBroadphase(gridCellCount int, worldPosition Vector, mesh *Mesh) *Broadphase {
b := &Broadphase{
mesh: mesh,
center: NewNode("broadphase center"),
workingAABB: NewBoundingAABB("bounding aabb", 1, 1, 1),
}
bp.Center.AddChildren(bp.aabb)
b.center.AddChildren(b.workingAABB)
b.center.SetWorldPositionVec(worldPosition.Add(mesh.Dimensions.Center()))
b.center.Transform() // Update the transform so that when resizing, the aabb can properly check all triangles
b.Resize(gridCellCount)
bp.Center.SetWorldPositionVec(bt.WorldPosition().Add(bt.Mesh.Dimensions.Center()))
bp.Center.Transform() // Update the transform so that when resizing, the aabb can properly check all triangles
bp.Resize(gridSize)
return bp
return b
}
// Clone clones the Broadphase.
func (bp *Broadphase) Clone() *Broadphase {
newBroadphase := NewBroadphase(0, bp.BoundingTriangles)
newBroadphase.Center = bp.Center.Clone().(*Node)
newBroadphase.aabb = newBroadphase.Center.children[0].(*BoundingAABB)
newBroadphase.GridSize = bp.GridSize
newBroadphase.cellSize = bp.cellSize
func (b *Broadphase) Clone() *Broadphase {
newBroadphase := NewBroadphase(0, b.center.WorldPosition(), b.mesh)
newBroadphase.center = b.center.Clone().(*Node)
newBroadphase.workingAABB = newBroadphase.center.children[0].(*BoundingAABB)
newBroadphase.GridCellCount = b.GridCellCount
newBroadphase.cellSize = b.cellSize
ts := make([][][][]uint16, len(bp.TriSets))
ts := make([][][][]uint16, len(b.TriSets))
for i := range bp.TriSets {
ts[i] = make([][][]uint16, len(bp.TriSets[i]))
for j := range bp.TriSets[i] {
ts[i][j] = make([][]uint16, len(bp.TriSets[i][j]))
for k := range bp.TriSets[i][j] {
ts[i][j][k] = make([]uint16, len(bp.TriSets[i][j][k]))
copy(ts[i][j][k], bp.TriSets[i][j][k])
for i := range b.TriSets {
ts[i] = make([][][]uint16, len(b.TriSets[i]))
for j := range b.TriSets[i] {
ts[i][j] = make([][]uint16, len(b.TriSets[i][j]))
for k := range b.TriSets[i][j] {
ts[i][j][k] = make([]uint16, len(b.TriSets[i][j][k]))
copy(ts[i][j][k], b.TriSets[i][j][k])
}
}
}
newBroadphase.TriSets = ts
newBroadphase.allTriSet = bp.allTriSet
newBroadphase.allTriSet = b.allTriSet.Clone()
return newBroadphase
}
// Resize resizes the Broadphase struct, using the new gridSize for how big in units each cell in the grid should be.
func (bp *Broadphase) Resize(gridSize int) {
bp.GridSize = gridSize
func (b *Broadphase) Resize(gridSize int) {
b.GridCellCount = gridSize
if gridSize <= 0 {
return
}
maxDim := bp.BoundingTriangles.Mesh.Dimensions.MaxDimension()
maxDim := b.mesh.Dimensions.MaxDimension()
cellSize := (maxDim / float64(gridSize)) + 1 // The +1 adds a little extra room
bp.cellSize = cellSize
b.cellSize = cellSize
bp.aabb.internalSize.X = cellSize
bp.aabb.internalSize.Y = cellSize
bp.aabb.internalSize.Z = cellSize
bp.aabb.updateSize()
b.workingAABB.internalSize.X = cellSize
b.workingAABB.internalSize.Y = cellSize
b.workingAABB.internalSize.Z = cellSize
b.workingAABB.updateSize()
bp.TriSets = make([][][][]uint16, gridSize)
bp.allTriSet = make(map[uint16]bool)
b.TriSets = make([][][][]uint16, gridSize)
b.allTriSet = newSet[uint16]()
hg := float64(bp.GridSize) / 2
hg := float64(b.GridCellCount) / 2
for i := 0; i < gridSize; i++ {
bp.TriSets[i] = make([][][]uint16, gridSize)
b.TriSets[i] = make([][][]uint16, gridSize)
for j := 0; j < gridSize; j++ {
bp.TriSets[i][j] = make([][]uint16, gridSize)
b.TriSets[i][j] = make([][]uint16, gridSize)
for k := 0; k < gridSize; k++ {
bp.TriSets[i][j][k] = []uint16{}
b.TriSets[i][j][k] = []uint16{}
bp.aabb.SetLocalPositionVec(Vector{
(float64(i) - hg + 0.5) * bp.cellSize,
(float64(j) - hg + 0.5) * bp.cellSize,
(float64(k) - hg + 0.5) * bp.cellSize,
b.workingAABB.SetLocalPositionVec(Vector{
(float64(i) - hg + 0.5) * b.cellSize,
(float64(j) - hg + 0.5) * b.cellSize,
(float64(k) - hg + 0.5) * b.cellSize,
0,
})
center := bp.aabb.WorldPosition()
center := b.workingAABB.WorldPosition()
for _, tri := range bp.BoundingTriangles.Mesh.Triangles {
for _, tri := range b.mesh.Triangles {
v0 := bp.BoundingTriangles.Mesh.VertexPositions[tri.VertexIndices[0]]
v1 := bp.BoundingTriangles.Mesh.VertexPositions[tri.VertexIndices[1]]
v2 := bp.BoundingTriangles.Mesh.VertexPositions[tri.VertexIndices[2]]
v0 := b.mesh.VertexPositions[tri.VertexIndices[0]]
v1 := b.mesh.VertexPositions[tri.VertexIndices[1]]
v2 := b.mesh.VertexPositions[tri.VertexIndices[2]]
closestOnTri := closestPointOnTri(center, v0, v1, v2)
if bp.aabb.PointInside(closestOnTri) {
bp.TriSets[i][j][k] = append(bp.TriSets[i][j][k], tri.ID)
if b.workingAABB.PointInside(closestOnTri) {
b.TriSets[i][j][k] = append(b.TriSets[i][j][k], tri.ID)
}
bp.allTriSet[tri.ID] = true
b.allTriSet.Add(tri.ID)
}
@ -123,40 +127,45 @@ func (bp *Broadphase) Resize(gridSize int) {
}
// TrianglesFromBounding returns a set (a map[int]bool) of triangle IDs, based on where the BoundingObject is
// Mesh returns the mesh that is associated with the Broadphase object.
func (b *Broadphase) Mesh() *Mesh {
return b.mesh
}
// TrianglesFromBoundingObject returns a set of triangle IDs, based on where the BoundingObject is
// in relation to the Broadphase owning BoundingTriangles instance. The returned set contains each triangle only
// once, of course.
func (bp *Broadphase) TrianglesFromBounding(boundingObject IBoundingObject) map[uint16]bool {
func (b *Broadphase) TrianglesFromBoundingObject(boundingObject IBoundingObject) Set[uint16] {
if bp.GridSize <= 1 {
return bp.allTriSet
if b.GridCellCount <= 1 {
return b.allTriSet
}
trianglesSet := make(map[uint16]bool, len(bp.TriSets))
trianglesSet := make(Set[uint16], len(b.TriSets))
hg := float64(bp.GridSize) / 2
hg := float64(b.GridCellCount) / 2
// We're brute forcing it here; this isn't ideal, but it works alright for now
for i := 0; i < bp.GridSize; i++ {
for j := 0; j < bp.GridSize; j++ {
for k := 0; k < bp.GridSize; k++ {
for i := 0; i < b.GridCellCount; i++ {
for j := 0; j < b.GridCellCount; j++ {
for k := 0; k < b.GridCellCount; k++ {
// We can skip empty sets
if len(bp.TriSets[i][j][k]) == 0 {
if len(b.TriSets[i][j][k]) == 0 {
continue
}
bp.aabb.SetLocalPositionVec(Vector{
(float64(i) - hg + 0.5) * bp.cellSize,
(float64(j) - hg + 0.5) * bp.cellSize,
(float64(k) - hg + 0.5) * bp.cellSize,
b.workingAABB.SetLocalPositionVec(Vector{
(float64(i) - hg + 0.5) * b.cellSize,
(float64(j) - hg + 0.5) * b.cellSize,
(float64(k) - hg + 0.5) * b.cellSize,
0,
})
if bp.aabb.Colliding(boundingObject) {
if b.workingAABB.Colliding(boundingObject) {
// triangles = append(triangles, bp.TriSets[i][j][k]...)
for _, triID := range bp.TriSets[i][j][k] {
trianglesSet[triID] = true
for _, triID := range b.TriSets[i][j][k] {
trianglesSet.Add(triID)
}
}
}
@ -167,36 +176,32 @@ func (bp *Broadphase) TrianglesFromBounding(boundingObject IBoundingObject) map[
}
func (bp *Broadphase) allAABBPositions() []*BoundingAABB {
func (b *Broadphase) allAABBPositions() []*BoundingAABB {
aabbs := []*BoundingAABB{}
if bp.GridSize <= 0 || bp.cellSize <= 0 {
if b.GridCellCount <= 0 || b.cellSize <= 0 {
return aabbs
}
hg := float64(bp.GridSize) / 2
hg := float64(b.GridCellCount) / 2
bp.aabb.SetLocalPositionVec(NewVectorZero())
b.workingAABB.SetLocalPositionVec(NewVectorZero())
for i := 0; i < bp.GridSize; i++ {
for j := 0; j < bp.GridSize; j++ {
for k := 0; k < bp.GridSize; k++ {
for i := 0; i < b.GridCellCount; i++ {
for j := 0; j < b.GridCellCount; j++ {
for k := 0; k < b.GridCellCount; k++ {
clone := bp.aabb.Clone().(*BoundingAABB)
clone := b.workingAABB.Clone().(*BoundingAABB)
clone.SetLocalPositionVec(Vector{
// (-float64(bp.GridSize)/2+float64(i))*bp.CellSize + (bp.CellSize / 2),
// (-float64(bp.GridSize)/2+float64(j))*bp.CellSize + (bp.CellSize / 2),
// (-float64(bp.GridSize)/2+float64(k))*bp.CellSize + (bp.CellSize / 2),
// 0, 0, 0,
(float64(i) - hg + 0.5) * bp.cellSize,
(float64(j) - hg + 0.5) * bp.cellSize,
(float64(k) - hg + 0.5) * bp.cellSize,
(float64(i) - hg + 0.5) * b.cellSize,
(float64(j) - hg + 0.5) * b.cellSize,
(float64(k) - hg + 0.5) * b.cellSize,
0,
})
clone.SetWorldTransform(bp.aabb.Transform().Mult(clone.Transform()))
clone.SetWorldTransform(b.workingAABB.Transform().Mult(clone.Transform()))
clone.Transform()
@ -208,106 +213,3 @@ func (bp *Broadphase) allAABBPositions() []*BoundingAABB {
return aabbs
}
// package tetra3d
// import (
// "github.com/kvartborg/vector"
// )
// type Broadphase struct {
// GridSize int
// CellSize float64
// Limits Dimensions
// Center *Node
// AABBs [][][]*BoundingAABB
// allAABBs []*BoundingAABB
// TriSets [][][][]*Triangle
// BoundingTriangles *BoundingTriangles
// }
// func NewBroadphase(gridSize int, cellSize float64, bt *BoundingTriangles) *Broadphase {
// bp := &Broadphase{
// BoundingTriangles: bt,
// Center: NewNode("broadphase center"),
// }
// bp.Resize(gridSize, cellSize)
// return bp
// }
// func (bp *Broadphase) Resize(gridSize int, cellSize float64) {
// bp.GridSize = gridSize
// bp.CellSize = cellSize
// bp.AABBs = make([][][]*BoundingAABB, gridSize)
// bp.TriSets = make([][][][]*Triangle, gridSize)
// bp.allAABBs = make([]*BoundingAABB, 0, gridSize*gridSize*gridSize)
// for i := range bp.AABBs {
// bp.AABBs[i] = make([][]*BoundingAABB, gridSize)
// bp.TriSets[i] = make([][][]*Triangle, gridSize)
// for j := 0; j < gridSize; j++ {
// bp.AABBs[i][j] = make([]*BoundingAABB, gridSize)
// bp.TriSets[i][j] = make([][]*Triangle, gridSize)
// for k := 0; k < gridSize; k++ {
// b := NewBoundingAABB("", cellSize, cellSize, cellSize)
// bp.Center.AddChildren(b)
// b.SetLocalPositionVec(Vector{
// (-float64(gridSize)/2+float64(i))*cellSize + (cellSize / 2),
// (-float64(gridSize)/2+float64(j))*cellSize + (cellSize / 2),
// (-float64(gridSize)/2+float64(k))*cellSize + (cellSize / 2),
// })
// bp.allAABBs = append(bp.allAABBs, b)
// bp.AABBs[i][j][k] = b
// bp.TriSets[i][j][k] = []*Triangle{}
// }
// }
// }
// bp.Limits = Dimensions{
// {-float64(gridSize) * cellSize, -float64(gridSize) * cellSize, -float64(gridSize) * cellSize},
// {float64(gridSize) * cellSize, float64(gridSize) * cellSize, float64(gridSize) * cellSize},
// }
// for _, tri := range bp.BoundingTriangles.Mesh.Triangles {
// for i := range bp.AABBs {
// for j := range bp.AABBs[i] {
// for k := range bp.AABBs[i][j] {
// v0 := bp.BoundingTriangles.Mesh.VertexPositions[tri.ID*3]
// v1 := bp.BoundingTriangles.Mesh.VertexPositions[tri.ID*3+1]
// v2 := bp.BoundingTriangles.Mesh.VertexPositions[tri.ID*3+2]
// closestOnAABB := bp.AABBs[i][j][k].ClosestPoint(tri.Center)
// closestOnTri := closestPointOnTri(closestOnAABB, v0, v1, v2)
// // if bp.AABBs[i][j][k].PointInside(v0) || bp.AABBs[i][j][k].PointInside(v1) || bp.AABBs[i][j][k].PointInside(v2) {
// if bp.AABBs[i][j][k].PointInside(closestOnTri) {
// bp.TriSets[i][j][k] = append(bp.TriSets[i][j][k], tri)
// }
// }
// }
// }
// }
// }
// func (bp *Broadphase) GetTrianglesFromBounding(boundingObject BoundingObject) map[*Triangle]bool {
// // triangles := make([]*Triangle, 0, len(bp.TriSets))
// triangles := make(map[*Triangle]bool, len(bp.TriSets))
// // We're brute forcing it here; this isn't ideal, but it works alright for now
// for i := range bp.AABBs {
// for j := range bp.AABBs[i] {
// for k := range bp.AABBs[i][j] {
// if bp.AABBs[i][j][k].Colliding(boundingObject) {
// // triangles = append(triangles, bp.TriSets[i][j][k]...)
// for _, tri := range bp.TriSets[i][j][k] {
// triangles[tri] = true
// }
// }
// }
// }
// }
// return triangles
// }

View File

@ -495,14 +495,14 @@ func (camera *Camera) ClipToScreen(vert Vector) Vector {
}
// WorldToScreenPixels transforms a 3D position in the world to a position onscreen, with X and Y representing the pixels.
// The Z coordinate indicates depth away from the camera in standard units.
// The Z coordinate indicates depth away from the camera in 3D world units.
func (camera *Camera) WorldToScreenPixels(vert Vector) Vector {
v := NewMatrix4Translate(vert.X, vert.Y, vert.Z).Mult(camera.ViewMatrix().Mult(camera.Projection()))
return camera.ClipToScreen(v.MultVecW(NewVectorZero()))
}
// WorldToScreen transforms a 3D position in the world to a 2D vector, with X and Y ranging from -1 to 1.
// The Z coordinate indicates depth away from the camera in standard units.
// The Z coordinate indicates depth away from the camera in 3D world units.
func (camera *Camera) WorldToScreen(vert Vector) Vector {
v := camera.WorldToScreenPixels(vert)
w, h := camera.Size()

View File

@ -410,6 +410,7 @@ The following is a rough to-do list (tasks with checks have been implemented):
- [X] -- Armature animation improvements?
- [X] -- Custom Vectors
- [ ] -- -- Vector pools again?
- [ ] -- -- Move over to float32 for mathematics - should be possible with math32 : https://github.com/chewxy/math32
- [ ] -- Matrix pools?
- [ ] -- Raytest optimization
- [ ] -- -- Sphere?

View File

@ -1,33 +1,5 @@
package tetra3d
// neighborSet represents a set of Sectors that are neighbors for any given Sector.
type neighborSet map[*Sector]bool
// Contains returns if the given sector is contained within the calling NeighborSet.
func (np neighborSet) Contains(sector *Sector) bool {
_, ok := np[sector]
return ok
}
// Combine combines the given other NeighborSet with the calling set.
func (np neighborSet) Combine(other neighborSet) {
for neighbor := range other {
np[neighbor] = true
}
}
// Remove removes the given sector from the NeighborSet.
func (np neighborSet) Remove(sector *Sector) {
delete(np, sector)
}
// Clear clears the NeighborSet.
func (np neighborSet) Clear() {
for v := range np {
delete(np, v)
}
}
type SectorDetectionType int
const (
@ -40,10 +12,12 @@ const (
// only the objects within the current sector and any neighboring sectors, up to a customizeable
// depth.
// A Sector is, spatially, an AABB, which sits next to or within other Sectors (AABBs).
// Logically, a Sector is determined to be a neighbor of another Sector if they either intersect,
// or share vertex positions. Which of these is the case depends on the Sectors' SectorDetectionType.
type Sector struct {
Model *Model // The owning Model that forms the Sector
AABB *BoundingAABB // The AABB used to search for neighbors if the SectorDetectionType is set to SectorDetectionTypeAABB
Neighbors neighborSet // The Sector's neighbors
Neighbors Set[*Sector] // The Sector's neighbors
SectorDetectionType SectorDetectionType // How the Sector is detected
sectorVisible bool
}
@ -59,7 +33,7 @@ func NewSector(model *Model) *Sector {
return &Sector{
Model: model,
AABB: sectorAABB,
Neighbors: neighborSet{},
Neighbors: newSet[*Sector](),
}
}
@ -70,10 +44,10 @@ func (sector *Sector) Clone() *Sector {
newSector := &Sector{
Model: sector.Model,
AABB: sector.AABB.Clone().(*BoundingAABB),
Neighbors: make(neighborSet, len(sector.Neighbors)),
Neighbors: make(Set[*Sector], len(sector.Neighbors)),
}
for n := range sector.Neighbors {
newSector.Neighbors[n] = true
newSector.Neighbors[n] = struct{}{}
}
return newSector
@ -113,8 +87,8 @@ func (sector *Sector) UpdateNeighbors(otherModels ...*Model) {
if transformedV.Equals(transformedV2) {
sector.Neighbors[otherModel.sector] = true
otherModel.sector.Neighbors[sector] = true
sector.Neighbors.Add(otherModel.sector)
otherModel.sector.Neighbors.Add(sector)
break exit
}
@ -122,15 +96,11 @@ func (sector *Sector) UpdateNeighbors(otherModels ...*Model) {
}
// sector.Model.Mesh.SelectVertices().SelectAll().ForEachIndex(func(index int) {
// })
case SectorDetectionTypeAABB:
if sector.AABB.Colliding(otherModel.sector.AABB) {
sector.Neighbors[otherModel.sector] = true
otherModel.sector.Neighbors[sector] = true
sector.Neighbors.Add(otherModel.sector)
otherModel.sector.Neighbors.Add(sector)
}
}
@ -145,9 +115,9 @@ func (sector *Sector) UpdateNeighbors(otherModels ...*Model) {
// # A - B - C - D - E - F
//
// If you were to check NeighborsWithinRange(2) from E, it would return F, D, and C.
func (sector *Sector) NeighborsWithinRange(searchRange int) neighborSet {
func (sector *Sector) NeighborsWithinRange(searchRange int) Set[*Sector] {
out := neighborSet{}
out := newSet[*Sector]()
if searchRange > 0 {
@ -159,6 +129,9 @@ func (sector *Sector) NeighborsWithinRange(searchRange int) neighborSet {
}
// The sector itself is not a neighbor of itself
out.Remove(sector)
return out
}

24
text.go
View File

@ -90,9 +90,8 @@ var textShaderSrc []byte
func NewText(meshPart *MeshPart, textureWidth int) *Text {
text := &Text{
meshPart: meshPart,
textureSize: textureWidth,
typewriterIndex: 0,
meshPart: meshPart,
textureSize: textureWidth,
}
// Calculate the width and height of the dimensions based off of the
@ -108,6 +107,10 @@ func NewText(meshPart *MeshPart, textureWidth int) *Text {
meshPart.Material = NewMaterial("text material")
meshPart.Material.BackfaceCulling = true
meshPart.Material.Shadeless = true
} else {
// We have to clone the material to ensure that unique objects that share the same material can both
// have their own Text textures.
meshPart.Material = meshPart.Material.Clone()
}
meshPart.Material.FragmentShaderOn = true
@ -284,6 +287,9 @@ func (textObj *Text) UpdateTexture() {
}
typewriterIndex := textObj.typewriterIndex
if !textObj.typewriterOn {
typewriterIndex = len(textObj.parsedText) - 1
}
textLineMargin := 2
lineHeight := int(float64(textObj.style.Font.Metrics().Height.Ceil()+textLineMargin) * textObj.style.LineHeightMultiplier)
@ -409,10 +415,8 @@ func (text *Text) TypewriterIndex() int {
func (text *Text) SetTypewriterIndex(typewriterIndex int) {
oldIndex := text.typewriterIndex
oldTypewriterOn := text.typewriterOn
text.typewriterIndex = typewriterIndex
text.typewriterOn = true
if text.typewriterIndex >= len(text.setText) {
text.typewriterIndex = len(text.setText)
@ -421,9 +425,10 @@ func (text *Text) SetTypewriterIndex(typewriterIndex int) {
text.typewriterIndex = 0
}
if oldTypewriterOn != text.typewriterOn || oldIndex != text.typewriterIndex {
if text.typewriterOn && oldIndex != text.typewriterIndex {
text.UpdateTexture()
}
}
// FinishTypewriter finishes the typewriter effect, so that the entire message is visible.
@ -454,6 +459,13 @@ func (text *Text) TypewriterFinished() bool {
return text.typewriterIndex >= len(text.setText)
}
func (text *Text) SetTypewriterOn(on bool) {
if text.typewriterOn != on {
text.UpdateTexture()
}
text.typewriterOn = on
}
// Dispose disposes of the text object's backing texture; this needs to be called to free VRAM, and should be called
// whenever the owning Model and Mesh are no longer is going to be used.
// This also will set the texture of the MeshPart this Text object is tied to, to nil.

View File

@ -159,3 +159,54 @@ func ExtendBase3DShader(customFragment string) (*ebiten.Shader, error) {
return ebiten.NewShader([]byte(shaderText))
}
// Set represents a Set of elements.
type Set[E comparable] map[E]struct{}
// newSet creates a new set.
func newSet[E comparable]() Set[E] {
return Set[E]{}
}
func (s Set[E]) Clone() Set[E] {
newSet := newSet[E]()
newSet.Combine(s)
return newSet
}
// Add adds the given elements to a set.
func (s Set[E]) Add(element E) {
s[element] = struct{}{}
}
// Combine combines the given other elements to the set.
func (s Set[E]) Combine(otherSet Set[E]) {
for element := range otherSet {
s.Add(element)
}
}
// Contains returns if the set contains the given element.
func (s Set[E]) Contains(element E) bool {
_, ok := s[element]
return ok
}
// Remove removes the given element from the set.
func (s Set[E]) Remove(element E) {
delete(s, element)
}
// Clear clears the set.
func (s Set[E]) Clear() {
for v := range s {
delete(s, v)
}
}
// ForEach runs the provided function for each element in the set.
func (s Set[E]) ForEach(f func(element E)) {
for element := range s {
f(element)
}
}