Implement 3D rendering

This commit is contained in:
LeshaInc
2023-06-14 14:36:30 +03:00
parent 5e334af482
commit bac0d2b5ff
23 changed files with 551 additions and 45 deletions

View File

@@ -0,0 +1,25 @@
#version 140
varying vec4 fColor;
varying vec3 fNormal;
varying vec2 fUV;
uniform vec3 uLightDir;
uniform vec3 uLightColor;
uniform vec3 uAmbientLightColor;
uniform sampler2D uTexture;
void main() {
vec3 normal = normalize(fNormal);
vec4 texColor = texture2D(uTexture, fUV);
vec3 albedo = vec3(1.0, 0.0, 0.0);
float alpha = 1.0;
vec3 light = uAmbientLightColor;
light += clamp(dot(uLightDir, normal), 0.0, 1.0) * uLightColor;
vec3 color = light * albedo;
gl_FragColor = vec4(color, alpha);
}

View File

@@ -0,0 +1,30 @@
#version 140
attribute vec3 inPos;
attribute vec3 inNormal;
attribute vec2 inUV;
attribute vec4 inColor;
attribute vec4 inTransform0;
attribute vec4 inTransform1;
attribute vec4 inTransform2;
attribute vec3 inUVTransform0;
attribute vec3 inUVTransform1;
varying vec4 fColor;
varying vec3 fNormal;
varying vec2 fUV;
uniform mat4 uView;
uniform mat4 uProj;
void main() {
mat4 transform = transpose(mat4(inTransform0, inTransform1, inTransform2, vec4(0, 0, 0, 1)));
fNormal = mat3(transpose(inverse(transform))) * inNormal;
gl_Position = uProj * uView * transform * vec4(inPos, 1);
mat3 uvTransform = transpose(mat3(inUVTransform0, inUVTransform1, vec3(0, 0, 1)));
fUV = (uvTransform * vec3(inUV, 1)).xy;
fColor = inColor;
}

View File

@@ -1,5 +1,7 @@
package ocelot.desktop.color
import ocelot.desktop.geometry.Vector3D
case class RGBAColorNorm(r: Float, g: Float, b: Float, a: Float = 1f) extends Color {
assert(0 <= r && r <= 1.0f, "Invalid RED channel")
assert(0 <= g && g <= 1.0f, "Invalid GREEN channel")
@@ -8,6 +10,8 @@ case class RGBAColorNorm(r: Float, g: Float, b: Float, a: Float = 1f) extends Co
def components: Array[Float] = Array(r, g, b, a)
def rgbVector: Vector3D = Vector3D(r, g, b)
def mapA(f: Float => Float): RGBAColorNorm = copy(a = f(a))
override def toInt: IntColor = toRGBA.toInt

View File

@@ -10,6 +10,13 @@ object Basis3D {
def scale(v: Vector3D): Basis3D = Basis3D(Vector3D(v.x, 0, 0), Vector3D(0, v.y, 0), Vector3D(0, 0, v.z))
def scale(s: Float): Basis3D = Basis3D(Vector3D(s, 0, 0), Vector3D(0, s, 0), Vector3D(0, 0, s))
def lookingAt(target: Vector3D, up: Vector3D = Vector3D.Up): Basis3D = {
val v_z = target.normalize
val v_x = up.cross(v_z).normalize
val v_y = v_z.cross(v_x)
Basis3D(v_x, v_y, v_z)
}
}
case class Basis3D(x: Vector3D, y: Vector3D, z: Vector3D) {
@@ -67,5 +74,17 @@ case class Basis3D(x: Vector3D, y: Vector3D, z: Vector3D) {
)
}
def up: Vector3D = this * Vector3D.Up
def down: Vector3D = this * Vector3D.Down
def left: Vector3D = this * Vector3D.Left
def right: Vector3D = this * Vector3D.Right
def forward: Vector3D = this * Vector3D.Forward
def back: Vector3D = this * Vector3D.Back
override def toString: String = s"Basis3D [$x, $y, $z]"
}

View File

@@ -0,0 +1,34 @@
package ocelot.desktop.geometry
object Box3D {
def fromMinMax(min: Vector3D, max: Vector3D): Box3D = {
val center = (min + max) * 0.5f
val halfExtents = (max - min) * 0.5f
Box3D(center, halfExtents)
}
def fromVertices(vertices: Iterator[Vector3D]): Box3D = {
val first = vertices.next()
val (min, max) = vertices.foldLeft((first, first)) { case ((min, max), vertex) =>
(min.min(vertex), max.max(vertex))
}
Box3D.fromMinMax(min, max)
}
}
case class Box3D(center: Vector3D, halfExtents: Vector3D) {
def vertices: Seq[Vector3D] = Seq(
center + halfExtents * Vector3D(-1, -1, -1),
center + halfExtents * Vector3D(-1, -1, 1),
center + halfExtents * Vector3D(-1, 1, -1),
center + halfExtents * Vector3D(-1, 1, 1),
center + halfExtents * Vector3D(1, -1, -1),
center + halfExtents * Vector3D(1, -1, 1),
center + halfExtents * Vector3D(1, 1, -1),
center + halfExtents * Vector3D(1, 1, 1),
)
def transform(transform: Transform3D): Box3D = {
Box3D.fromVertices(vertices.iterator.map(transform * _))
}
}

View File

@@ -2,7 +2,7 @@ package ocelot.desktop.geometry
object ProjectionMatrix3D {
def perspective(aspect: Float, fovY: Float, zNear: Float, zFar: Float): ProjectionMatrix3D = {
val f = (1.0 / math.tan(fovY / 2.0)).toFloat
val f = (1.0 / math.tan(math.toRadians(fovY) / 2.0)).toFloat
ProjectionMatrix3D(
f / aspect, 0, 0, 0,
0, f, 0, 0,

View File

@@ -14,6 +14,11 @@ object Transform3D {
def scale(s: Float): Transform3D = Transform3D(Basis3D.scale(s))
def translate(v: Vector3D): Transform3D = Transform3D(Basis3D.identity, v)
def lookingAt(origin: Vector3D, target: Vector3D, up: Vector3D = Vector3D.Up): Transform3D = {
val basis = Basis3D.lookingAt(origin - target, up)
Transform3D(basis, origin)
}
}
case class Transform3D(basis: Basis3D, origin: Vector3D) {

View File

@@ -2,10 +2,20 @@ package ocelot.desktop.geometry
object Vector3D {
val Zero: Vector3D = Vector3D(0, 0, 0)
val AxisX: Vector3D = Vector3D(1, 0, 0)
val AxisY: Vector3D = Vector3D(0, 1, 0)
val AxisZ: Vector3D = Vector3D(0, 0, 1)
val Up: Vector3D = AxisY
val Down: Vector3D = -AxisY
val Left: Vector3D = -AxisX
val Right: Vector3D = AxisX
val Forward: Vector3D = -AxisZ
val Back: Vector3D = AxisZ
def apply(x: Double, y: Double, z: Double): Vector3D = Vector3D(x.toFloat, y.toFloat, z.toFloat)
}
@@ -26,6 +36,12 @@ case class Vector3D(x: Float, y: Float, z: Float) {
def dot(that: Vector3D): Float = x * that.x + y * that.y + z * that.z
def cross(that: Vector3D): Vector3D = Vector3D(
y * that.z - z * that.y,
z * that.x - x * that.z,
x * that.y - y * that.x
)
def length: Float = math.sqrt(lengthSquared).toFloat
def lengthSquared: Float = x * x + y * y + z * z

View File

@@ -21,7 +21,7 @@ class Graphics(private var scalingFactor: Float) extends Logging with Resource {
private var height = 600
private val shaderProgram = new ShaderProgram("general")
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, shaderProgram)
private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)
private val normalFont = new Font("unscii-16", 16)
private val smallFont = new Font("unscii-8", 8)
@@ -39,7 +39,9 @@ class Graphics(private var scalingFactor: Float) extends Logging with Resource {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, offscreenFramebuffer)
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D,
offscreenTexture.texture, 0)
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)
private var currentFramebuffer = 0
private val currentScissor: Option[Rect2D] = None
shaderProgram.set("uTexture", 0)
shaderProgram.set("uTextTexture", 1)
@@ -62,10 +64,10 @@ class Graphics(private var scalingFactor: Float) extends Logging with Resource {
offscreenTexture.bind()
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, width, height, 0, GL11.GL_RGB,
GL11.GL_UNSIGNED_BYTE, null.asInstanceOf[ByteBuffer])
GL11.glViewport(0, 0, width, height)
viewportChanged = true
}
viewportChanged
}
@@ -97,9 +99,6 @@ class Graphics(private var scalingFactor: Float) extends Logging with Resource {
_font = smallFont
}
GL11.glEnable(GL11.GL_BLEND)
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA)
def save(): Unit = {
stack.push(stack.head.copy())
}
@@ -126,47 +125,42 @@ class Graphics(private var scalingFactor: Float) extends Logging with Resource {
GL30.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0)
GL30.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, offscreenFramebuffer)
GL30.glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST)
currentFramebuffer = offscreenFramebuffer
}
def endGroupAlpha(alpha: Float): Unit = {
flush()
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)
offscreenTexture.bind()
currentFramebuffer = 0
foreground = RGBAColorNorm(1, 1, 1, alpha)
spriteRect = Rect2D(0, 1f, 1f, -1f)
_rect(0, 0, width / scalingFactor, height / scalingFactor, fixUV = false)
renderer.flush()
flush(mainTexture = offscreenTexture)
}
def startViewport(): Unit = {
def blitViewport3D(viewport: Viewport3D, bounds: Rect2D, alpha: Float = 1.0f): Unit = {
flush()
foreground = RGBAColorNorm(1, 1, 1, alpha)
spriteRect = Rect2D(0, 1f, 1f, -1f)
_rect(bounds.x, bounds.y, bounds.w, bounds.h, fixUV = false)
flush(mainTexture = viewport.textureColor)
}
def begin(): Unit = {
shaderProgram.set("uProj", Transform2D.viewport(width, height))
}
def clear(): Unit = {
flush()
GL11.glClearColor(0.95f, 0.95f, 0.95f, 1.0f)
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT)
flush(clear = true)
}
def setScissor(x: Float, y: Float, width: Float, height: Float): Unit = {
flush()
stack.head.scissor = Some((x, y, width * scalingFactor, height * scalingFactor))
// TODO: make them aware of the transform stack
GL11.glScissor(
Math.round(x * scalingFactor),
Math.round(this.height - height * scalingFactor - y * scalingFactor),
Math.round(width * scalingFactor),
Math.round(height * scalingFactor)
)
GL11.glEnable(GL11.GL_SCISSOR_TEST)
}
def setScissor(): Unit = {
flush()
stack.head.scissor = None
GL11.glDisable(GL11.GL_SCISSOR_TEST)
}
def clear(x: Int, y: Int, width: Int, height: Int): Unit = {
@@ -373,10 +367,37 @@ class Graphics(private var scalingFactor: Float) extends Logging with Resource {
line(start.x, start.y, end.x, end.y, thickness, color)
}
def flush(): Unit = {
Spritesheet.texture.bind()
def flush(mainTexture: Texture = Spritesheet.texture, clear: Boolean = false): Unit = {
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, currentFramebuffer)
GL11.glViewport(0, 0, width, height)
GL11.glDisable(GL11.GL_DEPTH_TEST)
GL11.glDisable(GL11.GL_CULL_FACE)
GL11.glEnable(GL11.GL_BLEND)
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA)
stack.head.scissor match {
case Some((x, y, w, h)) =>
GL11.glEnable(GL11.GL_SCISSOR_TEST)
GL11.glScissor(
Math.round(x * scalingFactor),
Math.round(height - h * scalingFactor - y * scalingFactor),
Math.round(w * scalingFactor),
Math.round(h * scalingFactor)
)
case _ =>
GL11.glDisable(GL11.GL_SCISSOR_TEST)
}
mainTexture.bind()
_font.texture.bind(1)
renderer.flush()
if (clear) {
GL11.glClearColor(1, 1, 1, 1)
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT)
}
}
def update(): Unit = {

View File

@@ -1,6 +1,6 @@
package ocelot.desktop.graphics
import ocelot.desktop.geometry.Transform2D
import ocelot.desktop.geometry.{ProjectionMatrix3D, Transform2D, Transform3D, Vector3D}
import ocelot.desktop.util.{Logging, Resource}
import org.lwjgl.BufferUtils
import org.lwjgl.opengl.GL20
@@ -51,6 +51,11 @@ class ShaderProgram(name: String) extends Logging with Resource {
GL20.glUniform1i(getLocation(name), texture.texture)
}
def set(name: String, vector: Vector3D): Unit = {
bind()
GL20.glUniform3f(getLocation(name), vector.x, vector.y, vector.z)
}
def set(name: String, transform: Transform2D): Unit = {
bind()
@@ -61,6 +66,25 @@ class ShaderProgram(name: String) extends Logging with Resource {
GL20.glUniformMatrix3(getLocation(name), true, buffer)
}
def set(name: String, transform: Transform3D): Unit = {
bind()
val array = transform.array ++ Array(0f, 0f, 0f, 1f)
val buffer = BufferUtils.createFloatBuffer(array.length)
buffer.put(array)
buffer.flip()
GL20.glUniformMatrix4(getLocation(name), true, buffer)
}
def set(name: String, projection: ProjectionMatrix3D): Unit = {
bind()
val buffer = BufferUtils.createFloatBuffer(projection.array.length)
buffer.put(projection.array)
buffer.flip()
GL20.glUniformMatrix4(getLocation(name), true, buffer)
}
def set(name: String, v: Int): Unit = {
bind()
GL20.glUniform1i(getLocation(name), v)

View File

@@ -1,9 +1,15 @@
package ocelot.desktop.graphics
import ocelot.desktop.graphics.mesh.{Mesh3D, MeshInstance3D, MeshVertex3D}
import ocelot.desktop.graphics.render.InstanceRenderer
import ocelot.desktop.graphics.scene.{Scene3D, SceneMesh3D}
import ocelot.desktop.util.Spritesheet
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL30}
import scala.collection.mutable
class Viewport3D(val width: Int, var height: Int) {
private val textureColor = new Texture(width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE)
private[graphics] val textureColor = new Texture(width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE)
private val textureDepth = new Texture(width, height, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT)
private val framebuffer = ARBFramebufferObject.glGenFramebuffers()
@@ -11,4 +17,117 @@ class Viewport3D(val width: Int, var height: Int) {
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, textureColor.texture, 0)
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, textureDepth.texture, 0)
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)
private type Instancer = InstanceRenderer[MeshVertex3D, MeshInstance3D]
private val shaderProgram: ShaderProgram = new ShaderProgram("general_3d")
shaderProgram.set("uTexture", 0)
private val opaqueInstancers = new mutable.HashMap[Mesh3D, Instancer]()
private val transparentInstancers = new mutable.HashMap[Mesh3D, Instancer]()
private val transparentNodes = new mutable.ArrayBuffer[SceneMesh3D]()
def draw(scene: Scene3D): Unit = {
setupCamera(scene)
setupLight(scene)
collectOpaque(scene)
collectTransparent(scene)
sortOpaque(scene)
sortTransparent(scene)
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer)
GL11.glViewport(0, 0, width, height)
GL11.glDisable(GL11.GL_SCISSOR_TEST)
GL11.glEnable(GL11.GL_DEPTH_TEST)
GL11.glEnable(GL11.GL_CULL_FACE)
GL11.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
GL11.glClearDepth(1.0f)
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT)
Spritesheet.texture.bind()
drawOpaque()
drawTransparent()
}
private def setupCamera(scene: Scene3D): Unit = {
shaderProgram.set("uView", scene.camera.transform.inverse)
shaderProgram.set("uProj", scene.camera.projectionMatrix(width.toFloat / height.toFloat))
}
private def setupLight(scene: Scene3D): Unit = {
shaderProgram.set("uLightDir", scene.lightSource.basis.up)
shaderProgram.set("uLightColor", scene.lightSource.color.toRGBANorm.rgbVector * scene.lightSource.energy)
shaderProgram.set("uAmbientLightColor", scene.ambientLightColor.toRGBANorm.rgbVector * scene.ambientLightEnergy)
}
private def collectOpaque(scene: Scene3D): Unit = {
for (node <- scene.meshes.iterator.filter(_.isOpaque)) {
// TODO: frustum culling
val instancer = getOpaqueInstancer(node.mesh)
instancer.schedule(nodeToInstance(node))
}
}
private def collectTransparent(scene: Scene3D): Unit = {
transparentNodes.clear()
for (node <- scene.meshes.iterator.filter(!_.isOpaque)) {
transparentNodes.append(node)
}
}
private def sortOpaque(scene: Scene3D): Unit = {
for ((mesh, instancer) <- opaqueInstancers) {
val meshCenter = mesh.boundingBox.center
instancer.sort((a, b) => {
val aDist = scene.camera.distanceTo(a.transform * meshCenter)
val bDist = scene.camera.distanceTo(b.transform * meshCenter)
aDist < bDist // closest first
})
}
}
private def sortTransparent(scene: Scene3D): Unit = {
transparentNodes.sortWith((a, b) => {
val aDist = scene.camera.distanceTo(a.transform * a.mesh.boundingBox.center)
val bDist = scene.camera.distanceTo(b.transform * b.mesh.boundingBox.center)
a.priority < b.priority || aDist > bDist // farthest first
})
}
private def drawOpaque(): Unit = {
opaqueInstancers.values.foreach(_.flush())
}
private def drawTransparent(): Unit = {
var i = 0
while (i < transparentNodes.length) {
val node = transparentNodes(i)
i += 1
val instancer = getTransparentInstancer(node.mesh)
instancer.schedule(nodeToInstance(node))
while (i < transparentNodes.length && transparentNodes(i).mesh == node.mesh) {
val similarNode = transparentNodes(i)
instancer.schedule(nodeToInstance(similarNode))
i += 1
}
instancer.flush()
}
}
private def getOpaqueInstancer(mesh: Mesh3D): Instancer = {
opaqueInstancers.getOrElseUpdate(mesh, new Instancer(mesh, MeshInstance3D, shaderProgram))
}
private def getTransparentInstancer(mesh: Mesh3D): Instancer = {
transparentInstancers.getOrElseUpdate(mesh, new Instancer(mesh, MeshInstance3D, shaderProgram))
}
private def nodeToInstance(node: SceneMesh3D): MeshInstance3D = {
MeshInstance3D(node.color, node.transform, node.uvTransform)
}
}

