Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
CityDoctor
CityDoctor2
Commits
26d3eda9
Commit
26d3eda9
authored
Jun 19, 2026
by
Matthias Betz
Browse files
Add GLViewport integrating canvas, camera, picking and overlay
parent
e7bff9cc
Changes
1
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/GLViewport.java
0 → 100644
View file @
26d3eda9
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
=
5
f
;
// 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
?
1
f
:
(
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
(),
1
f
);
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
();
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment