All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cwinter.codecraft.graphics.engine.WebGLRenderer.scala Maven / Gradle / Ivy

The newest version!
package cwinter.codecraft.graphics.engine

import cwinter.codecraft.graphics.materials.Material
import cwinter.codecraft.graphics.model.{TheCompositeModelBuilderCache, TheModelCache, PrimitiveModelBuilder}
import cwinter.codecraft.util.maths.{ColorRGBA, Rectangle, Vector2, VertexXY}
import org.scalajs.dom
import org.scalajs.dom.raw.{HTMLDivElement, WebGLRenderingContext => GL}
import org.scalajs.dom.{document, html}

import scala.scalajs.js

private[codecraft] class WebGLRenderer(
  canvas: html.Canvas,
  gameWorld: Simulator
) extends Renderer {
  implicit val gl = initGL()
  implicit val renderStack = new JSRenderStack()
  val camera = new Camera2D
  val modelCache = new TheModelCache
  val compositeModelBuilderCache = new TheCompositeModelBuilderCache
  lazy val context = new GraphicsContext(renderStack, true, modelCache, compositeModelBuilderCache)
  camera.position = (gameWorld.initialCameraPos.x, gameWorld.initialCameraPos.y)
  camera.screenDims = (canvas.width, canvas.height)
  camera.zoom = gameWorld.initialCameraZoom

  private[this] val keyEventHandler = new KeyEventHandler(gameWorld, camera)
  canvas.onkeypress = onKeyPress _
  canvas.onmousedown = onMouseDown _
  if (document.createElement("div").hasAttribute("onwheel"))
    canvas.addEventListener("onwheel", onMouseWheel _, useCapture = false)
  else if (js.eval("document.onmousewheel !== undefined").asInstanceOf[Boolean]) {
    canvas.onmousewheel = onMouseWheel _
  } else canvas.addEventListener("DOMMouseScroll", onScroll _, useCapture = false)

  canvas.onmouseup = onMouseUp _
  canvas.onmousemove = onMouseMove _

  def onKeyPress(e: dom.KeyboardEvent) = {
    val key = e.keyCode match {
      case 37 => LeftArrow
      case 39 => RightArrow
      case 38 => UpArrow
      case 40 => DownArrow
      case 33 => PageUp
      case 34 => PageDown
      case _ => Letter(e.charCode.toChar)
    }
    keyEventHandler.keypress(key)
  }

  def onMouseWheel(e: dom.WheelEvent): Unit = {
    camera.zoom += 0.001f * e.deltaY.toFloat
  }

  def onScroll(e: dom.UIEvent): Unit = {
    camera.zoom += 0.02f * e.detail
  }

  private[this] var mouseIsDown = false
  def onMouseDown(e: dom.MouseEvent): Unit = {
    mouseIsDown = true
  }

  def onMouseUp(e: dom.MouseEvent): Unit = {
    mouseIsDown = false
  }

  private[this] var xLast = 0.0
  private[this] var yLast = 0.0
  def onMouseMove(e: dom.MouseEvent): Unit = {
    val dx = xLast - e.clientX
    val dy = yLast - e.clientY
    xLast = e.clientX
    yLast = e.clientY
    if ((e.buttons & 7) > 0) {
      camera.x += dx.toFloat * camera.zoomFactor
      camera.y -= dy.toFloat * camera.zoomFactor
    }
  }

  def render(): Unit = {
    Material.resetDrawCalls()
    Material.resetModelviewUploads()

    for (Vector2(x, y) <- gameWorld.debug.cameraOverride)
      camera.position = (x.toFloat, y.toFloat)

    val width = canvas.clientWidth
    val height = canvas.clientHeight
    if (width != camera.screenWidth || height != camera.screenHeight) {
      camera.screenDims = (width, height)
      canvas.width = width
      canvas.height = height
    }

    gl.viewport(0, 0, camera.screenWidth, camera.screenHeight)

    gl.clearColor(0.02, 0.02, 0.02, 1)
    gl.clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT)

    val (worldObjects, textModels) = gameWorld.dequeueFrame()
    val projectionT = camera.projection.transposed
    val onScreen =
      Rectangle(
        camera.x - camera.zoomFactor * 0.5f * camera.screenWidth,
        camera.x + camera.zoomFactor * 0.5f * camera.screenWidth,
        camera.y - camera.zoomFactor * 0.5f * camera.screenHeight,
        camera.y + camera.zoomFactor * 0.5f * camera.screenHeight
      )

    var mcurr = renderStack.materials
    while (mcurr != Nil) {
      val material = mcurr.head
      material.beforeDraw(projectionT)

      var objcurr = worldObjects
      while (objcurr != Nil) {
        val modelDescriptor = objcurr.head
        if (modelDescriptor.intersects(onScreen)) {
          modelDescriptor.closedModel(gameWorld.timestep, context).draw(material)
        }

        objcurr = objcurr.tail
      }

      material.afterDraw()
      mcurr = mcurr.tail
    }

    val textDiv = document.getElementById("text-container").asInstanceOf[HTMLDivElement]
    val textTestDiv = document.getElementById("text-test-container").asInstanceOf[HTMLDivElement]
    if (textDiv == null || textTestDiv == null) {
      println(
        "Could not find div#text-container and div#text-test-container. Without this, text cannot be rendered.")
    } else if (textModels.nonEmpty || textDiv.innerHTML != "") {
      textTestDiv.innerHTML = """
""" textDiv.innerHTML = "" for (text <- textModels) renderText(textDiv, text, width, height) if (gameWorld.isPaused) { renderText( textDiv, TextModel( "Game Paused. Press SPACEBAR to resume.", width / 2, height / 2, ColorRGBA(1, 1, 1, 1), absolutePos = true ), width, height ) } textTestDiv.innerHTML = "" } // dispose one-time VBOs //context.freeTempVBOs(gl) } private def renderText(container: HTMLDivElement, textModel: TextModel, width: Int, height: Int): Unit = { val TextModel(text, x, y, ColorRGBA(r, g, b, a), absolutePos, centered, largeFont) = textModel def int(f: Float) = Math.round(255 * f) val position = if (absolutePos) screenToBrowserCoords(x, y, width, height) else worldToBrowserCoords(x, y, width, height) val (textWidth, textHeight) = if (centered) { val testDivID = if (largeFont) "large-text-dim-test" else "small-text-dim-test" val testDiv = document.getElementById(testDivID).asInstanceOf[HTMLDivElement] testDiv.innerHTML = textModel.text (testDiv.clientWidth + 1, testDiv.clientHeight + 1) } else (0, 0) if (!absolutePos && (position.x - textWidth / 2 < 0 || position.y - textHeight < 0 || position.x > width + textWidth / 2 || position.y > height + textHeight / 2)) return val textElem = document.createElement("div").asInstanceOf[HTMLDivElement] textElem.className = if (largeFont) "large-floating-text" else "floating-text" textElem.style.color = s"rgba(${int(r)}, ${int(g)}, ${int(b)}, $a)" textElem.innerHTML = text textElem.style.left = s"${position.x.toInt - textWidth / 2}px" textElem.style.top = s"${position.y.toInt - textHeight / 2}px" container.appendChild(textElem) } private def worldToBrowserCoords(x: Float, y: Float, width: Int, height: Int): VertexXY = { val worldPos = VertexXY(x, -y) val cameraPos = (1 / camera.zoomFactor) * (worldPos - VertexXY(camera.x, -camera.y)) + VertexXY(width / 2f, height / 2f) cameraPos } private def screenToBrowserCoords(x: Float, y: Float, width: Int, height: Int): VertexXY = { require(-1 <= x); require(x <= 1); require(-1 <= y); require(y <= 1) VertexXY(x * width / 2f, -y * height / 2f) + VertexXY(width / 2f, height / 2f) } private def initGL(): GL = { val gl = canvas.getContext("experimental-webgl").asInstanceOf[GL] if (gl == null) { dom.window.alert("Could not initialise WebGL. Visit https://get.webgl.org for further info.") } gl.viewport(-1, 1, canvas.width, canvas.height) gl } def dispose(): Unit = context.dispose(gl) }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy