Commit 26d3eda9 authored by Matthias Betz's avatar Matthias Betz
Browse files

Add GLViewport integrating canvas, camera, picking and overlay

parent e7bff9cc
package de.hft.stuttgart.citydoctor2.gui.gl;
import com.huskerdev.grapl.gl.GLProfile;
import com.huskerdev.openglfx.canvas.GLCanvas;
import com.huskerdev.openglfx.jogl.JOGLExecutor;
import com.huskerdev.openglfx.jogl.events.JOGLRenderEvent;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL3;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
/** OpenGL viewport embedded in JavaFX via openglfx. Replaces the legacy SubScene. */
public class GLViewport {
private static final Color BACKGROUND = Color.AZURE;
private final GLCanvas canvas;
private final OrbitCamera camera = new OrbitCamera();
private final GlScene scene = new GlScene();
private final PickingFramebuffer picking = new PickingFramebuffer();
private final HighlightOverlay overlay = new HighlightOverlay();
private ShaderProgram sceneProgram;
private ShaderProgram pickProgram;
private boolean initialized;
// state set off-thread, consumed in render callback
private final AtomicReference<SceneData> pendingScene = new AtomicReference<>();
private final AtomicReference<int[]> pendingIndexSet = new AtomicReference<>();
private volatile int selectedId = 0;
private volatile boolean wireframe = false;
private volatile boolean cull = false;
private volatile float pointScale = 5f;
// highlight marker data, uploaded on the GL thread when dirty
private volatile float[] hlPoints = new float[0];
private volatile float[] hlPointColors = new float[0];
private volatile float[] hlLines = new float[0];
private volatile float[] hlLineColors = new float[0];
private volatile boolean highlightsDirty = false;
// pick request: screen coords + callback (invoked on FX thread with resolved Object or null)
private volatile int[] pickRequest;
private volatile Consumer<Object> pickCallback;
private SceneData currentScene;
// drag state
private double lastX;
private double lastY;
public GLViewport() {
canvas = new GLCanvas(new JOGLExecutor(), GLProfile.CORE);
canvas.addOnInitEvent(ev -> initialized = false);
canvas.addOnRenderEvent(ev -> render((JOGLRenderEvent) ev));
canvas.addOnReshapeEvent(ev -> { /* aspect handled per-frame from width/height */ });
installInputHandlers();
}
public GLCanvas getNode() { return canvas; }
// ---- public API (called from FX / worker threads) ----
public void setScene(SceneData data) { pendingScene.set(data); canvas.repaint(); }
public void setIndexSet(int[] indices) { pendingIndexSet.set(indices); canvas.repaint(); }
public void setSelectedId(int id) { selectedId = id; canvas.repaint(); }
public void setWireframe(boolean on) { wireframe = on; canvas.repaint(); }
public void setCulling(boolean on) { cull = on; canvas.repaint(); }
public void setPointScale(float scale) { pointScale = scale; canvas.repaint(); }
/** points: xyz, pointColors rgb-per-point; lines: xyz pairs, lineColors rgb-per-line-vertex. */
public void setHighlights(float[] points, float[] pointColors, float[] lines, float[] lineColors) {
this.hlPoints = points;
this.hlPointColors = pointColors;
this.hlLines = lines;
this.hlLineColors = lineColors;
this.highlightsDirty = true;
canvas.repaint();
}
public OrbitCamera camera() { return camera; }
public void requestPick(double x, double y, Consumer<Object> callback) {
pickRequest = new int[]{(int) Math.round(x), (int) Math.round(y)};
pickCallback = callback;
canvas.repaint();
}
// ---- render callback (GL thread) ----
private void render(JOGLRenderEvent ev) {
GL3 gl = ev.getGl();
int w = ev.width;
int h = ev.height;
int defaultFbo = ev.fbo;
if (!initialized) {
sceneProgram = new ShaderProgram(gl, "scene.vert", "scene.frag");
pickProgram = new ShaderProgram(gl, "pick.vert", "pick.frag");
overlay.init(gl);
gl.glEnable(GL.GL_DEPTH_TEST);
initialized = true;
}
SceneData newScene = pendingScene.getAndSet(null);
if (newScene != null) {
scene.upload(gl, newScene);
currentScene = newScene;
}
int[] newIndices = pendingIndexSet.getAndSet(null);
if (newIndices != null) {
scene.setIndexSet(gl, newIndices);
}
if (highlightsDirty) {
overlay.setPoints(gl, hlPoints, hlPointColors);
overlay.setLines(gl, hlLines, hlLineColors);
highlightsDirty = false;
}
camera.setAspect(h == 0 ? 1f : (float) w / h);
float[] mvp = camera.viewProjection();
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, defaultFbo);
gl.glViewport(0, 0, w, h);
gl.glClearColor((float) BACKGROUND.getRed(), (float) BACKGROUND.getGreen(),
(float) BACKGROUND.getBlue(), 1f);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
if (cull) {
gl.glEnable(GL.GL_CULL_FACE);
} else {
gl.glDisable(GL.GL_CULL_FACE);
}
gl.glPolygonMode(GL.GL_FRONT_AND_BACK, wireframe ? GL3.GL_LINE : GL3.GL_FILL);
sceneProgram.use(gl);
gl.glUniformMatrix4fv(sceneProgram.uniform(gl, "uMVP"), 1, false, mvp, 0);
gl.glUniform1i(sceneProgram.uniform(gl, "uSelectedId"), selectedId);
scene.draw(gl);
overlay.draw(gl, pointScale);
int[] req = pickRequest;
if (req != null && currentScene != null) {
picking.resize(gl, w, h);
picking.bind(gl);
gl.glClearColor(0, 0, 0, 1);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL3.GL_FILL);
pickProgram.use(gl);
gl.glUniformMatrix4fv(pickProgram.uniform(gl, "uMVP"), 1, false, mvp, 0);
scene.draw(gl);
int id = picking.readId(gl, req[0], req[1]);
Object target = currentScene.pickRegistry().resolve(id);
selectedId = (target != null) ? id : 0;
Consumer<Object> cb = pickCallback;
pickRequest = null;
pickCallback = null;
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, defaultFbo);
if (cb != null) {
Platform.runLater(() -> cb.accept(target));
}
}
}
// ---- input ----
private void installInputHandlers() {
canvas.setOnMousePressed(e -> { lastX = e.getX(); lastY = e.getY(); });
canvas.setOnMouseDragged(e -> {
double dx = e.getX() - lastX;
double dy = e.getY() - lastY;
lastX = e.getX();
lastY = e.getY();
if (e.getButton() == MouseButton.PRIMARY) {
camera.orbit(dx / 3.0, dy / 3.0);
} else if (e.getButton() == MouseButton.SECONDARY) {
double speed = Math.abs(camera.distance()) / 1000.0;
camera.pan(-dx * speed, dy * speed);
}
canvas.repaint();
});
canvas.setOnScroll(e -> {
camera.zoom(e.getDeltaY() < 0 ? 1.05 : 0.95);
canvas.repaint();
});
}
public void resetCamera() { camera.reset(); canvas.repaint(); }
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment