Skip to content

Instantly share code, notes, and snippets.

@melix
Created November 29, 2017 19:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save melix/c64c138b89b15363af4f6e593f1efef7 to your computer and use it in GitHub Desktop.
Save melix/c64c138b89b15363af4f6e593f1efef7 to your computer and use it in GitHub Desktop.
A Groovy+JavaFX port of https://github.com/s-macke/VoxelSpace
/**
* An approximate port of https://github.com/s-macke/VoxelSpace
* using static Groovy and JavaFX.
*
* Click on the panel to "fly".
*
* Run with : groovy voxel.groovy
*
* Twitter: @CedricChampeau
*/
import groovy.transform.Canonical
import groovy.transform.CompileStatic
import javafx.animation.AnimationTimer
import javafx.application.Application
import javafx.concurrent.Task
import javafx.scene.Cursor
import javafx.scene.Scene
import javafx.scene.canvas.Canvas
import javafx.scene.canvas.GraphicsContext
import javafx.scene.input.MouseEvent
import javafx.scene.layout.Pane
import javafx.scene.paint.Color
import javafx.stage.Stage
import javax.imageio.ImageIO
import java.awt.image.BufferedImage
import java.util.concurrent.atomic.AtomicBoolean
@CompileStatic
class Camera {
volatile double x = 512d
volatile double y = 300d
volatile double height = 78d
volatile double angle = 0d
volatile double horizon = 100d
volatile double distance = 800d
}
@CompileStatic
@Canonical
class VoxelMap {
final byte[] height
final int[] color
static VoxelMap load(String color, String height) {
new VoxelMap(heightMap(height), colorMap(color))
}
private static int[] colorMap(String color) {
img(color).with {
def arr = new int[1024 * 1024]
for (int i = 0; i < arr.length; i++) {
arr[i] = getRGB(i % 1024, i.intdiv(1024))
}
arr
}
}
private static byte[] heightMap(String height) {
int[] ints = img(height).data.getPixels(0, 0, 1024, 1024, new int[1024 * 1024])
byte[] bytes = new byte[ints.length]
for (int i = 0; i < ints.length; i++) {
bytes[i] = (byte) ints[i]
}
bytes
}
private static BufferedImage img(String file) {
ImageIO.read("https://raw.githubusercontent.com/s-macke/VoxelSpace/master/maps/${file}.png".toURL())
}
}
@CompileStatic
@Canonical
class Delta {
volatile double angleDelta
volatile double zDelta
}
@CompileStatic
class VoxelApp extends Application {
Camera camera = new Camera()
VoxelMap map = null
static void start(String colors, String height) {
launch(VoxelApp, colors, height)
}
@Override
void init() {
super.init()
def colors = parameters.unnamed[0]
def height = parameters.unnamed[1]
map = VoxelMap.load(colors, height)
}
@Override
void start(Stage primaryStage) {
primaryStage.title = "Voxel Engine demo"
def root = new Pane()
def canvas = new Canvas(800d, 600d)
def gc = canvas.graphicsContext2D
root.style = "-fx-background-color: #99ccff;"
render(gc)
root.children.add(canvas)
def scene = new Scene(root)
primaryStage.scene = scene
primaryStage.show()
setupResizeListener(scene, canvas, gc)
setupRenderLoop(primaryStage, gc, scene)
}
private static Color rgb(int rgb) { Color.rgb((rgb >> 16) & 255, (rgb >> 8) & 255, rgb & 255) }
private
void setupRenderLoop(Stage primaryStage, GraphicsContext gc, Scene scene) {
def dragging = new AtomicBoolean()
def delta = new Delta()
new Thread(new Task<Void>() {
Void call() {
while (true) {
if (dragging.get()) {
camera.with {
horizon += 2d * delta.zDelta
height += delta.zDelta
angle += delta.angleDelta
x -= Math.sin(angle) * 5d
y -= Math.cos(angle) * 5d
height = Math.max(0d, height)
height = Math.min(height, 255d)
}
}
sleep(50)
}
}
}).start()
setupMouseDragHandlers(primaryStage, scene, dragging, delta)
startAnimation(gc)
}
private void startAnimation(GraphicsContext gc) {
new AnimationTimer() {
void handle(long timestamp) {
render(gc)
}
}.start()
}
private void setupMouseDragHandlers(Stage primaryStage, Scene scene, AtomicBoolean dragging, Delta delta) {
scene.with {
onMousePressed = { mouseEvent ->
dragging.set(true)
scene.setCursor(Cursor.MOVE)
}
onMouseReleased = {
scene.setCursor(Cursor.HAND)
dragging.set(false)
delta.angleDelta = 0d
delta.zDelta = 0d
}
onMouseDragged = { MouseEvent mouseEvent ->
primaryStage.with {
double centerX = width / 2d
double centerY = height / 2d
delta.angleDelta = (centerX - mouseEvent.x) / (10d * width)
delta.zDelta = 10d * (centerY - mouseEvent.y) / height
}
}
}
}
private void setupResizeListener(Scene scene, Canvas canvas, GraphicsContext gc) {
scene.with {
widthProperty().addListener { _, oldValue, newValue ->
if (oldValue != newValue) {
canvas.width = (double) newValue
render(gc)
}
}
heightProperty().addListener { _, oldValue, newValue ->
if (oldValue != newValue) {
canvas.height = (double) newValue
render(gc)
}
}
}
}
private void drawLine(GraphicsContext g, double x, double ytop, double ybottom, int color) {
def top = ytop < 0d ? 0d : ytop
if (top > ybottom) return
g.stroke = rgb(color)
g.strokeLine(x, ybottom, x, top)
}
private void render(GraphicsContext g) {
def screenwidth = g.canvas.width
def screenheight = g.canvas.height
def sinang = Math.sin(camera.angle)
def cosang = Math.cos(camera.angle)
def screenWidthAsInt = (int) screenwidth
def hiddeny = new double[screenWidthAsInt]
for (int i = 0; i < screenWidthAsInt; i++) {
hiddeny[i] = screenheight
}
g.clearRect(0, 0, screenwidth, screenheight)
def dz = 1d
def z = 1d
while (z < camera.distance) {
// 90 degree field of view
def plx = -cosang * z - sinang * z
def ply = sinang * z - cosang * z
def prx = cosang * z - sinang * z
def pry = -sinang * z - cosang * z
def dx = (prx - plx) / screenwidth
def dy = (pry - ply) / screenwidth
plx += camera.x
ply += camera.y
def invz = 1d / z * 240d
for (int i=0; i<screenWidthAsInt; i++) {
def mapoffset = ((((int) Math.floor(ply)) & 1023) << 10) + (((int) Math.floor(plx)) & 1023)
def heightonscreen = (camera.height - map.height[mapoffset]) * invz + camera.horizon
drawLine(g, (double) i, heightonscreen, hiddeny[i], map.color[mapoffset])
if (heightonscreen < hiddeny[i]) {
hiddeny[i] = heightonscreen
}
plx += dx
ply += dy
}
z += dz
}
}
}
VoxelApp.start("C1W", "D1")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment