Skip to content

Commit

Permalink
Adding Camera.DrawImageIn3D.
Browse files Browse the repository at this point in the history
This function allows you to draw an *ebiten.Image on the destination image, but have it intersect with 3D objects as though it existed in 3D space.
Adding 3D Sprite example.
QoL: Removing vertex snapping to integer values when rendering.
FIX: CapsuleTriangle collision now uses the radius directly, as this handles varying collisions better.
QoL: Adding Camera.Size().
Simplifying Camera.WorldToScreen / Camera.WorldToClip.
Updating go.mod dependencies, gltf reader to no longer need to dereference the sampler input / output for read accesors.
QoL: Simplifying / adding named variables for returned variables in Matrix4.Decompose() for ease of understanding when reading the function documentation through autocomplete.
FIX: Billboarding now works properly for orthographic cameras.
FIX: Node.SetWorldZ now sets the proper axis (Z) of the Node's world position.
QoL: Adding resolution and animation playback FPS to Tetra3D addon.
  • Loading branch information
SolarLune committed Oct 28, 2022
1 parent 82d566c commit 44544ee
Show file tree
Hide file tree
Showing 15 changed files with 721 additions and 41 deletions.
6 changes: 3 additions & 3 deletions bounds.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ func btSphereTriangles(sphere *BoundingSphere, triangles *BoundingTriangles) *Co
invertedTransform := triTrans.Inverted()
transformNoLoc := triTrans.Clone()
transformNoLoc.SetRow(3, vector.Vector{0, 0, 0, 1})

sphereWorldPosition := sphere.WorldPosition()
spherePos := invertedTransform.MultVec(sphereWorldPosition)
sphereRadius := sphere.WorldRadius() * math.Abs(math.Max(invertedTransform[0][0], math.Max(invertedTransform[1][1], invertedTransform[2][2])))
Expand Down Expand Up @@ -680,7 +681,6 @@ func btCapsuleTriangles(capsule *BoundingCapsule, triangles *BoundingTriangles)
transformNoLoc.SetRow(3, vector.Vector{0, 0, 0, 1})

capsuleRadius := capsule.WorldRadius() * math.Abs(math.Max(invertedTransform[0][0], math.Max(invertedTransform[1][1], invertedTransform[2][2])))
capsuleRadiusSquared := math.Pow(capsuleRadius, 2)

capsuleTop := invertedTransform.MultVec(capsule.lineTop())
capsuleBottom := invertedTransform.MultVec(capsule.lineBottom())
Expand Down Expand Up @@ -739,13 +739,13 @@ func btCapsuleTriangles(capsule *BoundingCapsule, triangles *BoundingTriangles)

delta := fastVectorSub(spherePos, closest)

if mag := fastVectorMagnitudeSquared(delta); mag <= capsuleRadiusSquared {
if mag := delta.Magnitude(); mag <= capsuleRadius {

result.add(
&Intersection{
StartingPoint: closestCapsulePoint,
ContactPoint: triTrans.MultVec(closest),
MTV: transformNoLoc.MultVec(delta.Unit().Scale(capsuleRadiusSquared - mag)),
MTV: transformNoLoc.MultVec(delta.Unit().Scale(capsuleRadius - mag)),
Triangle: tri,
Normal: transformNoLoc.MultVec(tri.Normal).Unit(),
},
Expand Down
151 changes: 135 additions & 16 deletions camera.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Camera struct {
clipAlphaCompositeShader *ebiten.Shader
clipAlphaRenderShader *ebiten.Shader
colorShader *ebiten.Shader
sprite3DShader *ebiten.Shader

// Visibility check variables
cameraForward vector.Vector
Expand Down Expand Up @@ -245,6 +246,36 @@ func NewCamera(w, h int) *Camera {
panic(err)
}

sprite3DShaderText := []byte(
`package main
var SpriteDepth float
func decodeDepth(rgba vec4) float {
return rgba.r + (rgba.g / 255) + (rgba.b / 65025)
}
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
resultDepth := imageSrc1At(texCoord)
if resultDepth.a == 0 || decodeDepth(resultDepth) > SpriteDepth {
return imageSrc0At(texCoord)
}
discard()
}
`,
)

cam.sprite3DShader, err = ebiten.NewShader(sprite3DShaderText)

if err != nil {
panic(err)
}

if w != 0 && h != 0 {
cam.Resize(w, h)
}
Expand Down Expand Up @@ -307,6 +338,12 @@ func (camera *Camera) Resize(w, h int) {

}

// Size returns the width and height of the camera's backing color texture. All of the Camera's textures are the same size, so these
// same size values can also be used for the depth texture, the accumulation buffer, etc.
func (camera *Camera) Size() (w, h int) {
return camera.resultColorTexture.Size()
}

// ViewMatrix returns the Camera's view matrix.
func (camera *Camera) ViewMatrix() Matrix4 {

Expand Down Expand Up @@ -393,14 +430,14 @@ func (camera *Camera) ClipToScreen(vert vector.Vector) vector.Vector {

// WorldToScreen transforms a 3D position in the world to screen coordinates.
func (camera *Camera) WorldToScreen(vert vector.Vector) vector.Vector {
v := NewMatrix4Translate(vert[0], vert[1], vert[2]).Mult(camera.ViewMatrix().Mult(camera.Projection()))
return camera.ClipToScreen(v.MultVecW(vector.Vector{0, 0, 0}))
mat := camera.ViewMatrix().Mult(camera.Projection())
return camera.ClipToScreen(mat.MultVecW(vert))
}

// WorldToClip transforms a 3D position in the world to clip coordinates (before screen normalization).
func (camera *Camera) WorldToClip(vert vector.Vector) vector.Vector {
v := NewMatrix4Translate(vert[0], vert[1], vert[2]).Mult(camera.ViewMatrix().Mult(camera.Projection()))
return v.MultVecW(vector.Vector{0, 0, 0})
mat := camera.ViewMatrix().Mult(camera.Projection())
return mat.MultVecW(vert)
}

// PointInFrustum returns true if the point is visible through the camera frustum.
Expand Down Expand Up @@ -905,19 +942,19 @@ func (camera *Camera) Render(scene *Scene, models ...*Model) {
}
}

colorVertexList[vertexListIndex].DstX = float32(int(p0[0]))
colorVertexList[vertexListIndex].DstY = float32(int(p0[1]))
colorVertexList[vertexListIndex+1].DstX = float32(int(p1[0]))
colorVertexList[vertexListIndex+1].DstY = float32(int(p1[1]))
colorVertexList[vertexListIndex+2].DstX = float32(int(p2[0]))
colorVertexList[vertexListIndex+2].DstY = float32(int(p2[1]))
colorVertexList[vertexListIndex].DstX = float32(p0[0])
colorVertexList[vertexListIndex].DstY = float32(p0[1])
colorVertexList[vertexListIndex+1].DstX = float32(p1[0])
colorVertexList[vertexListIndex+1].DstY = float32(p1[1])
colorVertexList[vertexListIndex+2].DstX = float32(p2[0])
colorVertexList[vertexListIndex+2].DstY = float32(p2[1])

depthVertexList[vertexListIndex].DstX = float32(int(p0[0]))
depthVertexList[vertexListIndex].DstY = float32(int(p0[1]))
depthVertexList[vertexListIndex+1].DstX = float32(int(p1[0]))
depthVertexList[vertexListIndex+1].DstY = float32(int(p1[1]))
depthVertexList[vertexListIndex+2].DstX = float32(int(p2[0]))
depthVertexList[vertexListIndex+2].DstY = float32(int(p2[1]))
depthVertexList[vertexListIndex].DstX = float32(p0[0])
depthVertexList[vertexListIndex].DstY = float32(p0[1])
depthVertexList[vertexListIndex+1].DstX = float32(p1[0])
depthVertexList[vertexListIndex+1].DstY = float32(p1[1])
depthVertexList[vertexListIndex+2].DstX = float32(p2[0])
depthVertexList[vertexListIndex+2].DstY = float32(p2[1])

meshPart.sortingTriangles[t].rendered = true

Expand Down Expand Up @@ -1275,6 +1312,88 @@ func (camera *Camera) Render(scene *Scene, models ...*Model) {

}

func encodeDepth(depth float64) *Color {

r := math.Floor(depth*255) / 255
_, f := math.Modf(depth * 255)
g := math.Floor(f*255) / 255
_, f = math.Modf(depth * 255 * 255)
b := f

if r < 0 {
r = 0
} else if r > 1 {
r = 1
}
if g < 0 {
g = 0
} else if g > 1 {
g = 1
}
if b < 0 {
b = 0
} else if b > 1 {
b = 1
}

return NewColor(float32(r), float32(g), float32(b), 1)

}

type SpriteRender3d struct {
Image *ebiten.Image
Options *ebiten.DrawImageOptions
WorldPosition vector.Vector
}

// DrawImageIn3D 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) DrawImageIn3D(screen *ebiten.Image, renderSettings ...SpriteRender3d) {

// TODO: Replace this with a more performant alternative, where we minimize shader / texture switches.

for _, rs := range renderSettings {

camera.colorIntermediate.Clear()

px := camera.WorldToScreen(rs.WorldPosition)

_, _, d, _ := fastMatrixMultVecW(camera.ViewMatrix().Mult(camera.Projection()), rs.WorldPosition)

depth := float32(d / camera.Far)

if depth < 0 {
depth = 0
}
if depth > 1 {
depth = 1
}

opt := rs.Options

if opt == nil {
opt = &ebiten.DrawImageOptions{}
}
opt.GeoM.Translate(px[0], px[1])

camera.colorIntermediate.DrawImage(rs.Image, opt)

colorTextureW, colorTextureH := camera.resultColorTexture.Size()
rectShaderOptions := &ebiten.DrawRectShaderOptions{}
rectShaderOptions.Images[0] = camera.colorIntermediate
rectShaderOptions.Images[1] = camera.resultDepthTexture
rectShaderOptions.Uniforms = map[string]interface{}{
"SpriteDepth": depth,
}
screen.DrawRectShader(colorTextureW, colorTextureH, camera.sprite3DShader, rectShaderOptions)

}

}

func (camera *Camera) drawCircle(screen *ebiten.Image, position vector.Vector, radius float64, drawColor color.Color) {

transformedCenter := camera.WorldToScreen(position)
Expand Down
Binary file added examples/3dSprite/heart.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 44544ee

Please sign in to comment.