Hello folks!
I was having some fun with Processing and JavaFX integration.
This is part of development for some future projects and still in it’s early stages!
Code < Click here to expand!
/**
* Background P3D Cube Renderer with JavaFX Controls
* Author: glv (primary developer and prompt engineer)
* Assisted by: ChatGPT
* Date: 2025-07-01
* Version 1.0.0
*
* Description:
* This Processing sketch demonstrates running a 3D rotating cube
* on an offscreen PGraphics (P3D) inside a background PApplet,
* while displaying the live rendered image in a JavaFX ImageView.
* JavaFX sliders control the cube's yaw (Y axis), pitch (X axis),
* and roll (Z axis) rotations in real-time.
*
* JavaFX UI runs on the JavaFX Application Thread via Platform.runLater(),
* ensuring toolkit initialization and thread safety.
* The cube renderer runs independently on a Processing thread.
*
* Environment:
* Processing 4.4.4
* Windows 10
* JavaFX 4.0 beta 5 library
*
* Note:
* For W10 and Processing 4.4.4 the JavaFX JAR files
* need to be placed in the sketch’s 'code' folder
* to enable JavaFX functionality.
*/
// Processing JavaFX integration to bridge Processing sketches with JavaFX
import processing.javafx.*;
// JavaFX application toolkit for thread management and UI updates
import javafx.application.Platform;
// JavaFX classes for scene management and UI components
import javafx.scene.Scene;
import javafx.scene.image.*; // ImageView, WritableImage, PixelWriter, PixelFormat
import javafx.scene.control.*; // Slider, Button, Label
import javafx.scene.layout.*; // VBox layout container
import javafx.stage.Stage; // Top-level JavaFX window
import javafx.geometry.Pos; // Layout alignment constants
CubeRendererApp cubeApp;
ImageView imageView;
Slider yawSlider, pitchSlider, rollSlider;
Label yawLabel, pitchLabel, rollLabel;
Button startButton, resetButton;
volatile PImage flag;
void settings() {
size(800, 600);
}
void setup() {
System.setProperty("glass.win.uiScale", "1"); // This works!
// flag = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Flag_of_Canada_%28Pantone%29.svg/500px-Flag_of_Canada_%28Pantone%29.svg.png");
surface.setVisible(false);
// Force JavaFX toolkit initialization once:
try {
Platform.startup(() -> {
}
);
}
catch (IllegalStateException e) {
// toolkit already initialized, ignore
}
// Now safely run JavaFX UI creation
Platform.runLater(() -> {
imageView = new ImageView();
imageView.setFitWidth(300);
imageView.setFitHeight(300);
imageView.setPreserveRatio(true);
// SLIDERS
yawSlider = new Slider(-180, 180, 0);
yawSlider.setShowTickMarks(true);
yawSlider.setShowTickLabels(true);
yawSlider.setMajorTickUnit(90);
yawSlider.setMinorTickCount(2);
yawSlider.setBlockIncrement(5);
yawSlider.setSnapToTicks(false);
yawSlider.setPadding(new javafx.geometry.Insets(0, 10, 0, 10));
pitchSlider = new Slider(-180, 180, 0);
pitchSlider.setShowTickMarks(true);
pitchSlider.setShowTickLabels(true);
pitchSlider.setMajorTickUnit(90);
pitchSlider.setMinorTickCount(2);
pitchSlider.setBlockIncrement(5);
pitchSlider.setSnapToTicks(false);
pitchSlider.setPadding(new javafx.geometry.Insets(0, 10, 0, 10));
rollSlider = new Slider(-180, 180, 0);
rollSlider.setShowTickMarks(true);
rollSlider.setShowTickLabels(true);
rollSlider.setMajorTickUnit(90);
rollSlider.setMinorTickCount(2);
rollSlider.setBlockIncrement(5);
rollSlider.setSnapToTicks(false);
rollSlider.setPadding(new javafx.geometry.Insets(0, 10, 0, 10));
// Spacer
Region spacer = new Region();
spacer.setMinHeight(20); // or whatever height you want (e.g., 10–50 px)
// LABELS
yawLabel = new Label("Yaw (Y axis): 0°");
pitchLabel = new Label("Pitch (X axis): 0°");
rollLabel = new Label("Roll (Z axis): 0°");
// BUTTONS
startButton = new Button("Start Renderer");
resetButton = new Button("Reset Rotations");
startButton.setOnAction(e -> {
if (cubeApp == null) {
cubeApp = new CubeRendererApp();
PApplet.runSketch(new String[]{"CubeRendererApp"}, cubeApp);
bindSliders();
}
}
);
resetButton.setOnAction(e -> {
if (cubeApp != null) {
yawSlider.setValue(0);
pitchSlider.setValue(0);
rollSlider.setValue(0);
}
}
);
// LAYOUT (only declared once)
VBox root = new VBox(10,
imageView,
spacer,
yawLabel, yawSlider,
pitchLabel, pitchSlider,
rollLabel, rollSlider,
startButton,
resetButton
);
root.setAlignment(Pos.CENTER);
Stage stage = new Stage();
stage.setScene(new Scene(root, 400, 730));
stage.setTitle("Background P3D Cube with JavaFX Controls");
stage.show();
}
);
}
void draw() {
background(30);
fill(255);
textSize(18);
text("Main sketch", 20, 40);
// Update JavaFX ImageView with the latest cube frame
if (cubeApp != null && cubeApp.sharedCopy != null) {
WritableImage fxImg = pImageToWritableImage(cubeApp.sharedCopy);
Platform.runLater(() -> imageView.setImage(fxImg));
}
}
// Bind JavaFX sliders to update cube rotation angles
void bindSliders() {
yawSlider.valueProperty().addListener((obs, oldV, newV) -> {
float v = newV.floatValue();
yawLabel.setText(String.format("Yaw (Y axis): %.2f", v));
cubeApp.setYaw(v);
}
);
pitchSlider.valueProperty().addListener((obs, oldV, newV) -> {
float v = newV.floatValue();
pitchLabel.setText(String.format("Pitch (X axis): %.2f", v));
cubeApp.setPitch(v);
}
);
rollSlider.valueProperty().addListener((obs, oldV, newV) -> {
float v = newV.floatValue();
rollLabel.setText(String.format("Roll (Z axis): %.2f", v));
cubeApp.setRoll(v);
}
);
}
// Convert Processing PImage to JavaFX WritableImage for display
WritableImage pImageToWritableImage(PImage pImg) {
pImg.loadPixels();
WritableImage wimg = new WritableImage(pImg.width, pImg.height);
PixelWriter pw = wimg.getPixelWriter();
pw.setPixels(0, 0, pImg.width, pImg.height,
javafx.scene.image.PixelFormat.getIntArgbInstance(),
pImg.pixels, 0, pImg.width);
return wimg;
}
// Background Processing PApplet rendering a rotating 3D cube offscreen
public static class CubeRendererApp extends PApplet {
PGraphics pg;
public volatile PImage sharedCopy;
float yaw=0, pitch=0, roll=0;
PImage flag;
public void settings() {
size(300, 300, P3D);
}
public void setup() {
pg = createGraphics(300, 300, P3D);
//surface.setVisible(false); Must be visible so will hide it!
surface.setLocation(-500, -500);
flag = loadImage("https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Flag_of_Canada_%28Pantone%29.svg/500px-Flag_of_Canada_%28Pantone%29.svg.png");
flag.resize(80, 180);
flag.save("test.png");
}
public void draw() {
// // Position quad exactly on top face
float w = 40; // half width
float d = 90; // half depth
float y = -10.1; // top face y coordinate (half height, negative because up)
pg.beginDraw();
pg.background(10);
pg.lights();
pg.translate(pg.width/2f, pg.height/2f, 0);
pg.rotateY(radians(yaw));
pg.rotateX(radians(pitch));
pg.rotateZ(radians(roll));
// Draw textured top face first
pg.beginShape(QUADS);
pg.texture(flag);
// Rotate UVs -90° (counter-clockwise)
pg.vertex(-w, y, -d, flag.width, 0); // top-left → top-right
pg.vertex( w, y, -d, flag.width, flag.height); // top-right → bottom-right
pg.vertex( w, y, d, 0, flag.height); // bottom-right → bottom-left
pg.vertex(-w, y, d, 0, 0); // bottom-left → top-left
pg.endShape();
// Then draw cube box
pg.noFill();
pg.stroke(255);
pg.box(2*40, 2*10, 2*90);
pg.endDraw();
// Update shared frame for main sketch display
sharedCopy = pg.get();
}
public void setYaw(float y) {
yaw = y;
}
public void setPitch(float p) {
pitch = p;
}
public void setRoll(float r) {
roll = r;
}
}
:)