View File

@@ -1,5 +1,68 @@
package ocelot.desktop.graphics.mesh
import ocelot.desktop.geometry.{Box3D, Vector2D, Vector3D}
import ocelot.desktop.graphics.buffer.Index
case class Mesh3D(vertices: Seq[MeshVertex3D], indices: Option[Seq[Index]] = None) extends Mesh[MeshVertex3D]
object Mesh3D {
val Cube: Mesh3D = {
val builder = new MeshBuilder3D
// TODO: uv coords
// left
builder.quad(
Vector3D(-1.0, -1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(-1.0, -1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(-1.0, +1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(-1.0, +1.0, -1.0), Vector2D(0.0, 0.0),
)
// right
builder.quad(
Vector3D(+1.0, -1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, +1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, +1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, -1.0, +1.0), Vector2D(0.0, 0.0),
)
// front
builder.quad(
Vector3D(-1.0, -1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(-1.0, +1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, +1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, -1.0, -1.0), Vector2D(0.0, 0.0),
)
// back
builder.quad(
Vector3D(-1.0, -1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, -1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, +1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(-1.0, +1.0, +1.0), Vector2D(0.0, 0.0),
)
// up
builder.quad(
Vector3D(-1.0, +1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(-1.0, +1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, +1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, +1.0, -1.0), Vector2D(0.0, 0.0),
)
// down
builder.quad(
Vector3D(-1.0, -1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, -1.0, -1.0), Vector2D(0.0, 0.0),
Vector3D(+1.0, -1.0, +1.0), Vector2D(0.0, 0.0),
Vector3D(-1.0, -1.0, +1.0), Vector2D(0.0, 0.0),
)
builder.build()
}
}
case class Mesh3D(vertices: Seq[MeshVertex3D], indices: Option[Seq[Index]] = None) extends Mesh[MeshVertex3D] {
val boundingBox: Box3D = {
Box3D.fromVertices(vertices.iterator.map(_.pos))
}
}

View File

@@ -0,0 +1,51 @@
package ocelot.desktop.graphics.mesh
import ocelot.desktop.geometry.{Vector2D, Vector3D}
import ocelot.desktop.graphics.buffer.Index
import scala.collection.mutable.ArrayBuffer
class MeshBuilder3D {
private val vertices = new ArrayBuffer[MeshVertex3D]()
private val indices = new ArrayBuffer[Index]()
def vertex(pos: Vector3D, normal: Vector3D, uv: Vector2D): Index = {
vertices += MeshVertex3D(pos, normal, uv)
Index(vertices.length - 1)
}
def triangleIndices(a: Index, b: Index, c: Index): Unit = {
indices += a
indices += b
indices += c
}
def triangle(a: Vector3D, aUV: Vector2D,
b: Vector3D, bUV: Vector2D,
c: Vector3D, cUV: Vector2D): Unit =
{
val normal = (b - a).cross(c - a).normalize
val aIdx = vertex(a, normal, aUV)
val bIdx = vertex(b, normal, bUV)
val cIdx = vertex(c, normal, cUV)
triangleIndices(aIdx, bIdx, cIdx)
}
def quad(a: Vector3D, aUV: Vector2D,
b: Vector3D, bUV: Vector2D,
c: Vector3D, cUV: Vector2D,
d: Vector3D, dUV: Vector2D): Unit =
{
val normal = (b - a).cross(c - a).normalize
val aIdx = vertex(a, normal, aUV)
val bIdx = vertex(b, normal, bUV)
val cIdx = vertex(c, normal, cUV)
val dIdx = vertex(d, normal, dUV)
triangleIndices(aIdx, bIdx, cIdx)
triangleIndices(aIdx, cIdx, dIdx)
}
def build(): Mesh3D = {
Mesh3D(vertices.toSeq, indices = Some(indices.toSeq))
}
}

View File

@@ -19,7 +19,7 @@ object MeshInstance3D extends VertexType {
)
}
class MeshInstance3D(color: Color, transform: Transform3D, uvTransform: Transform2D) extends Vertex {
case class MeshInstance3D(color: Color, transform: Transform3D, uvTransform: Transform2D) extends Vertex {
override def vertexType: VertexType = MeshInstance3D
override def stride: Int = MeshInstance3D.stride

View File

@@ -2,7 +2,7 @@ package ocelot.desktop.graphics.render
import ocelot.desktop.graphics.ShaderProgram
import ocelot.desktop.graphics.buffer.{IndexBuffer, VertexBuffer}
import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance2D, Vertex, VertexArray}
import ocelot.desktop.graphics.mesh.{Mesh, Vertex, VertexArray, VertexType}
import ocelot.desktop.util.{Logging, Resource}
import org.lwjgl.BufferUtils
import org.lwjgl.opengl._
@@ -10,12 +10,12 @@ import org.lwjgl.opengl._
import java.nio.ByteBuffer
import scala.collection.mutable.ArrayBuffer
class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], shader: ShaderProgram) extends Resource with Logging {
class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], instanceType: VertexType, shader: ShaderProgram) extends Resource with Logging {
private val InitialCapacity: Int = 4096
private val vertexBuffer = new VertexBuffer[V](mesh.vertices)
private val indexBuffer: Option[IndexBuffer] = mesh.indices.map(indices => new IndexBuffer(indices))
private val instanceBuffer = new VertexBuffer[I](MeshInstance2D, InitialCapacity)
private val instanceBuffer = new VertexBuffer[I](instanceType, InitialCapacity)
private val vertexArray = new VertexArray(shader)
@@ -29,6 +29,10 @@ class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], shader: ShaderPr
instances += instance
}
def sort(ltComparator: (I, I) => Boolean): Unit = {
instances.sortInPlaceWith(ltComparator)
}
def flush(): Unit = {
write()
draw()
@@ -48,10 +52,10 @@ class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], shader: ShaderPr
indexBuffer match {
case Some(ib) =>
val method = classOf[ARBDrawInstanced].getDeclaredMethod("nglDrawElementsInstancedARBBO",
Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, java.lang.Long.TYPE)
val method = classOf[ARBDrawInstanced].getDeclaredMethod("nglDrawElementsInstancedARB",
Integer.TYPE, Integer.TYPE, Integer.TYPE, java.lang.Long.TYPE, Integer.TYPE, java.lang.Long.TYPE)
method.setAccessible(true)
method.invoke(null, GL11.GL_TRIANGLES, ib.capacity, GL11.GL_UNSIGNED_INT, 0, instances.length, glDrawElementsInstancedARBptr)
method.invoke(null, GL11.GL_TRIANGLES, ib.capacity, GL11.GL_UNSIGNED_INT, 0L, instances.length, glDrawElementsInstancedARBptr)
case None =>
val method = classOf[ARBDrawInstanced].getDeclaredMethod("nglDrawArraysInstancedARB",
Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, java.lang.Long.TYPE)

View File

@@ -1,10 +1,14 @@
package ocelot.desktop.graphics.scene
import ocelot.desktop.geometry.ProjectionMatrix3D
import ocelot.desktop.geometry.{ProjectionMatrix3D, Vector3D}
class Camera3D(var zNear: Float = 0.01f,
var zFar: Float = 1000.0f,
var fovY: Float = 80.0f) extends SceneNode3D
{
def distanceTo(point: Vector3D): Float = {
(origin - point).length
}
def projectionMatrix(aspect: Float): ProjectionMatrix3D = ProjectionMatrix3D.perspective(aspect, fovY, zNear, zFar)
}

View File

@@ -3,4 +3,4 @@ package ocelot.desktop.graphics.scene
import ocelot.desktop.color.Color
class DirectionalLight3D(var color: Color = Color.White,
var strength: Float = 1) extends SceneNode3D
var energy: Float = 1) extends SceneNode3D

View File

@@ -1,10 +1,13 @@
package ocelot.desktop.graphics.scene
import ocelot.desktop.color.Color
import scala.collection.mutable.ArrayBuffer
class Scene3D {
var camera: Camera3D = new Camera3D
var lightSource: DirectionalLight3D = new DirectionalLight3D
var ambientLight: Float = 0.1f
var ambientLightEnergy: Float = 0.3f
var ambientLightColor: Color = Color.White
val meshes: ArrayBuffer[SceneMesh3D] = new ArrayBuffer[SceneMesh3D]()
}

View File

@@ -1,8 +1,11 @@
package ocelot.desktop.graphics.scene
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.Transform2D
import ocelot.desktop.graphics.mesh.Mesh3D
class SceneMesh3D(var mesh: Mesh3D,
var color: Color = Color.White,
var isOpaque: Boolean = true) extends SceneNode3D
var uvTransform: Transform2D = Transform2D.identity,
var isOpaque: Boolean = true,
var priority: Int = 0) extends SceneNode3D

View File

@@ -1,7 +1,15 @@
package ocelot.desktop.graphics.scene
import ocelot.desktop.geometry.Transform3D
import ocelot.desktop.geometry.{Basis3D, Transform3D, Vector3D}
trait SceneNode3D {
var transform: Transform3D = Transform3D.identity
final def origin: Vector3D = transform.origin
final def origin_=(new_origin: Vector3D): Unit = transform = Transform3D(basis, new_origin)
final def basis: Basis3D = transform.basis
final def basis_=(new_basis: Basis3D): Unit = transform = Transform3D(new_basis, origin)
}

View File

@@ -195,7 +195,7 @@ object UiHandler extends Logging {
logger.info("VSync disabled (via config)")
}
Display.create()
Display.create(new PixelFormat, new ContextAttribs(3, 2))
if (Settings.get.windowValidatePosition) {
fixInsaneInitialWindowGeometry()
@@ -421,7 +421,7 @@ object UiHandler extends Logging {
}
private def draw(): Unit = {
graphics.startViewport()
graphics.begin()
graphics.clear()
root.draw(graphics)

View File

@@ -11,11 +11,12 @@ import ocelot.desktop.ui.layout.{CopyLayout, Layout}
import ocelot.desktop.ui.widget.window.{NodeSelector, ProfilerWindow, WindowPool}
import ocelot.desktop.util.DrawUtils
import ocelot.desktop.util.animation.ValueAnimation
import ocelot.desktop.windows.Viewport3DWindow
import ocelot.desktop.{ColorScheme, OcelotDesktop, Settings}
import org.lwjgl.input.Keyboard
import totoro.ocelot.brain.entity.traits.{Entity, Environment, SidedEnvironment}
import totoro.ocelot.brain.entity.{Case, Screen}
import totoro.ocelot.brain.event.{EventBus, InventoryEntityAddedEvent, InventoryEvent, NodeEvent}
import totoro.ocelot.brain.event.{EventBus, InventoryEvent, NodeEvent}
import totoro.ocelot.brain.nbt.ExtendedNBT._
import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound}
import totoro.ocelot.brain.util.{Direction, Tier}
@@ -30,6 +31,7 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
var windowPool = new WindowPool
var nodeSelector = new NodeSelector
var profilerWindow = new ProfilerWindow
val viewport3DWindow = new Viewport3DWindow
var cameraOffset: Vector2D = Vector2D(0, 0)
var newConnection: Option[(Node, NodePort, Vector2D)] = None
@@ -205,6 +207,9 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover
case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_F4, _) =>
windowPool.openWindow(profilerWindow)
case KeyEvent(KeyEvent.State.Press, Keyboard.KEY_F8, _) =>
windowPool.openWindow(viewport3DWindow)
}
private def openSelector(p: Vector2D): Unit = {

View File

@@ -0,0 +1,68 @@
package ocelot.desktop.windows
import ocelot.desktop.geometry._
import ocelot.desktop.graphics.mesh.Mesh3D
import ocelot.desktop.graphics.scene.{Scene3D, SceneMesh3D}
import ocelot.desktop.graphics.{Graphics, Viewport3D}
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.layout.{Layout, LinearLayout}
import ocelot.desktop.ui.widget.window.BasicWindow
import ocelot.desktop.ui.widget.{Label, PaddingBox, Widget}
import ocelot.desktop.util.{DrawUtils, Orientation}
class Viewport3DWindow extends BasicWindow {
private lazy val viewport = new Viewport3D(600, 400)
children :+= new PaddingBox(new Widget {
override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical)
children :+= new PaddingBox(new Label {
override def text: String = "3D Viewport"
override val isSmall: Boolean = true
}, Padding2D.equal(4))
children :+= new PaddingBox(new Widget {
override def minimumSize: Size2D = Size2D(600, 400)
override def maximumSize: Size2D = Size2D(600, 400)
override def draw(g: Graphics): Unit = {
g.blitViewport3D(viewport, bounds)
}
}, Padding2D(left = 2, right = 2, bottom = 2))
}, Padding2D.equal(8))
override def draw(g: Graphics): Unit = {
viewport.draw(createScene())
beginDraw(g)
DrawUtils.windowWithShadow(g, position.x, position.y, size.width, size.height, 1f, 0.5f)
drawChildren(g)
endDraw(g)
}
private var time = 0.0f
private def createScene(): Scene3D = {
val scene = new Scene3D
scene.camera.transform = Transform3D.lookingAt(Vector3D(math.cos(time).toFloat * 10f, 5, math.sin(time).toFloat * 10f), Vector3D.Zero)
scene.lightSource.basis = Basis3D.rotate(Vector3D.AxisZ, 30f.toRadians) * Basis3D.rotate(Vector3D.AxisX, 30f.toRadians)
for (sx <- -10 to 10) {
for (sy <- -10 to 10) {
scene.meshes += {
val mesh = new SceneMesh3D(Mesh3D.Cube)
mesh.origin = Vector3D(sx * 3, 0.0, sy * 3)
mesh
}
}
}
time += UiHandler.dt
scene
}
}