mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2026-03-25 08:52:47 +01:00
Implement 3D rendering
This commit is contained in:
25
src/main/resources/ocelot/desktop/shader/general_3d.frag
Normal file
25
src/main/resources/ocelot/desktop/shader/general_3d.frag
Normal 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);
|
||||
}
|
||||
30
src/main/resources/ocelot/desktop/shader/general_3d.vert
Normal file
30
src/main/resources/ocelot/desktop/shader/general_3d.vert
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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]"
|
||||
}
|
||||
|
||||
34
src/main/scala/ocelot/desktop/geometry/Box3D.scala
Normal file
34
src/main/scala/ocelot/desktop/geometry/Box3D.scala
Normal 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 * _))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
68
src/main/scala/ocelot/desktop/windows/Viewport3DWindow.scala
Normal file
68
src/main/scala/ocelot/desktop/windows/Viewport3DWindow.scala
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user