mirror of
https://gitlab.com/cc-ru/ocelot/ocelot-desktop.git
synced 2025-12-20 02:59:19 +01:00
Initial commit
This commit is contained in:
commit
cd50101510
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# SBT
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
/.gradle
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Eclipse
|
||||||
|
/.classpath
|
||||||
|
/.project
|
||||||
|
/.settings
|
||||||
|
/bin
|
||||||
|
|
||||||
|
# IntelliJ IDEA
|
||||||
|
/*.iml
|
||||||
|
/*.ipr
|
||||||
|
/*.iws
|
||||||
|
/.idea
|
||||||
|
/out
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
/eclipse
|
||||||
|
/run
|
||||||
|
|
||||||
|
# Windows...
|
||||||
|
**/Thumbs.db
|
||||||
|
|
||||||
|
# OS X
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
# Local proxy settings
|
||||||
|
cacerts
|
||||||
|
|
||||||
|
# Temp files
|
||||||
|
/*.dll
|
||||||
|
/*.so
|
||||||
|
/*.dylib
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "lib/ocelot-brain"]
|
||||||
|
path = lib/ocelot-brain
|
||||||
|
url = https://gitlab.com/cc-ru/ocelot/ocelot-brain
|
||||||
30
build.sbt
Normal file
30
build.sbt
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name := "ocelot-desktop"
|
||||||
|
version := "0.1"
|
||||||
|
scalaVersion := "2.12.8"
|
||||||
|
|
||||||
|
lazy val root = project.in(file("."))
|
||||||
|
.dependsOn(brain % "compile->compile")
|
||||||
|
.aggregate(brain)
|
||||||
|
|
||||||
|
lazy val brain = ProjectRef(file("lib/ocelot-brain"), "ocelot-brain")
|
||||||
|
|
||||||
|
libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.11.1"
|
||||||
|
libraryDependencies += "org.apache.logging.log4j" % "log4j-api" % "2.11.1"
|
||||||
|
libraryDependencies += "org.apache.logging.log4j" %% "log4j-api-scala" % "11.0"
|
||||||
|
|
||||||
|
val lwjglVersion = "3.2.1"
|
||||||
|
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl" % lwjglVersion
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl" % lwjglVersion classifier "natives-linux"
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl" % lwjglVersion classifier "natives-windows"
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl-glfw" % lwjglVersion
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl-glfw" % lwjglVersion classifier "natives-linux"
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl-glfw" % lwjglVersion classifier "natives-windows"
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl-opengl" % lwjglVersion
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl-opengl" % lwjglVersion classifier "natives-linux"
|
||||||
|
libraryDependencies += "org.lwjgl" % "lwjgl-opengl" % lwjglVersion classifier "natives-windows"
|
||||||
|
|
||||||
|
assemblyMergeStrategy in assembly := {
|
||||||
|
case PathList("META-INF", "MANIFEST.MF") => MergeStrategy.discard
|
||||||
|
case _ => MergeStrategy.first
|
||||||
|
}
|
||||||
1
lib/ocelot-brain
Submodule
1
lib/ocelot-brain
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 13c7448fadb34c76e1ae06ef75ad44efd785829a
|
||||||
1
project/assembly.sbt
Normal file
1
project/assembly.sbt
Normal file
@ -0,0 +1 @@
|
|||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
|
||||||
1
project/build.properties
Normal file
1
project/build.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
sbt.version = 1.2.7
|
||||||
17
src/main/resources/log4j2.xml
Normal file
17
src/main/resources/log4j2.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="INFO">
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
|
||||||
|
</Console>
|
||||||
|
<File name="MyFile" fileName="ocelot-desktop.log" immediateFlush="false" append="false">
|
||||||
|
<PatternLayout pattern="%d{yyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||||
|
</File>
|
||||||
|
</Appenders>
|
||||||
|
<Loggers>
|
||||||
|
<Root level="debug">
|
||||||
|
<AppenderRef ref="Console" />
|
||||||
|
<AppenderRef ref="MyFile"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
</Configuration>
|
||||||
8942
src/main/resources/ocelot/desktop/font.hex
Normal file
8942
src/main/resources/ocelot/desktop/font.hex
Normal file
File diff suppressed because it is too large
Load Diff
13
src/main/resources/ocelot/desktop/shader/general.frag
Normal file
13
src/main/resources/ocelot/desktop/shader/general.frag
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#version 140
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
in vec4 fColor;
|
||||||
|
in vec2 fUV;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
outColor = fColor;
|
||||||
|
}
|
||||||
30
src/main/resources/ocelot/desktop/shader/general.vert
Normal file
30
src/main/resources/ocelot/desktop/shader/general.vert
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#version 140
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 inPos;
|
||||||
|
layout(location = 1) in vec2 inUV;
|
||||||
|
|
||||||
|
layout(location = 2) in vec4 inColor;
|
||||||
|
layout(location = 3) in vec3 inTransform0;
|
||||||
|
layout(location = 4) in vec3 inTransform1;
|
||||||
|
layout(location = 5) in vec3 inUVTransform0;
|
||||||
|
layout(location = 6) in vec3 inUVTransform1;
|
||||||
|
|
||||||
|
layout(location = 7) in float inZ;
|
||||||
|
|
||||||
|
out vec4 fColor;
|
||||||
|
out vec2 fUV;
|
||||||
|
|
||||||
|
uniform mat3 uProj;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fColor = inColor;
|
||||||
|
|
||||||
|
mat3 modelMatrix = transpose(mat3(inTransform0, inTransform1, vec3(0.0, 0.0, 1.0)));
|
||||||
|
vec3 transformed = uProj * modelMatrix * vec3(inPos, 1.0);
|
||||||
|
|
||||||
|
gl_Position = vec4(transformed.xy, 1.0 - inZ / 8000000.0, 1.0);
|
||||||
|
|
||||||
|
mat3 uvMatrix = transpose(mat3(inUVTransform0, inUVTransform1, vec3(0.0, 0.0, 1.0)));
|
||||||
|
fUV = (uvMatrix * vec3(inUV, 1.0)).xy;
|
||||||
|
}
|
||||||
14
src/main/resources/ocelot/desktop/shader/text.frag
Normal file
14
src/main/resources/ocelot/desktop/shader/text.frag
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#version 140
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
in vec4 fColor;
|
||||||
|
in vec2 fUV;
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float alpha = texture(uTexture, fUV).r;
|
||||||
|
outColor = vec4(fColor.rgb, fColor.a * alpha);
|
||||||
|
}
|
||||||
30
src/main/resources/ocelot/desktop/shader/text.vert
Normal file
30
src/main/resources/ocelot/desktop/shader/text.vert
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#version 140
|
||||||
|
#extension GL_ARB_separate_shader_objects : enable
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 inPos;
|
||||||
|
layout(location = 1) in vec2 inUV;
|
||||||
|
|
||||||
|
layout(location = 2) in vec4 inColor;
|
||||||
|
layout(location = 3) in vec3 inTransform0;
|
||||||
|
layout(location = 4) in vec3 inTransform1;
|
||||||
|
layout(location = 5) in vec3 inUVTransform0;
|
||||||
|
layout(location = 6) in vec3 inUVTransform1;
|
||||||
|
|
||||||
|
layout(location = 7) in float inZ;
|
||||||
|
|
||||||
|
out vec4 fColor;
|
||||||
|
out vec2 fUV;
|
||||||
|
|
||||||
|
uniform mat3 uProj;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fColor = inColor;
|
||||||
|
|
||||||
|
mat3 modelMatrix = transpose(mat3(inTransform0, inTransform1, vec3(0.0, 0.0, 1.0)));
|
||||||
|
vec3 transformed = uProj * modelMatrix * vec3(inPos, 1.0);
|
||||||
|
|
||||||
|
gl_Position = vec4(transformed.xy, 1.0 - inZ / 8000000.0, 1.0);
|
||||||
|
|
||||||
|
mat3 uvMatrix = transpose(mat3(inUVTransform0, inUVTransform1, vec3(0.0, 0.0, 1.0)));
|
||||||
|
fUV = (uvMatrix * vec3(inUV, 1.0)).xy;
|
||||||
|
}
|
||||||
256
src/main/scala/ocelot/desktop/OcelotDesktop.scala
Normal file
256
src/main/scala/ocelot/desktop/OcelotDesktop.scala
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
package ocelot.desktop
|
||||||
|
|
||||||
|
import java.io.{PrintWriter, StringWriter}
|
||||||
|
|
||||||
|
import ocelot.desktop.graphics.Graphics
|
||||||
|
import ocelot.desktop.util.{FontLoader, KeyMapping, ResourceManager}
|
||||||
|
import ocelot.desktop.widget.ScreenWidget
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.Version
|
||||||
|
import org.lwjgl.glfw.{GLFW, GLFWErrorCallback}
|
||||||
|
import org.lwjgl.opengl._
|
||||||
|
import org.lwjgl.system.MemoryUtil
|
||||||
|
import org.lwjgl.system.MemoryUtil.NULL
|
||||||
|
import totoro.ocelot.brain.Ocelot
|
||||||
|
import totoro.ocelot.brain.entity.{APU, Cable, Case, Keyboard, Memory, Screen}
|
||||||
|
import totoro.ocelot.brain.event._
|
||||||
|
import totoro.ocelot.brain.loot.Loot
|
||||||
|
import totoro.ocelot.brain.network.Network
|
||||||
|
import totoro.ocelot.brain.user.User
|
||||||
|
import totoro.ocelot.brain.util.Tier
|
||||||
|
|
||||||
|
import scala.collection.mutable.ArrayBuffer
|
||||||
|
|
||||||
|
|
||||||
|
object OcelotDesktop extends Logging {
|
||||||
|
private var window: Long = _
|
||||||
|
|
||||||
|
var windowWidth: Int = 0
|
||||||
|
var windowHeight: Int = 0
|
||||||
|
|
||||||
|
var graphics: Graphics = _
|
||||||
|
var redraw = true
|
||||||
|
|
||||||
|
def mainInner(): Unit = {
|
||||||
|
logger.info("Starting up Ocelot Desktop")
|
||||||
|
logger.info(s"Using LWJGL ${Version.getVersion}")
|
||||||
|
|
||||||
|
init()
|
||||||
|
loop()
|
||||||
|
|
||||||
|
logger.info("Cleaning up")
|
||||||
|
|
||||||
|
ResourceManager.freeResources()
|
||||||
|
|
||||||
|
GLFW.glfwTerminate()
|
||||||
|
GLFW.glfwSetErrorCallback(null).free()
|
||||||
|
|
||||||
|
logger.info("Thanks for using Ocelot Desktop")
|
||||||
|
}
|
||||||
|
|
||||||
|
def main(args: Array[String]): Unit = {
|
||||||
|
try mainInner()
|
||||||
|
catch {
|
||||||
|
case e: Exception =>
|
||||||
|
val sw = new StringWriter
|
||||||
|
val pw = new PrintWriter(sw)
|
||||||
|
|
||||||
|
e.printStackTrace(pw)
|
||||||
|
|
||||||
|
logger.error(s"${sw.toString}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def init(): Unit = {
|
||||||
|
GLFWErrorCallback.createPrint(System.err).set
|
||||||
|
|
||||||
|
if (!GLFW.glfwInit()) throw new IllegalStateException("Unable to initialize GLFW")
|
||||||
|
|
||||||
|
GLFW.glfwDefaultWindowHints()
|
||||||
|
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE)
|
||||||
|
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, GLFW.GLFW_FALSE)
|
||||||
|
GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, 16)
|
||||||
|
|
||||||
|
window = GLFW.glfwCreateWindow(640, 400, "Ocelot Desktop", NULL, NULL)
|
||||||
|
if (window == NULL) throw new RuntimeException("Failed to create GLFW window")
|
||||||
|
|
||||||
|
windowWidth = 640
|
||||||
|
windowHeight = 400
|
||||||
|
|
||||||
|
GLFW.glfwMakeContextCurrent(window)
|
||||||
|
GL.createCapabilities()
|
||||||
|
|
||||||
|
GLFW.glfwSetKeyCallback(window, handleKey)
|
||||||
|
GLFW.glfwSetCharCallback(window, handleChar)
|
||||||
|
GLFW.glfwSetWindowSizeCallback(window, handleResize)
|
||||||
|
GLFW.glfwSetWindowRefreshCallback(window, handleRefresh)
|
||||||
|
|
||||||
|
GLFW.glfwSetInputMode(window, GLFW.GLFW_STICKY_KEYS, GLFW.GLFW_FALSE)
|
||||||
|
|
||||||
|
GLFW.glfwSwapInterval(1)
|
||||||
|
|
||||||
|
GLFW.glfwShowWindow(window)
|
||||||
|
|
||||||
|
GL11.glEnable(GL13.GL_MULTISAMPLE)
|
||||||
|
|
||||||
|
val s = GL11.glGetInteger(GL13.GL_SAMPLES)
|
||||||
|
logger.info(s"Created window with $s MSAA samples")
|
||||||
|
|
||||||
|
logger.info("OpenGL vendor: " + GL11.glGetString(GL11.GL_VENDOR))
|
||||||
|
logger.info("OpenGL renderer: " + GL11.glGetString(GL11.GL_RENDERER))
|
||||||
|
logger.info("OpenGL extensions: " + GL11.glGetString(GL11.GL_EXTENSIONS))
|
||||||
|
|
||||||
|
// GL11.glEnable(GL43.GL_DEBUG_OUTPUT)
|
||||||
|
// GL43.glDebugMessageCallback((_: Int, _: Int, _: Int, _: Int, length: Int, messagePtr: Long, _: Long) => {
|
||||||
|
// val message = MemoryUtil.memUTF8(MemoryUtil.memByteBuffer(messagePtr, length))
|
||||||
|
// logger.info(s"OpenGL: $message")
|
||||||
|
// }, 0)
|
||||||
|
|
||||||
|
FontLoader.init()
|
||||||
|
Ocelot.initialize(LogManager.getLogger(Ocelot))
|
||||||
|
|
||||||
|
createWorkspace()
|
||||||
|
|
||||||
|
graphics = new Graphics
|
||||||
|
}
|
||||||
|
|
||||||
|
var computer: Case = _
|
||||||
|
var screen: Screen = _
|
||||||
|
|
||||||
|
private def createWorkspace(): Unit = {
|
||||||
|
val network = new Network()
|
||||||
|
|
||||||
|
val cable = new Cable()
|
||||||
|
|
||||||
|
network.connect(cable)
|
||||||
|
|
||||||
|
computer = new Case(Tier.Four)
|
||||||
|
|
||||||
|
cable.connect(computer)
|
||||||
|
|
||||||
|
val cpu = new APU(Tier.Two)
|
||||||
|
computer.add(cpu)
|
||||||
|
|
||||||
|
val memory = new Memory(Tier.Six)
|
||||||
|
computer.add(memory)
|
||||||
|
|
||||||
|
computer.add(Loot.AdvLoaderEEPROM.create())
|
||||||
|
computer.add(Loot.OpenOsFloppy.create())
|
||||||
|
|
||||||
|
screen = new Screen(Tier.Three)
|
||||||
|
cable.connect(screen)
|
||||||
|
|
||||||
|
val keyboard = new Keyboard
|
||||||
|
screen.connect(keyboard)
|
||||||
|
|
||||||
|
EventBus.listenTo(classOf[BeepEvent], { case event: BeepEvent =>
|
||||||
|
println(s"[EVENT] Beep (address = ${event.address}, frequency = ${event.frequency}, duration = ${event.duration})")
|
||||||
|
})
|
||||||
|
EventBus.listenTo(classOf[BeepPatternEvent], { case event: BeepPatternEvent =>
|
||||||
|
println(s"[EVENT] Beep (address = ${event.address}, pattern = ${event.pattern})")
|
||||||
|
})
|
||||||
|
EventBus.listenTo(classOf[MachineCrashEvent], { case event: MachineCrashEvent =>
|
||||||
|
println(s"[EVENT] Machine crash! (address = ${event.address}, ${event.message})")
|
||||||
|
})
|
||||||
|
|
||||||
|
EventBus.listenTo(classOf[TextBufferSetEvent], { case event: TextBufferSetEvent =>
|
||||||
|
screenWidget.set(event.x, event.y, event.value, event.vertical)
|
||||||
|
})
|
||||||
|
|
||||||
|
EventBus.listenTo(classOf[TextBufferFillEvent], { case event: TextBufferFillEvent =>
|
||||||
|
screenWidget.fill(event.x, event.y, event.width, event.height, event.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
EventBus.listenTo(classOf[TextBufferCopyEvent], { case event: TextBufferCopyEvent =>
|
||||||
|
screenWidget.copy(event.x, event.y, event.width, event.height, event.horizontalTranslation, event.verticalTranslation)
|
||||||
|
})
|
||||||
|
|
||||||
|
EventBus.listenTo(classOf[TextBufferSetForegroundColorEvent], { case event: TextBufferSetForegroundColorEvent =>
|
||||||
|
screenWidget.foreground = event.color
|
||||||
|
})
|
||||||
|
EventBus.listenTo(classOf[TextBufferSetBackgroundColorEvent], { case event: TextBufferSetBackgroundColorEvent =>
|
||||||
|
screenWidget.background = event.color
|
||||||
|
})
|
||||||
|
|
||||||
|
computer.turnOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
val screenWidget = new ScreenWidget
|
||||||
|
|
||||||
|
private var lastUpdate = System.currentTimeMillis()
|
||||||
|
|
||||||
|
private def draw(): Unit = {
|
||||||
|
val time = System.currentTimeMillis()
|
||||||
|
if (time - lastUpdate >= 50) {
|
||||||
|
computer.update()
|
||||||
|
lastUpdate = time
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.clear()
|
||||||
|
|
||||||
|
screenWidget.draw(graphics)
|
||||||
|
|
||||||
|
graphics.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private def handleRefresh(window: Long): Unit = {
|
||||||
|
redraw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private def handleResize(window: Long, width: Int, height: Int): Unit = {
|
||||||
|
GL11.glViewport(0, 0, width, height)
|
||||||
|
windowWidth = width
|
||||||
|
windowHeight = height
|
||||||
|
}
|
||||||
|
|
||||||
|
case class KeyAction(var action: Int, var code: Int, var char: Char)
|
||||||
|
|
||||||
|
val keyActions: ArrayBuffer[KeyAction] = ArrayBuffer()
|
||||||
|
|
||||||
|
private def handleKey(window: Long, key: Int, scancode: Int, action: Int, mods: Int): Unit = {
|
||||||
|
keyActions += KeyAction(action, KeyMapping.map(key), '\0')
|
||||||
|
}
|
||||||
|
|
||||||
|
private def handleChar(window: Long, codepoint: Int): Unit = {
|
||||||
|
keyActions.head.char = codepoint.asInstanceOf[Char]
|
||||||
|
}
|
||||||
|
|
||||||
|
private def loop(): Unit = {
|
||||||
|
var prevTime = System.currentTimeMillis()
|
||||||
|
var numFrames = 0
|
||||||
|
|
||||||
|
while (!GLFW.glfwWindowShouldClose(window)) {
|
||||||
|
graphics.setViewport(windowWidth, windowHeight)
|
||||||
|
|
||||||
|
if (redraw) {
|
||||||
|
draw()
|
||||||
|
redraw = true // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFW.glfwSwapBuffers(window)
|
||||||
|
GLFW.glfwPollEvents()
|
||||||
|
|
||||||
|
for (action <- keyActions) {
|
||||||
|
if (action.action == GLFW.GLFW_PRESS || action.action == GLFW.GLFW_REPEAT)
|
||||||
|
screen.keyDown(action.char, action.code, User("myself"))
|
||||||
|
else {
|
||||||
|
screen.keyUp(action.char, action.code, User("myself"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyActions.clear()
|
||||||
|
|
||||||
|
numFrames += 1
|
||||||
|
|
||||||
|
val currTime = System.currentTimeMillis()
|
||||||
|
val dt = currTime - prevTime
|
||||||
|
|
||||||
|
if (dt > 5000) {
|
||||||
|
prevTime = currTime
|
||||||
|
logger.info(s"FPS: ${numFrames.asInstanceOf[Float] / (dt.asInstanceOf[Float] / 1000.0)}")
|
||||||
|
numFrames = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/scala/ocelot/desktop/color/Color.scala
Normal file
7
src/main/scala/ocelot/desktop/color/Color.scala
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package ocelot.desktop.color
|
||||||
|
|
||||||
|
trait Color {
|
||||||
|
def toRGBA: RGBAColor
|
||||||
|
|
||||||
|
def toRGBANorm: RGBAColorNorm
|
||||||
|
}
|
||||||
13
src/main/scala/ocelot/desktop/color/IntColor.scala
Normal file
13
src/main/scala/ocelot/desktop/color/IntColor.scala
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ocelot.desktop.color
|
||||||
|
|
||||||
|
case class IntColor(color: Int) extends Color {
|
||||||
|
override def toRGBA: RGBAColor = {
|
||||||
|
RGBAColor(
|
||||||
|
(color >> 16).asInstanceOf[Short],
|
||||||
|
((color >> 8) & 0xFF).asInstanceOf[Short],
|
||||||
|
(color & 0xFF).asInstanceOf[Short], 255
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toRGBANorm: RGBAColorNorm = toRGBA.toRGBANorm
|
||||||
|
}
|
||||||
12
src/main/scala/ocelot/desktop/color/RGBAColor.scala
Normal file
12
src/main/scala/ocelot/desktop/color/RGBAColor.scala
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package ocelot.desktop.color
|
||||||
|
|
||||||
|
case class RGBAColor(r: Short, g: Short, b: Short, a: Short) extends Color {
|
||||||
|
assert(0 <= r && r <= 255, "Invalid RED channel")
|
||||||
|
assert(0 <= g && g <= 255, "Invalid GREEN channel")
|
||||||
|
assert(0 <= b && b <= 255, "Invalid BLUE channel")
|
||||||
|
assert(0 <= a && a <= 255, "Invalid ALPHA channel")
|
||||||
|
|
||||||
|
override def toRGBA: RGBAColor = this
|
||||||
|
|
||||||
|
override def toRGBANorm: RGBAColorNorm = RGBAColorNorm(r.asInstanceOf[Float] / 255f, g.asInstanceOf[Float] / 255f, b.asInstanceOf[Float] / 255f, a.asInstanceOf[Float] / 255f)
|
||||||
|
}
|
||||||
14
src/main/scala/ocelot/desktop/color/RGBAColorNorm.scala
Normal file
14
src/main/scala/ocelot/desktop/color/RGBAColorNorm.scala
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package ocelot.desktop.color
|
||||||
|
|
||||||
|
case class RGBAColorNorm(r: Float, g: Float, b: Float, a: Float) extends Color {
|
||||||
|
assert(0 <= r && r <= 1.0f, "Invalid RED channel")
|
||||||
|
assert(0 <= g && g <= 1.0f, "Invalid GREEN channel")
|
||||||
|
assert(0 <= b && b <= 1.0f, "Invalid BLUE channel")
|
||||||
|
assert(0 <= a && a <= 1.0f, "Invalid ALPHA channel")
|
||||||
|
|
||||||
|
def array: Array[Float] = Array(r, g, b, a)
|
||||||
|
|
||||||
|
override def toRGBA: RGBAColor = RGBAColor((r * 255).asInstanceOf, (g * 255).asInstanceOf, (b * 255).asInstanceOf, (a * 255).asInstanceOf)
|
||||||
|
|
||||||
|
override def toRGBANorm: RGBAColorNorm = this
|
||||||
|
}
|
||||||
58
src/main/scala/ocelot/desktop/geometry/Transform2D.scala
Normal file
58
src/main/scala/ocelot/desktop/geometry/Transform2D.scala
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package ocelot.desktop.geometry
|
||||||
|
|
||||||
|
object Transform2D {
|
||||||
|
def identity: Transform2D = Transform2D(
|
||||||
|
1, 0, 0,
|
||||||
|
0, 1, 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def scale(x: Float, y: Float): Transform2D = Transform2D(
|
||||||
|
x, 0, 0,
|
||||||
|
0, y, 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def scale(a: Float): Transform2D = Transform2D.scale(a, a)
|
||||||
|
|
||||||
|
def translate(x: Float, y: Float): Transform2D = Transform2D(
|
||||||
|
1, 0, x,
|
||||||
|
0, 1, y
|
||||||
|
)
|
||||||
|
|
||||||
|
def viewport(width: Float, height: Float): Transform2D =
|
||||||
|
Transform2D.translate(-1f, 1f) >> Transform2D.scale(2f / width, -2f / height)
|
||||||
|
|
||||||
|
def rotate(angle: Float): Transform2D = {
|
||||||
|
val (s, c) = (math.sin(angle).asInstanceOf[Float], math.cos(angle).asInstanceOf[Float])
|
||||||
|
|
||||||
|
Transform2D(
|
||||||
|
c, -s, 0,
|
||||||
|
s, c, 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Transform2D(m11: Float, m12: Float, m13: Float, m21: Float, m22: Float, m23: Float) {
|
||||||
|
def array: Array[Float] = Array(m11, m12, m13, m21, m22, m23)
|
||||||
|
|
||||||
|
// :|
|
||||||
|
def >>(that: Transform2D): Transform2D = Transform2D(
|
||||||
|
m11 * that.m11 + m12 * that.m21, m11 * that.m12 + m12 * that.m22, m11 * that.m13 + m12 * that.m23 + m13,
|
||||||
|
m21 * that.m11 + m22 * that.m21, m21 * that.m12 + m22 * that.m22, m21 * that.m13 + m22 * that.m23 + m23,
|
||||||
|
)
|
||||||
|
|
||||||
|
// (●__●)
|
||||||
|
def <<(that: Transform2D): Transform2D = Transform2D(
|
||||||
|
m11 * that.m11 + m21 * that.m12, m12 * that.m11 + m22 * that.m12, m13 * that.m11 + m23 * that.m12 + that.m13,
|
||||||
|
m11 * that.m21 + m21 * that.m22, m12 * that.m21 + m22 * that.m22, m13 * that.m21 + m23 * that.m22 + that.m23
|
||||||
|
)
|
||||||
|
|
||||||
|
def *(that: Vector2D): Vector2D = Vector2D(
|
||||||
|
m11 * that.x + m12 * that.y + m13,
|
||||||
|
m21 * that.x + m22 * that.y + m23
|
||||||
|
)
|
||||||
|
|
||||||
|
override def toString: String =
|
||||||
|
f"""Matrix2D [$m11%6.3f $m12%6.3f $m13%6.3f]
|
||||||
|
| [$m21%6.3f $m22%6.3f $m23%6.3f]
|
||||||
|
""".stripMargin
|
||||||
|
}
|
||||||
70
src/main/scala/ocelot/desktop/geometry/Vector2D.scala
Normal file
70
src/main/scala/ocelot/desktop/geometry/Vector2D.scala
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package ocelot.desktop.geometry
|
||||||
|
|
||||||
|
object Vector2D {
|
||||||
|
def apply(x: Double, y: Double): Vector2D = Vector2D(x.asInstanceOf[Float], y.asInstanceOf[Float])
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Vector2D(var x: Float, var y: Float) {
|
||||||
|
def +(that: Vector2D): Vector2D = Vector2D(x + that.x, y + that.y)
|
||||||
|
|
||||||
|
def +=(that: Vector2D): Unit = {
|
||||||
|
x += that.x
|
||||||
|
y += that.y
|
||||||
|
}
|
||||||
|
|
||||||
|
def -(that: Vector2D): Vector2D = Vector2D(x - that.x, y - that.y)
|
||||||
|
|
||||||
|
def -=(that: Vector2D): Unit = {
|
||||||
|
x -= that.x
|
||||||
|
y -= that.y
|
||||||
|
}
|
||||||
|
|
||||||
|
def *(scalar: Float): Vector2D = Vector2D(x * scalar, y * scalar)
|
||||||
|
|
||||||
|
def *=(scalar: Float): Unit = {
|
||||||
|
x *= scalar
|
||||||
|
y *= scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
def /(scalar: Float): Vector2D = Vector2D(x / scalar, y / scalar)
|
||||||
|
|
||||||
|
def /=(scalar: Float): Unit = {
|
||||||
|
x /= scalar
|
||||||
|
y /= scalar
|
||||||
|
}
|
||||||
|
|
||||||
|
def unary_-(): Vector2D = Vector2D(-x, -y)
|
||||||
|
|
||||||
|
def *(that: Vector2D): Float = x * that.x + y * that.y
|
||||||
|
|
||||||
|
def length: Float = math.sqrt(lengthSquared).asInstanceOf
|
||||||
|
|
||||||
|
def lengthSquared: Float = this.x * this.x + this.y * this.y
|
||||||
|
|
||||||
|
def normalized: Vector2D = {
|
||||||
|
val l = this.length
|
||||||
|
if (l > 0) this / this.length else this
|
||||||
|
}
|
||||||
|
|
||||||
|
def normalize(): Unit = {
|
||||||
|
val l = this.length
|
||||||
|
if (l > 0) this /= this.length
|
||||||
|
}
|
||||||
|
|
||||||
|
def angle: Float = math.atan2(this.y, this.x).asInstanceOf
|
||||||
|
|
||||||
|
def angle(that: Vector2D): Float = angle - that.angle
|
||||||
|
|
||||||
|
def rotated(angle: Float): Vector2D = {
|
||||||
|
val (s, c) = (math.sin(angle), math.cos(angle))
|
||||||
|
Vector2D(c * x - s * y, s * x + c * y)
|
||||||
|
}
|
||||||
|
|
||||||
|
def rotate(angle: Float): Unit = {
|
||||||
|
val (s, c) = (math.sin(angle), math.cos(angle))
|
||||||
|
x = (c * x - s * y).asInstanceOf
|
||||||
|
y = (s * x + c * y).asInstanceOf
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"Vector2D [$x, $y]"
|
||||||
|
}
|
||||||
134
src/main/scala/ocelot/desktop/graphics/Graphics.scala
Normal file
134
src/main/scala/ocelot/desktop/graphics/Graphics.scala
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package ocelot.desktop.graphics
|
||||||
|
|
||||||
|
import ocelot.desktop.color.{Color, RGBAColorNorm}
|
||||||
|
import ocelot.desktop.geometry.Transform2D
|
||||||
|
import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance}
|
||||||
|
import ocelot.desktop.graphics.render.InstanceRenderer
|
||||||
|
import ocelot.desktop.util.FontLoader
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.opengl.GL11
|
||||||
|
|
||||||
|
class Graphics extends Logging {
|
||||||
|
private var projection = Transform2D.viewport(800, 600)
|
||||||
|
private var z = 0f
|
||||||
|
|
||||||
|
private val generalShaderProgram = new ShaderProgram("general")
|
||||||
|
private val rectRenderer = new InstanceRenderer(Mesh.quad)
|
||||||
|
|
||||||
|
private val textShaderProgram = new ShaderProgram("text")
|
||||||
|
private val textRenderer = new InstanceRenderer(Mesh.quad)
|
||||||
|
|
||||||
|
private val fontAtlas = FontLoader.makeTexture()
|
||||||
|
|
||||||
|
private var _foreground = RGBAColorNorm(0f, 0f, 0f, 1f)
|
||||||
|
private var _background = RGBAColorNorm(0f, 0f, 0f, 1f)
|
||||||
|
private var _fontSize: Float = 16f
|
||||||
|
|
||||||
|
GL11.glEnable(GL11.GL_DEPTH_TEST)
|
||||||
|
GL11.glDepthFunc(GL11.GL_LEQUAL)
|
||||||
|
GL11.glEnable(GL11.GL_BLEND)
|
||||||
|
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA)
|
||||||
|
|
||||||
|
def setViewport(width: Int, height: Int): Unit = {
|
||||||
|
projection = Transform2D.viewport(width, height)
|
||||||
|
|
||||||
|
generalShaderProgram.set("uProj", projection)
|
||||||
|
textShaderProgram.set("uProj", projection)
|
||||||
|
}
|
||||||
|
|
||||||
|
def clear(): Unit = {
|
||||||
|
GL11.glClearColor(0.95f, 0.95f, 0.95f, 1.0f)
|
||||||
|
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setScissor(x: Int, y: Int, width: Int, height: Int): Unit = {
|
||||||
|
GL11.glScissor(x, y, width, height)
|
||||||
|
GL11.glEnable(GL11.GL_SCISSOR_TEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setScissor(): Unit = {
|
||||||
|
GL11.glDisable(GL11.GL_SCISSOR_TEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
def clear(x: Int, y: Int, width: Int, height: Int): Unit = {
|
||||||
|
setScissor(x, y, width, height)
|
||||||
|
clear()
|
||||||
|
setScissor()
|
||||||
|
}
|
||||||
|
|
||||||
|
def foreground: RGBAColorNorm = _foreground
|
||||||
|
|
||||||
|
def foreground_=(col: Color): Unit = {
|
||||||
|
_foreground = col.toRGBANorm
|
||||||
|
}
|
||||||
|
|
||||||
|
def background: RGBAColorNorm = _background
|
||||||
|
|
||||||
|
def background_=(col: Color): Unit = {
|
||||||
|
_background = col.toRGBANorm
|
||||||
|
}
|
||||||
|
|
||||||
|
def fontSize: Float = _fontSize
|
||||||
|
|
||||||
|
def fontSize_=(value: Float): Unit = {
|
||||||
|
_fontSize = value
|
||||||
|
}
|
||||||
|
|
||||||
|
def text(x: Float, y: Float, text: String): Unit = {
|
||||||
|
var ox = x
|
||||||
|
|
||||||
|
for (c <- text) {
|
||||||
|
char(ox, y, c)
|
||||||
|
|
||||||
|
if (FontLoader.isCharWide(c))
|
||||||
|
ox += fontSize
|
||||||
|
else
|
||||||
|
ox += fontSize / 2f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def char(x: Float, y: Float, c: Char): Unit = {
|
||||||
|
val rect = FontLoader.map.getOrElse(c, FontLoader.map('?'))
|
||||||
|
|
||||||
|
val width = if (FontLoader.isCharWide(c)) fontSize else fontSize / 2f
|
||||||
|
val height = fontSize
|
||||||
|
|
||||||
|
val uvTransform = Transform2D.translate(
|
||||||
|
rect.x,
|
||||||
|
rect.y
|
||||||
|
) >> Transform2D.scale(
|
||||||
|
rect.w,
|
||||||
|
rect.h - 0.3f / FontLoader.AtlasHeight.asInstanceOf[Float]
|
||||||
|
// ^ dirty hack to avoid edge bleeding, somehow works
|
||||||
|
)
|
||||||
|
|
||||||
|
rectRenderer.schedule(MeshInstance(_background, z, Transform2D.translate(x, y) >> Transform2D.scale(width, height)))
|
||||||
|
textRenderer.schedule(MeshInstance(_foreground, z + 1, Transform2D.translate(x, y) >> Transform2D.scale(width, height), uvTransform))
|
||||||
|
|
||||||
|
z += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
def rect(x: Float, y: Float, width: Float, height: Float): Unit = {
|
||||||
|
rectRenderer.schedule(MeshInstance(_background, z, Transform2D.translate(x, y) >> Transform2D.scale(width, height)))
|
||||||
|
z += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def flush(): Unit = {
|
||||||
|
generalShaderProgram.bind()
|
||||||
|
rectRenderer.flush()
|
||||||
|
|
||||||
|
textShaderProgram.bind()
|
||||||
|
fontAtlas.bind()
|
||||||
|
textRenderer.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
def commit(): Unit = {
|
||||||
|
flush()
|
||||||
|
|
||||||
|
rectRenderer.commit()
|
||||||
|
textRenderer.commit()
|
||||||
|
|
||||||
|
z = 0
|
||||||
|
setScissor()
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/main/scala/ocelot/desktop/graphics/ShaderProgram.scala
Normal file
81
src/main/scala/ocelot/desktop/graphics/ShaderProgram.scala
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package ocelot.desktop.graphics
|
||||||
|
|
||||||
|
import ocelot.desktop.geometry.Transform2D
|
||||||
|
import ocelot.desktop.util.{Resource, ResourceManager}
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.opengl.GL20
|
||||||
|
|
||||||
|
import scala.io.Source
|
||||||
|
|
||||||
|
class ShaderProgram(name: String) extends Logging with Resource {
|
||||||
|
logger.info(s"Loading shader program ($name)")
|
||||||
|
|
||||||
|
val fragmentShader: Int = createShader(Source.fromResource(s"ocelot/desktop/shader/$name.frag", getClass.getClassLoader), GL20.GL_FRAGMENT_SHADER, "fragment")
|
||||||
|
val vertexShader: Int = createShader(Source.fromResource(s"ocelot/desktop/shader/$name.vert", getClass.getClassLoader), GL20.GL_VERTEX_SHADER, "vertex")
|
||||||
|
|
||||||
|
val shaderProgram: Int = GL20.glCreateProgram()
|
||||||
|
|
||||||
|
GL20.glAttachShader(shaderProgram, vertexShader)
|
||||||
|
GL20.glAttachShader(shaderProgram, fragmentShader)
|
||||||
|
GL20.glLinkProgram(shaderProgram)
|
||||||
|
|
||||||
|
{
|
||||||
|
val success = Array(0)
|
||||||
|
GL20.glGetProgramiv(shaderProgram, GL20.GL_LINK_STATUS, success)
|
||||||
|
|
||||||
|
if (success.head == 0) {
|
||||||
|
val info = GL20.glGetProgramInfoLog(shaderProgram)
|
||||||
|
logger.error(s"Failed to link shader program ($name)\n$info")
|
||||||
|
throw new RuntimeException("Shader linkage failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceManager.registerResource(this)
|
||||||
|
|
||||||
|
def freeResource(): Unit = {
|
||||||
|
logger.debug(s"Destroyed shader program ($name)")
|
||||||
|
GL20.glDeleteProgram(shaderProgram)
|
||||||
|
GL20.glDeleteShader(vertexShader)
|
||||||
|
GL20.glDeleteShader(fragmentShader)
|
||||||
|
}
|
||||||
|
|
||||||
|
def bind(): Unit = {
|
||||||
|
GL20.glUseProgram(shaderProgram)
|
||||||
|
}
|
||||||
|
|
||||||
|
def set(name: String, transform: Transform2D): Unit = {
|
||||||
|
bind()
|
||||||
|
|
||||||
|
val array = transform.array ++ Array(0f, 0f, 1f)
|
||||||
|
GL20.glUniformMatrix3fv(getLocation(name), true, array)
|
||||||
|
}
|
||||||
|
|
||||||
|
def set(name: String, v: Int): Unit = {
|
||||||
|
bind()
|
||||||
|
GL20.glUniform1i(getLocation(name), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def getLocation(name: String): Int = {
|
||||||
|
GL20.glGetUniformLocation(shaderProgram, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def createShader(source: Source, ty: Int, s: String): Int = {
|
||||||
|
val shader = GL20.glCreateShader(ty)
|
||||||
|
|
||||||
|
GL20.glShaderSource(shader, source.mkString)
|
||||||
|
GL20.glCompileShader(shader)
|
||||||
|
|
||||||
|
val success = Array(0)
|
||||||
|
GL20.glGetShaderiv(shader, GL20.GL_COMPILE_STATUS, success)
|
||||||
|
|
||||||
|
if (success.head == 0) {
|
||||||
|
val info = GL20.glGetShaderInfoLog(shader)
|
||||||
|
logger.error(s"Failed to compile shader ($s)\n$info")
|
||||||
|
throw new RuntimeException("Shader compilation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
source.close()
|
||||||
|
|
||||||
|
shader
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/main/scala/ocelot/desktop/graphics/Texture.scala
Normal file
51
src/main/scala/ocelot/desktop/graphics/Texture.scala
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package ocelot.desktop.graphics
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
import ocelot.desktop.util.{Resource, ResourceManager}
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.opengl._
|
||||||
|
import org.lwjgl.system.MemoryUtil.NULL
|
||||||
|
|
||||||
|
class Texture extends Logging with Resource {
|
||||||
|
val texture: Int = GL11.glGenTextures()
|
||||||
|
|
||||||
|
ResourceManager.registerResource(this)
|
||||||
|
|
||||||
|
def freeResource(): Unit = {
|
||||||
|
logger.debug(s"Destroyed texture (ID: $texture)")
|
||||||
|
GL11.glDeleteTextures(texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
bind()
|
||||||
|
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST)
|
||||||
|
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST)
|
||||||
|
|
||||||
|
def this(width: Int, height: Int, format: Int, dataType: Int) = {
|
||||||
|
this()
|
||||||
|
bind()
|
||||||
|
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
def set(width: Int, height: Int, format: Int, dataType: Int, buf: ByteBuffer): Unit = {
|
||||||
|
bind()
|
||||||
|
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, buf)
|
||||||
|
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D)
|
||||||
|
}
|
||||||
|
|
||||||
|
def this(width: Int, height: Int, format: Int, dataType: Int, buf: ByteBuffer) = {
|
||||||
|
this()
|
||||||
|
set(width, height, format, dataType, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
def write(x: Int, y: Int, w: Int, h: Int, format: Int, dataType: Int, buf: ByteBuffer): Unit = {
|
||||||
|
bind()
|
||||||
|
GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, x, y, w, h, format, dataType, buf)
|
||||||
|
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D)
|
||||||
|
}
|
||||||
|
|
||||||
|
def bind(unit: Int = 0): Unit = {
|
||||||
|
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit)
|
||||||
|
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/scala/ocelot/desktop/graphics/buffer/Buffer.scala
Normal file
74
src/main/scala/ocelot/desktop/graphics/buffer/Buffer.scala
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package ocelot.desktop.graphics.buffer
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
import ocelot.desktop.util.Resource
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.BufferUtils
|
||||||
|
import org.lwjgl.opengl.GL15
|
||||||
|
import org.lwjgl.system.MemoryUtil.NULL
|
||||||
|
|
||||||
|
class Buffer[T <: BufferPut] extends Logging with Resource {
|
||||||
|
val target: Int = GL15.GL_ARRAY_BUFFER
|
||||||
|
var buffer: Int = GL15.glGenBuffers()
|
||||||
|
var capacity: Int = _
|
||||||
|
var stride: Int = _
|
||||||
|
|
||||||
|
def freeResource(): Unit = {
|
||||||
|
logger.debug(s"Destroyed buffer (ID: $buffer) of ${capacity * stride} bytes")
|
||||||
|
GL15.glDeleteBuffers(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
def this(elements: Seq[T]) = {
|
||||||
|
this()
|
||||||
|
createStatic(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
def write(offset: Int, buf: ByteBuffer): Unit = {
|
||||||
|
bind()
|
||||||
|
GL15.glBufferSubData(target, offset * stride, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
def resize(newCap: Int): Unit = {
|
||||||
|
createDynamic(this.stride, newCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def createDynamic(stride: Int, cap: Int): Unit = {
|
||||||
|
bind()
|
||||||
|
GL15.nglBufferData(target, stride * cap, NULL, GL15.GL_DYNAMIC_DRAW)
|
||||||
|
|
||||||
|
this.stride = stride
|
||||||
|
capacity = cap
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def createStatic(elements: Seq[T]): Unit = {
|
||||||
|
assert(elements.nonEmpty, "Zero size immutable buffer")
|
||||||
|
extractMeta(elements.head)
|
||||||
|
|
||||||
|
bind()
|
||||||
|
|
||||||
|
val buf = storeElements(elements)
|
||||||
|
|
||||||
|
GL15.glBufferData(target, buf, GL15.GL_STATIC_DRAW)
|
||||||
|
capacity = elements.length
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def storeElements(elements: Seq[T]): ByteBuffer = {
|
||||||
|
val data = BufferUtils.createByteBuffer(elements.length * elements.head.stride)
|
||||||
|
|
||||||
|
for (element <- elements) {
|
||||||
|
element.put(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.flip
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def extractMeta(head: T): Unit = {
|
||||||
|
stride = head.stride
|
||||||
|
}
|
||||||
|
|
||||||
|
def bind(): Unit = {
|
||||||
|
GL15.glBindBuffer(target, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package ocelot.desktop.graphics.buffer
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
trait BufferPut {
|
||||||
|
def stride: Int
|
||||||
|
|
||||||
|
def put(buf: ByteBuffer): Unit
|
||||||
|
}
|
||||||
11
src/main/scala/ocelot/desktop/graphics/buffer/Index.scala
Normal file
11
src/main/scala/ocelot/desktop/graphics/buffer/Index.scala
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package ocelot.desktop.graphics.buffer
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
case class Index(i: Int) extends BufferPut {
|
||||||
|
override def stride: Int = 4
|
||||||
|
|
||||||
|
override def put(buf: ByteBuffer): Unit = {
|
||||||
|
buf.putInt(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package ocelot.desktop.graphics.buffer
|
||||||
|
|
||||||
|
import org.lwjgl.opengl.GL15
|
||||||
|
|
||||||
|
class IndexBuffer extends Buffer[Index] {
|
||||||
|
override val target: Int = GL15.GL_ELEMENT_ARRAY_BUFFER
|
||||||
|
|
||||||
|
def this(elements: Seq[Index]) = {
|
||||||
|
this()
|
||||||
|
createStatic(elements)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package ocelot.desktop.graphics.buffer
|
||||||
|
|
||||||
|
import ocelot.desktop.graphics.mesh.{Vertex, VertexType}
|
||||||
|
|
||||||
|
class VertexBuffer[V <: Vertex] extends Buffer[V] {
|
||||||
|
var ty: VertexType = _
|
||||||
|
|
||||||
|
def this(elements: Seq[V]) = {
|
||||||
|
this()
|
||||||
|
createStatic(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
def this(vertexType: VertexType, cap: Int) = {
|
||||||
|
this()
|
||||||
|
|
||||||
|
ty = vertexType
|
||||||
|
createDynamic(ty.stride, cap)
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def extractMeta(head: V): Unit = {
|
||||||
|
ty = head.vertexType
|
||||||
|
super.extractMeta(head)
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/scala/ocelot/desktop/graphics/mesh/Mesh.scala
Normal file
13
src/main/scala/ocelot/desktop/graphics/mesh/Mesh.scala
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package ocelot.desktop.graphics.mesh
|
||||||
|
|
||||||
|
import ocelot.desktop.graphics.buffer.Index
|
||||||
|
import ocelot.desktop.geometry.Vector2D
|
||||||
|
|
||||||
|
object Mesh {
|
||||||
|
val quad: Mesh = Mesh(
|
||||||
|
Array(Vector2D(0f, 0f), Vector2D(1f, 0f), Vector2D(1f, 1f), Vector2D(0f, 1f)).map(v => MeshVertex(v, v)),
|
||||||
|
Some(Array(0, 1, 2, 2, 3, 0).map(i => Index(i)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Mesh(vertices: Seq[MeshVertex], indices: Option[Seq[Index]] = None)
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package ocelot.desktop.graphics.mesh
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
import ocelot.desktop.color.Color
|
||||||
|
import ocelot.desktop.geometry.Transform2D
|
||||||
|
import org.lwjgl.opengl.GL11
|
||||||
|
|
||||||
|
object MeshInstance extends VertexType {
|
||||||
|
override val stride: Int = 68
|
||||||
|
|
||||||
|
override val attributes: Seq[Attribute] = Array(
|
||||||
|
Attribute(4, GL11.GL_FLOAT, normalized = false, stride, 0),
|
||||||
|
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 16),
|
||||||
|
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 28),
|
||||||
|
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 40),
|
||||||
|
Attribute(3, GL11.GL_FLOAT, normalized = false, stride, 52),
|
||||||
|
Attribute(1, GL11.GL_FLOAT, normalized = false, stride, 64),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MeshInstance(color: Color, z: Float, transform: Transform2D, uvTransform: Transform2D = Transform2D.identity) extends Vertex {
|
||||||
|
override def stride: Int = MeshInstance.stride
|
||||||
|
|
||||||
|
override def vertexType: VertexType = MeshInstance
|
||||||
|
|
||||||
|
override def put(buf: ByteBuffer): Unit = {
|
||||||
|
color.toRGBANorm.array.foreach(f => buf.putFloat(f))
|
||||||
|
transform.array.foreach(f => buf.putFloat(f))
|
||||||
|
uvTransform.array.foreach(f => buf.putFloat(f))
|
||||||
|
buf.putFloat(z)
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/scala/ocelot/desktop/graphics/mesh/MeshVertex.scala
Normal file
28
src/main/scala/ocelot/desktop/graphics/mesh/MeshVertex.scala
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package ocelot.desktop.graphics.mesh
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
import ocelot.desktop.geometry.Vector2D
|
||||||
|
import org.lwjgl.opengl.GL11
|
||||||
|
|
||||||
|
object MeshVertex extends VertexType {
|
||||||
|
override val stride: Int = 16
|
||||||
|
|
||||||
|
override val attributes: Seq[Attribute] = Array(
|
||||||
|
Attribute(2, GL11.GL_FLOAT, normalized = false, stride, 0),
|
||||||
|
Attribute(2, GL11.GL_FLOAT, normalized = false, stride, 8)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class MeshVertex(pos: Vector2D, uv: Vector2D) extends Vertex {
|
||||||
|
override def stride: Int = MeshVertex.stride
|
||||||
|
|
||||||
|
override def vertexType: VertexType = MeshVertex
|
||||||
|
|
||||||
|
override def put(buf: ByteBuffer): Unit = {
|
||||||
|
buf.putFloat(pos.x)
|
||||||
|
buf.putFloat(pos.y)
|
||||||
|
buf.putFloat(uv.x)
|
||||||
|
buf.putFloat(uv.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/main/scala/ocelot/desktop/graphics/mesh/Vertex.scala
Normal file
7
src/main/scala/ocelot/desktop/graphics/mesh/Vertex.scala
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package ocelot.desktop.graphics.mesh
|
||||||
|
|
||||||
|
import ocelot.desktop.graphics.buffer.BufferPut
|
||||||
|
|
||||||
|
trait Vertex extends BufferPut {
|
||||||
|
def vertexType: VertexType
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package ocelot.desktop.graphics.mesh
|
||||||
|
|
||||||
|
import ocelot.desktop.graphics.buffer.{IndexBuffer, VertexBuffer}
|
||||||
|
import ocelot.desktop.util.{Resource, ResourceManager}
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.opengl.{GL20, GL30, GL33}
|
||||||
|
|
||||||
|
class VertexArray extends Logging with Resource {
|
||||||
|
val array: Int = GL30.glGenVertexArrays()
|
||||||
|
var location = 0
|
||||||
|
|
||||||
|
ResourceManager.registerResource(this)
|
||||||
|
|
||||||
|
def freeResource(): Unit = {
|
||||||
|
logger.debug(s"Destroyed VAO (ID: $array)")
|
||||||
|
GL30.glDeleteVertexArrays(array)
|
||||||
|
}
|
||||||
|
|
||||||
|
def addVertexBuffer[V <: Vertex](vbuf: VertexBuffer[V], instanced: Boolean = false): Unit = {
|
||||||
|
bind()
|
||||||
|
|
||||||
|
for (attr <- vbuf.ty.attributes) {
|
||||||
|
GL20.glEnableVertexAttribArray(location)
|
||||||
|
vbuf.bind()
|
||||||
|
|
||||||
|
GL20.glVertexAttribPointer(location, attr.size, attr.ty, attr.normalized, attr.stride, attr.pointer)
|
||||||
|
|
||||||
|
if (instanced) GL33.glVertexAttribDivisor(location, 1)
|
||||||
|
|
||||||
|
location += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def addIndexBuffer(ibuf: IndexBuffer): Unit = {
|
||||||
|
bind()
|
||||||
|
ibuf.bind()
|
||||||
|
}
|
||||||
|
|
||||||
|
def bind(): Unit = GL30.glBindVertexArray(array)
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package ocelot.desktop.graphics.mesh
|
||||||
|
|
||||||
|
trait VertexType {
|
||||||
|
val stride: Int
|
||||||
|
val attributes: Seq[Attribute]
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Attribute(size: Int, ty: Int, normalized: Boolean, stride: Int, pointer: Long)
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
package ocelot.desktop.graphics.render
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
import ocelot.desktop.graphics.buffer.{IndexBuffer, VertexBuffer}
|
||||||
|
import ocelot.desktop.graphics.mesh.{Mesh, MeshInstance, MeshVertex, VertexArray}
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.BufferUtils
|
||||||
|
import org.lwjgl.opengl._
|
||||||
|
|
||||||
|
import scala.collection.mutable.ArrayBuffer
|
||||||
|
|
||||||
|
class InstanceRenderer(mesh: Mesh) extends Logging {
|
||||||
|
val InitialCapacity: Int = 4096
|
||||||
|
|
||||||
|
val vertexBuffer = new VertexBuffer[MeshVertex](mesh.vertices)
|
||||||
|
val indexBuffer: Option[IndexBuffer] = mesh.indices.map(indices => new IndexBuffer(indices))
|
||||||
|
val instanceBuffer = new VertexBuffer[MeshInstance](MeshInstance, InitialCapacity)
|
||||||
|
|
||||||
|
val vertexArray = new VertexArray
|
||||||
|
|
||||||
|
vertexArray.addVertexBuffer(vertexBuffer)
|
||||||
|
indexBuffer.foreach(ib => vertexArray.addIndexBuffer(ib))
|
||||||
|
vertexArray.addVertexBuffer(instanceBuffer, instanced = true)
|
||||||
|
|
||||||
|
private val instances = new ArrayBuffer[MeshInstance]
|
||||||
|
|
||||||
|
private var position = 0
|
||||||
|
|
||||||
|
private var writeStart = 0
|
||||||
|
private var writeLen = 0
|
||||||
|
|
||||||
|
private var drawStart = 0
|
||||||
|
private var drawLen = 0
|
||||||
|
|
||||||
|
def schedule(instance: MeshInstance): Unit = {
|
||||||
|
if (writeLen > 0 || instances.length <= position || instances(position) != instance) {
|
||||||
|
if (writeLen == 0) writeStart = position
|
||||||
|
writeLen += 1
|
||||||
|
|
||||||
|
if (instances.length <= position) {
|
||||||
|
instances += instance
|
||||||
|
} else {
|
||||||
|
instances(position) = instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
position += 1
|
||||||
|
drawLen += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
def flush(): Unit = {
|
||||||
|
write()
|
||||||
|
draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
def commit(): Unit = {
|
||||||
|
flush()
|
||||||
|
position = 0
|
||||||
|
writeStart = 0
|
||||||
|
writeLen = 0
|
||||||
|
drawStart = 0
|
||||||
|
drawLen = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private def draw(): Unit = {
|
||||||
|
if (drawLen == 0) return
|
||||||
|
|
||||||
|
vertexArray.bind()
|
||||||
|
|
||||||
|
indexBuffer match {
|
||||||
|
case Some(ib) => ARBBaseInstance.glDrawElementsInstancedBaseInstance(GL11.GL_TRIANGLES, ib.capacity, GL11.GL_UNSIGNED_INT, 0, drawLen, drawStart)
|
||||||
|
case None => ARBBaseInstance.glDrawArraysInstancedBaseInstance(GL11.GL_TRIANGLES, 0, vertexBuffer.capacity, drawLen, drawStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawStart += drawLen
|
||||||
|
drawLen = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private def write(): Unit = {
|
||||||
|
if (instances.length > instanceBuffer.capacity) {
|
||||||
|
instanceBuffer.resize(instances.length * 2)
|
||||||
|
instanceBuffer.write(0, packInstances(0, instances.length))
|
||||||
|
} else if (writeLen > 0) {
|
||||||
|
instanceBuffer.write(writeStart, packInstances(writeStart, writeLen))
|
||||||
|
|
||||||
|
writeLen = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def packInstances(start: Int, len: Int): ByteBuffer = {
|
||||||
|
val data = BufferUtils.createByteBuffer(len * MeshInstance.stride)
|
||||||
|
|
||||||
|
for (element <- instances.slice(start, start + len)) {
|
||||||
|
element.put(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
data.flip
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package ocelot.desktop.graphics.render
|
||||||
|
|
||||||
|
import ocelot.desktop.color.RGBAColorNorm
|
||||||
|
import ocelot.desktop.graphics.ShaderProgram
|
||||||
|
import ocelot.desktop.graphics.mesh.Mesh
|
||||||
|
|
||||||
|
class TextRenderer {
|
||||||
|
private val shaderProgram = new ShaderProgram("text")
|
||||||
|
private val renderer = new InstanceRenderer(Mesh.quad)
|
||||||
|
|
||||||
|
var bg: RGBAColorNorm = RGBAColorNorm(0f, 0f, 0f, 0f)
|
||||||
|
var fg: RGBAColorNorm = RGBAColorNorm(0f, 0f, 0f, 1f)
|
||||||
|
var scale: Float = 16
|
||||||
|
|
||||||
|
def scheduleChar(x: Float, y: Float, char: Char): Unit = {
|
||||||
|
// renderer.schedule(MeshInstance(RGBAColorNorm(1.0f, 0.0f, 0.0f, 1.0f), Transform2D.translate(x * 10 + 1, y * 20 + 1) >> Transform2D.scale(8f, 18f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
def flush(): Unit = renderer.flush()
|
||||||
|
|
||||||
|
def commit(): Unit = renderer.commit()
|
||||||
|
}
|
||||||
130
src/main/scala/ocelot/desktop/util/FontLoader.scala
Normal file
130
src/main/scala/ocelot/desktop/util/FontLoader.scala
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package ocelot.desktop.util
|
||||||
|
|
||||||
|
import java.awt.Color
|
||||||
|
import java.awt.image.{BufferedImage, DataBufferByte, IndexColorModel}
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util
|
||||||
|
|
||||||
|
import ocelot.desktop.graphics.Texture
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
import org.lwjgl.opengl.GL11
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.io.{Codec, Source}
|
||||||
|
|
||||||
|
object FontLoader extends Logging {
|
||||||
|
val GlyphHeight = 16
|
||||||
|
val AtlasWidth = 1536
|
||||||
|
val AtlasHeight = 1536
|
||||||
|
|
||||||
|
var glyphCount = 0
|
||||||
|
|
||||||
|
var currentColor = 0xFFFFFF
|
||||||
|
var atlas: BufferedImage = {
|
||||||
|
val icmArr = Array(0.toByte, 0xff.toByte)
|
||||||
|
val icm = new IndexColorModel(1, 2, icmArr, icmArr, icmArr, icmArr)
|
||||||
|
new BufferedImage(AtlasWidth, AtlasHeight, BufferedImage.TYPE_BYTE_BINARY, icm)
|
||||||
|
}
|
||||||
|
|
||||||
|
var map = new mutable.HashMap[Char, Rect]
|
||||||
|
|
||||||
|
def isCharWide(char: Char): Boolean = map.get(char).exists(rect => rect.w == rect.h)
|
||||||
|
|
||||||
|
def setColor(rgb: Int): Unit = {
|
||||||
|
if (rgb == currentColor) return
|
||||||
|
|
||||||
|
currentColor = rgb
|
||||||
|
|
||||||
|
val c = new Color(rgb)
|
||||||
|
val icm = new IndexColorModel(1, 2,
|
||||||
|
Array(0.toByte, c.getRed.toByte),
|
||||||
|
Array(0.toByte, c.getGreen.toByte),
|
||||||
|
Array(0.toByte, c.getBlue.toByte),
|
||||||
|
Array(0.toByte, 0xff.toByte))
|
||||||
|
|
||||||
|
atlas = new BufferedImage(icm, atlas.getRaster, false, new util.Hashtable())
|
||||||
|
}
|
||||||
|
|
||||||
|
def init(): Unit = {
|
||||||
|
logger.info("Loading font.hex")
|
||||||
|
|
||||||
|
var ox = 0
|
||||||
|
var oy = 0
|
||||||
|
|
||||||
|
val resource = getClass.getResource("/ocelot/desktop/font.hex")
|
||||||
|
val source = Source.fromInputStream(resource.getContent.asInstanceOf[InputStream])(Codec.UTF8)
|
||||||
|
|
||||||
|
for (line <- source.getLines()) {
|
||||||
|
val colon = line.indexOf(':')
|
||||||
|
val char = Integer.parseInt(line.substring(0, colon), 16).toChar
|
||||||
|
val width = (line.length - colon - 1) / 4
|
||||||
|
|
||||||
|
if (ox + width > AtlasWidth) {
|
||||||
|
ox = 0
|
||||||
|
oy += GlyphHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
map(char) = Rect(
|
||||||
|
ox.asInstanceOf[Float] / AtlasWidth.asInstanceOf[Float],
|
||||||
|
oy.asInstanceOf[Float] / AtlasHeight.asInstanceOf[Float],
|
||||||
|
width.asInstanceOf[Float] / AtlasWidth.asInstanceOf[Float],
|
||||||
|
GlyphHeight.asInstanceOf[Float] / AtlasHeight.asInstanceOf[Float]
|
||||||
|
)
|
||||||
|
|
||||||
|
var x = 0
|
||||||
|
var y = 0
|
||||||
|
|
||||||
|
for (char <- line.substring(colon + 1)) {
|
||||||
|
for (i <- 3 to 0 by -1) {
|
||||||
|
val bit = (Character.digit(char, 16) >> i) & 1
|
||||||
|
|
||||||
|
atlas.setRGB(ox + x, oy + y, bit match {
|
||||||
|
case 0 => 0x00000000
|
||||||
|
case 1 => 0xFFFFFFFF
|
||||||
|
})
|
||||||
|
|
||||||
|
x += 1
|
||||||
|
|
||||||
|
if (x == width) {
|
||||||
|
x = 0
|
||||||
|
y += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ox += width
|
||||||
|
glyphCount += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val size = atlas.getRaster.getDataBuffer.getSize
|
||||||
|
logger.info(s"Packed $glyphCount glyphs into ${AtlasWidth}x$AtlasHeight 1-bit texture ($size bytes)")
|
||||||
|
}
|
||||||
|
|
||||||
|
def makeTexture(): Texture = {
|
||||||
|
val bytes = atlas.getRaster.getDataBuffer.asInstanceOf[DataBufferByte]
|
||||||
|
val buf = ByteBuffer.allocateDirect(AtlasWidth * AtlasHeight * 1)
|
||||||
|
|
||||||
|
for (b <- bytes.getData) { // :/
|
||||||
|
buf.put(((b >> 7 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
buf.put(((b >> 6 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
buf.put(((b >> 5 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
buf.put(((b >> 4 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
buf.put(((b >> 3 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
buf.put(((b >> 2 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
buf.put(((b >> 1 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
buf.put(((b >> 0 & 1) * 255).asInstanceOf[Byte])
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.flip()
|
||||||
|
|
||||||
|
val tex = new Texture()
|
||||||
|
tex.bind()
|
||||||
|
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1)
|
||||||
|
tex.set(AtlasWidth, AtlasHeight, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, buf)
|
||||||
|
|
||||||
|
tex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Rect(x: Float, y: Float, w: Float, h: Float)
|
||||||
93
src/main/scala/ocelot/desktop/util/KeyMapping.scala
Normal file
93
src/main/scala/ocelot/desktop/util/KeyMapping.scala
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package ocelot.desktop.util
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFW
|
||||||
|
|
||||||
|
object KeyMapping {
|
||||||
|
def map(code: Int): Int = {
|
||||||
|
code match {
|
||||||
|
case GLFW.GLFW_KEY_ESCAPE => 1
|
||||||
|
case x if GLFW.GLFW_KEY_1 <= x && x <= GLFW.GLFW_KEY_9 => x - GLFW.GLFW_KEY_1 + 2
|
||||||
|
case GLFW.GLFW_KEY_0 => 11
|
||||||
|
case GLFW.GLFW_KEY_MINUS => 12
|
||||||
|
case GLFW.GLFW_KEY_EQUAL => 13
|
||||||
|
case GLFW.GLFW_KEY_BACKSPACE => 14
|
||||||
|
case GLFW.GLFW_KEY_TAB => 15
|
||||||
|
case GLFW.GLFW_KEY_Q => 16
|
||||||
|
case GLFW.GLFW_KEY_W => 17
|
||||||
|
case GLFW.GLFW_KEY_E => 18
|
||||||
|
case GLFW.GLFW_KEY_R => 19
|
||||||
|
case GLFW.GLFW_KEY_T => 20
|
||||||
|
case GLFW.GLFW_KEY_Y => 21
|
||||||
|
case GLFW.GLFW_KEY_U => 22
|
||||||
|
case GLFW.GLFW_KEY_I => 23
|
||||||
|
case GLFW.GLFW_KEY_O => 24
|
||||||
|
case GLFW.GLFW_KEY_P => 25
|
||||||
|
case GLFW.GLFW_KEY_LEFT_BRACKET => 26
|
||||||
|
case GLFW.GLFW_KEY_RIGHT_BRACKET => 27
|
||||||
|
case GLFW.GLFW_KEY_ENTER => 28
|
||||||
|
case GLFW.GLFW_KEY_LEFT_CONTROL => 29
|
||||||
|
case GLFW.GLFW_KEY_A => 30
|
||||||
|
case GLFW.GLFW_KEY_S => 31
|
||||||
|
case GLFW.GLFW_KEY_D => 32
|
||||||
|
case GLFW.GLFW_KEY_F => 33
|
||||||
|
case GLFW.GLFW_KEY_G => 34
|
||||||
|
case GLFW.GLFW_KEY_H => 35
|
||||||
|
case GLFW.GLFW_KEY_J => 36
|
||||||
|
case GLFW.GLFW_KEY_K => 37
|
||||||
|
case GLFW.GLFW_KEY_L => 38
|
||||||
|
case GLFW.GLFW_KEY_SEMICOLON => 39
|
||||||
|
case GLFW.GLFW_KEY_Q => 40
|
||||||
|
// case GLFW.GLFW_KEY_BACK_QUOTE => 41
|
||||||
|
// case GLFW.GLFW_KEY_SHIFT => 42
|
||||||
|
// case GLFW.GLFW_KEY_BACK_SLASH => 43
|
||||||
|
case GLFW.GLFW_KEY_Z => 44
|
||||||
|
case GLFW.GLFW_KEY_X => 45
|
||||||
|
case GLFW.GLFW_KEY_C => 46
|
||||||
|
case GLFW.GLFW_KEY_V => 47
|
||||||
|
case GLFW.GLFW_KEY_B => 48
|
||||||
|
case GLFW.GLFW_KEY_N => 49
|
||||||
|
case GLFW.GLFW_KEY_M => 50
|
||||||
|
case GLFW.GLFW_KEY_COMMA => 51
|
||||||
|
case GLFW.GLFW_KEY_PERIOD => 52
|
||||||
|
case GLFW.GLFW_KEY_SLASH => 53
|
||||||
|
// case GLFW.GLFW_KEY_MULTIPLY => 55
|
||||||
|
// case GLFW.GLFW_KEY_ALT => 56
|
||||||
|
case GLFW.GLFW_KEY_SPACE => 57
|
||||||
|
case GLFW.GLFW_KEY_CAPS_LOCK => 58
|
||||||
|
case x if GLFW.GLFW_KEY_F1 <= x && x <= GLFW.GLFW_KEY_F10 => x - GLFW.GLFW_KEY_F1 + 59
|
||||||
|
case GLFW.GLFW_KEY_NUM_LOCK => 69
|
||||||
|
case GLFW.GLFW_KEY_SCROLL_LOCK => 70
|
||||||
|
// case x if GLFW.GLFW_KEY_NUMPAD7 <= x && x <= GLFW.GLFW_KEY_NUMPAD9 => x - GLFW.GLFW_KEY_NUMPAD7 + 71
|
||||||
|
// case GLFW.GLFW_KEY_SUBTRACT => 74
|
||||||
|
// case x if GLFW.GLFW_KEY_NUMPAD4 <= x && x <= GLFW.GLFW_KEY_NUMPAD6 => x - GLFW.GLFW_KEY_NUMPAD4 + 75
|
||||||
|
// case GLFW.GLFW_KEY_ADD => 78
|
||||||
|
// case x if GLFW.GLFW_KEY_NUMPAD1 <= x && x <= GLFW.GLFW_KEY_NUMPAD3 => x - GLFW.GLFW_KEY_NUMPAD1 + 79
|
||||||
|
// case GLFW.GLFW_KEY_NUMPAD0 => 82
|
||||||
|
// case GLFW.GLFW_KEY_DECIMAL => 83
|
||||||
|
// case x if GLFW.GLFW_KEY_F11 <= x && x <= GLFW.GLFW_KEY_F18 => x - GLFW.GLFW_KEY_F11 + 87
|
||||||
|
// case GLFW.GLFW_KEY_KANA => 112
|
||||||
|
// case GLFW.GLFW_KEY_CONVERT => 121
|
||||||
|
// case GLFW.GLFW_KEY_NONCONVERT => 123
|
||||||
|
// case GLFW.GLFW_KEY_CIRCUMFLEX => 144
|
||||||
|
// case GLFW.GLFW_KEY_AT => 145
|
||||||
|
// case GLFW.GLFW_KEY_COLON => 146
|
||||||
|
// case GLFW.GLFW_KEY_UNDERSCORE => 147
|
||||||
|
// case GLFW.GLFW_KEY_KANJI => 148
|
||||||
|
// case GLFW.GLFW_KEY_STOP => 149
|
||||||
|
// case GLFW.GLFW_KEY_DIVIDE => 181
|
||||||
|
case GLFW.GLFW_KEY_PAUSE => 197
|
||||||
|
case GLFW.GLFW_KEY_HOME => 199
|
||||||
|
case GLFW.GLFW_KEY_UP => 200
|
||||||
|
case GLFW.GLFW_KEY_PAGE_UP => 201
|
||||||
|
case GLFW.GLFW_KEY_LEFT => 203
|
||||||
|
case GLFW.GLFW_KEY_RIGHT => 205
|
||||||
|
case GLFW.GLFW_KEY_END => 207
|
||||||
|
case GLFW.GLFW_KEY_DOWN => 208
|
||||||
|
case GLFW.GLFW_KEY_PAGE_DOWN => 209
|
||||||
|
case GLFW.GLFW_KEY_INSERT => 210
|
||||||
|
case GLFW.GLFW_KEY_DELETE => 211
|
||||||
|
// case GLFW.GLFW_KEY_META => 219
|
||||||
|
case _ => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/main/scala/ocelot/desktop/util/Resource.scala
Normal file
5
src/main/scala/ocelot/desktop/util/Resource.scala
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ocelot.desktop.util
|
||||||
|
|
||||||
|
trait Resource {
|
||||||
|
def freeResource(): Unit
|
||||||
|
}
|
||||||
23
src/main/scala/ocelot/desktop/util/ResourceManager.scala
Normal file
23
src/main/scala/ocelot/desktop/util/ResourceManager.scala
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package ocelot.desktop.util
|
||||||
|
|
||||||
|
import scala.collection.mutable.ArrayBuffer
|
||||||
|
|
||||||
|
object ResourceManager {
|
||||||
|
private val resources = new ArrayBuffer[Resource]
|
||||||
|
|
||||||
|
def registerResource(resource: Resource): Unit = {
|
||||||
|
resources += resource
|
||||||
|
}
|
||||||
|
|
||||||
|
def freeResource(resource: Resource): Unit = {
|
||||||
|
resource.freeResource()
|
||||||
|
resources -= resource
|
||||||
|
}
|
||||||
|
|
||||||
|
def freeResources(): Unit = {
|
||||||
|
for (resource <- resources)
|
||||||
|
resource.freeResource()
|
||||||
|
|
||||||
|
resources.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/main/scala/ocelot/desktop/widget/ScreenWidget.scala
Normal file
90
src/main/scala/ocelot/desktop/widget/ScreenWidget.scala
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package ocelot.desktop.widget
|
||||||
|
|
||||||
|
import ocelot.desktop.color.IntColor
|
||||||
|
import ocelot.desktop.graphics.Graphics
|
||||||
|
import ocelot.desktop.util.FontLoader
|
||||||
|
import org.apache.logging.log4j.scala.Logging
|
||||||
|
|
||||||
|
class ScreenWidget extends Logging {
|
||||||
|
private val fontSize = 16f
|
||||||
|
|
||||||
|
val width = 80
|
||||||
|
val height = 25
|
||||||
|
|
||||||
|
var background: Int = 0x000000
|
||||||
|
var foreground: Int = 0xFFFFFF
|
||||||
|
|
||||||
|
private val data: Array[Cell] = Array.fill(width * height)(Cell(' ', background, foreground))
|
||||||
|
|
||||||
|
def draw(g: Graphics): Unit = {
|
||||||
|
g.clear(0, 0, math.round(fontSize * width / 2f), math.round(fontSize * height))
|
||||||
|
g.fontSize = fontSize
|
||||||
|
|
||||||
|
for (y <- 0 until height) {
|
||||||
|
for (x <- 0 until width) {
|
||||||
|
val Cell(char, bg, fg) = data(y * width + x)
|
||||||
|
g.background = IntColor(bg)
|
||||||
|
g.foreground = IntColor(fg)
|
||||||
|
g.char(x * fontSize / 2f, y * fontSize, char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def set(x: Int, y: Int, text: String, vertical: Boolean): Unit = {
|
||||||
|
var ox = x
|
||||||
|
var oy = y
|
||||||
|
|
||||||
|
for (char <- text) {
|
||||||
|
setChar(ox, oy, char)
|
||||||
|
|
||||||
|
if (vertical)
|
||||||
|
oy += 1
|
||||||
|
else
|
||||||
|
ox += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def fill(x: Int, y: Int, w: Int, h: Int, c: Char): Unit = {
|
||||||
|
for (sx <- x until x + w)
|
||||||
|
for (sy <- y until y + h)
|
||||||
|
setChar(sx, sy, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
def copy(x: Int, y: Int, w: Int, h: Int, tx: Int, ty: Int): Unit = {
|
||||||
|
val dataClone = data.clone
|
||||||
|
|
||||||
|
for (sx <- x until x + w) {
|
||||||
|
for (sy <- y until y + h) {
|
||||||
|
val nx = sx + tx
|
||||||
|
val ny = sy + ty
|
||||||
|
|
||||||
|
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
||||||
|
val src = dataClone(sy * width + sx)
|
||||||
|
val dst = data((sy + ty) * width + sx + tx)
|
||||||
|
|
||||||
|
dst.char = src.char
|
||||||
|
dst.bg = src.bg
|
||||||
|
dst.fg = src.fg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setChar(x: Int, y: Int, c: Char): Unit = {
|
||||||
|
val isWide = FontLoader.isCharWide(c)
|
||||||
|
|
||||||
|
if (isWide && x == width - 1)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (isWide)
|
||||||
|
setChar(x + 1, y, ' ')
|
||||||
|
|
||||||
|
val cell = data(y * width + x)
|
||||||
|
cell.char = c
|
||||||
|
cell.bg = background
|
||||||
|
cell.fg = foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Cell(var char: Char, var bg: Int, var fg: Int)
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user