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
854aab9b
Commit
854aab9b
authored
Sep 10, 2026
by
Matthias Betz
Browse files
Merge remote-tracking branch 'remotes/origin/dev' into 120-portierung-sweep-algorithmen
parents
760147a4
cc4fe260
Pipeline
#12442
passed with stage
in 2 minutes and 26 seconds
Changes
56
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/Mat4.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
/**
* Minimal column-major 4x4 float matrix helpers, layout-compatible with OpenGL uniform uploads
* (element index = column*4 + row).
*/
public
final
class
Mat4
{
private
Mat4
()
{}
public
static
float
[]
identity
()
{
float
[]
m
=
new
float
[
16
];
m
[
0
]
=
m
[
5
]
=
m
[
10
]
=
m
[
15
]
=
1
f
;
return
m
;
}
public
static
float
[]
translation
(
float
x
,
float
y
,
float
z
)
{
float
[]
m
=
identity
();
m
[
12
]
=
x
;
m
[
13
]
=
y
;
m
[
14
]
=
z
;
return
m
;
}
/** Returns a * b (column-major). */
public
static
float
[]
multiply
(
float
[]
a
,
float
[]
b
)
{
float
[]
r
=
new
float
[
16
];
for
(
int
col
=
0
;
col
<
4
;
col
++)
{
for
(
int
row
=
0
;
row
<
4
;
row
++)
{
float
sum
=
0
f
;
for
(
int
k
=
0
;
k
<
4
;
k
++)
{
sum
+=
a
[
k
*
4
+
row
]
*
b
[
col
*
4
+
k
];
}
r
[
col
*
4
+
row
]
=
sum
;
}
}
return
r
;
}
/** Returns m * (x,y,z,w) as a length-4 array. */
public
static
float
[]
transform
(
float
[]
m
,
float
x
,
float
y
,
float
z
,
float
w
)
{
return
new
float
[]
{
m
[
0
]
*
x
+
m
[
4
]
*
y
+
m
[
8
]
*
z
+
m
[
12
]
*
w
,
m
[
1
]
*
x
+
m
[
5
]
*
y
+
m
[
9
]
*
z
+
m
[
13
]
*
w
,
m
[
2
]
*
x
+
m
[
6
]
*
y
+
m
[
10
]
*
z
+
m
[
14
]
*
w
,
m
[
3
]
*
x
+
m
[
7
]
*
y
+
m
[
11
]
*
z
+
m
[
15
]
*
w
};
}
public
static
float
[]
rotationX
(
float
rad
)
{
float
c
=
(
float
)
Math
.
cos
(
rad
);
float
s
=
(
float
)
Math
.
sin
(
rad
);
float
[]
m
=
identity
();
m
[
5
]
=
c
;
m
[
9
]
=
-
s
;
m
[
6
]
=
s
;
m
[
10
]
=
c
;
return
m
;
}
public
static
float
[]
rotationZ
(
float
rad
)
{
float
c
=
(
float
)
Math
.
cos
(
rad
);
float
s
=
(
float
)
Math
.
sin
(
rad
);
float
[]
m
=
identity
();
m
[
0
]
=
c
;
m
[
4
]
=
-
s
;
m
[
1
]
=
s
;
m
[
5
]
=
c
;
return
m
;
}
/** Right-handed perspective projection (camera looks down -Z). */
public
static
float
[]
perspective
(
float
fovyRad
,
float
aspect
,
float
near
,
float
far
)
{
float
f
=
(
float
)
(
1.0
/
Math
.
tan
(
fovyRad
/
2.0
));
float
[]
m
=
new
float
[
16
];
m
[
0
]
=
f
/
aspect
;
m
[
5
]
=
f
;
m
[
10
]
=
(
far
+
near
)
/
(
near
-
far
);
m
[
11
]
=
-
1
f
;
m
[
14
]
=
(
2
f
*
far
*
near
)
/
(
near
-
far
);
return
m
;
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/MeshAccumulator.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
de.hft.stuttgart.citydoctor2.math.Triangle3d
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
/**
* Builds interleaved-by-attribute vertex arrays from triangles. Vertices are stored per triangle corner
* (3 per triangle) so a triangle's vertices occupy a contiguous, predictable range — which lets the
* draw index set be built by triangle without dedup bookkeeping.
*/
public
class
MeshAccumulator
{
private
final
Vector3d
center
;
private
final
FloatList
positions
=
new
FloatList
();
private
final
FloatList
colors
=
new
FloatList
();
private
final
IntList
ids
=
new
IntList
();
private
int
vertexCount
;
public
MeshAccumulator
(
Vector3d
center
)
{
this
.
center
=
center
;
}
public
void
addTriangle
(
Triangle3d
t
,
int
id
,
float
r
,
float
g
,
float
b
)
{
addVertex
(
t
.
getP1
(),
id
,
r
,
g
,
b
);
addVertex
(
t
.
getP2
(),
id
,
r
,
g
,
b
);
addVertex
(
t
.
getP3
(),
id
,
r
,
g
,
b
);
}
private
void
addVertex
(
Vector3d
p
,
int
id
,
float
r
,
float
g
,
float
b
)
{
positions
.
add
((
float
)
(
p
.
getX
()
-
center
.
getX
()),
(
float
)
(
p
.
getY
()
-
center
.
getY
()),
(
float
)
(
p
.
getZ
()
-
center
.
getZ
()));
colors
.
add
(
r
,
g
,
b
);
ids
.
add
(
id
);
vertexCount
++;
}
public
int
vertexCount
()
{
return
vertexCount
;
}
public
float
[]
positions
()
{
return
positions
.
toArray
();
}
public
float
[]
colors
()
{
return
colors
.
toArray
();
}
public
int
[]
ids
()
{
return
ids
.
toArray
();
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/OrbitCamera.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
/**
* Orbit camera reproducing the legacy JavaFX interaction model: world is rotated about Z then X, the
* camera sits at {@code distance} along -Z (negative distance = farther away) and pans in X/Y.
*/
public
class
OrbitCamera
{
private
static
final
double
INITIAL_AZIMUTH
=
180.0
;
private
static
final
double
INITIAL_ELEVATION
=
-
60.0
;
private
static
final
double
INITIAL_DISTANCE
=
100.0
;
private
static
final
double
FOVY_RAD
=
Math
.
toRadians
(
30
);
private
static
final
float
NEAR
=
0.1f
;
private
static
final
float
FAR
=
10000
f
;
private
double
azimuthDeg
=
INITIAL_AZIMUTH
;
private
double
elevationDeg
=
INITIAL_ELEVATION
;
private
double
distance
=
INITIAL_DISTANCE
;
private
double
panX
=
0
;
private
double
panY
=
0
;
private
float
aspect
=
1.0f
;
public
void
setAspect
(
float
aspect
)
{
if
(
aspect
>
0
)
{
this
.
aspect
=
aspect
;
}
}
public
void
orbit
(
double
deltaAzimuthDeg
,
double
deltaElevationDeg
)
{
azimuthDeg
+=
deltaAzimuthDeg
;
elevationDeg
+=
deltaElevationDeg
;
}
public
void
pan
(
double
dx
,
double
dy
)
{
panX
+=
dx
;
panY
+=
dy
;
}
/** factor > 1 moves farther, < 1 moves closer. */
public
void
zoom
(
double
factor
)
{
distance
*=
factor
;
}
/** Matches legacy MainWindow.zoomOutForBoundingBox distance computation. */
public
void
zoomOutForBoundingBox
(
double
diagonalLength
)
{
double
longestSide
=
diagonalLength
*
0.4
;
distance
=
longestSide
/
Math
.
tan
(
FOVY_RAD
/
2.0
);
panX
=
0
;
panY
=
0
;
}
public
void
reset
()
{
azimuthDeg
=
INITIAL_AZIMUTH
;
elevationDeg
=
INITIAL_ELEVATION
;
distance
=
INITIAL_DISTANCE
;
panX
=
0
;
panY
=
0
;
}
/** Copies the orbit/zoom/pan state from another camera (but not its aspect ratio). */
public
void
copyOrbitFrom
(
OrbitCamera
other
)
{
this
.
azimuthDeg
=
other
.
azimuthDeg
;
this
.
elevationDeg
=
other
.
elevationDeg
;
this
.
distance
=
other
.
distance
;
this
.
panX
=
other
.
panX
;
this
.
panY
=
other
.
panY
;
}
public
double
distance
()
{
return
distance
;
}
/** Current azimuth (rotation about Z) in degrees. */
public
double
azimuthDeg
()
{
return
azimuthDeg
;
}
/** Current elevation (rotation about X) in degrees. */
public
double
elevationDeg
()
{
return
elevationDeg
;
}
/** Combined projection * view * model-rotation, column-major, ready for a GL uniform. */
public
float
[]
viewProjection
()
{
float
[]
proj
=
Mat4
.
perspective
((
float
)
FOVY_RAD
,
aspect
,
NEAR
,
FAR
);
float
[]
view
=
Mat4
.
translation
((
float
)
-
panX
,
(
float
)
-
panY
,
(
float
)
-
distance
);
float
[]
rot
=
Mat4
.
multiply
(
Mat4
.
rotationX
((
float
)
Math
.
toRadians
(
elevationDeg
)),
Mat4
.
rotationZ
((
float
)
Math
.
toRadians
(
azimuthDeg
)));
return
Mat4
.
multiply
(
proj
,
Mat4
.
multiply
(
view
,
rot
));
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/PickRegistry.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
java.util.ArrayList
;
import
java.util.List
;
/** Maps picking ids (1-based) to the model objects they represent. */
public
class
PickRegistry
{
private
final
List
<
Object
>
targets
=
new
ArrayList
<>();
/** Registers a target and returns its non-zero picking id. */
public
int
register
(
Object
target
)
{
targets
.
add
(
target
);
return
targets
.
size
();
// id = index + 1
}
/** Resolves a picking id to its target, or null for background / unknown id. */
public
Object
resolve
(
int
id
)
{
if
(
id
<=
0
||
id
>
targets
.
size
())
{
return
null
;
}
return
targets
.
get
(
id
-
1
);
}
public
int
size
()
{
return
targets
.
size
();
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/PickingCodec.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
/** Encodes integer ids into RGB bytes for GPU color-picking. Id 0 is reserved for background. */
public
final
class
PickingCodec
{
private
PickingCodec
()
{}
public
static
int
red
(
int
id
)
{
return
id
&
0xFF
;
}
public
static
int
green
(
int
id
)
{
return
(
id
>>
8
)
&
0xFF
;
}
public
static
int
blue
(
int
id
)
{
return
(
id
>>
16
)
&
0xFF
;
}
public
static
int
decode
(
int
r
,
int
g
,
int
b
)
{
return
(
r
&
0xFF
)
|
((
g
&
0xFF
)
<<
8
)
|
((
b
&
0xFF
)
<<
16
);
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/PickingFramebuffer.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
com.jogamp.opengl.GL
;
import
com.jogamp.opengl.GL3
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteOrder
;
/** Off-screen framebuffer that stores picking ids as RGBA8; supports single-pixel readback. */
public
class
PickingFramebuffer
{
private
int
fbo
;
private
int
colorTex
;
private
int
depthRbo
;
private
int
width
;
private
int
height
;
private
boolean
created
;
public
void
resize
(
GL3
gl
,
int
w
,
int
h
)
{
if
(
w
<=
0
||
h
<=
0
||
(
w
==
width
&&
h
==
height
&&
created
))
{
return
;
}
dispose
(
gl
);
width
=
w
;
height
=
h
;
int
[]
tmp
=
new
int
[
1
];
gl
.
glGenFramebuffers
(
1
,
tmp
,
0
);
fbo
=
tmp
[
0
];
gl
.
glBindFramebuffer
(
GL
.
GL_FRAMEBUFFER
,
fbo
);
gl
.
glGenTextures
(
1
,
tmp
,
0
);
colorTex
=
tmp
[
0
];
gl
.
glBindTexture
(
GL
.
GL_TEXTURE_2D
,
colorTex
);
gl
.
glTexImage2D
(
GL
.
GL_TEXTURE_2D
,
0
,
GL
.
GL_RGBA8
,
w
,
h
,
0
,
GL
.
GL_RGBA
,
GL
.
GL_UNSIGNED_BYTE
,
null
);
gl
.
glTexParameteri
(
GL
.
GL_TEXTURE_2D
,
GL
.
GL_TEXTURE_MIN_FILTER
,
GL
.
GL_NEAREST
);
gl
.
glTexParameteri
(
GL
.
GL_TEXTURE_2D
,
GL
.
GL_TEXTURE_MAG_FILTER
,
GL
.
GL_NEAREST
);
gl
.
glFramebufferTexture2D
(
GL
.
GL_FRAMEBUFFER
,
GL
.
GL_COLOR_ATTACHMENT0
,
GL
.
GL_TEXTURE_2D
,
colorTex
,
0
);
gl
.
glGenRenderbuffers
(
1
,
tmp
,
0
);
depthRbo
=
tmp
[
0
];
gl
.
glBindRenderbuffer
(
GL
.
GL_RENDERBUFFER
,
depthRbo
);
gl
.
glRenderbufferStorage
(
GL
.
GL_RENDERBUFFER
,
GL
.
GL_DEPTH_COMPONENT16
,
w
,
h
);
gl
.
glFramebufferRenderbuffer
(
GL
.
GL_FRAMEBUFFER
,
GL
.
GL_DEPTH_ATTACHMENT
,
GL
.
GL_RENDERBUFFER
,
depthRbo
);
gl
.
glBindFramebuffer
(
GL
.
GL_FRAMEBUFFER
,
0
);
created
=
true
;
}
public
void
bind
(
GL3
gl
)
{
gl
.
glBindFramebuffer
(
GL
.
GL_FRAMEBUFFER
,
fbo
);
gl
.
glViewport
(
0
,
0
,
width
,
height
);
}
/**
* Reads the picking id at canvas pixel (x, y) measured from the top-left. GL's origin is bottom-left,
* so the row is flipped.
*/
public
int
readId
(
GL3
gl
,
int
x
,
int
y
)
{
if
(!
created
||
x
<
0
||
y
<
0
||
x
>=
width
||
y
>=
height
)
{
return
0
;
}
ByteBuffer
buf
=
ByteBuffer
.
allocateDirect
(
4
).
order
(
ByteOrder
.
nativeOrder
());
gl
.
glBindFramebuffer
(
GL
.
GL_FRAMEBUFFER
,
fbo
);
gl
.
glReadPixels
(
x
,
height
-
1
-
y
,
1
,
1
,
GL
.
GL_RGBA
,
GL
.
GL_UNSIGNED_BYTE
,
buf
);
gl
.
glBindFramebuffer
(
GL
.
GL_FRAMEBUFFER
,
0
);
int
r
=
buf
.
get
(
0
)
&
0xFF
;
int
g
=
buf
.
get
(
1
)
&
0xFF
;
int
b
=
buf
.
get
(
2
)
&
0xFF
;
return
PickingCodec
.
decode
(
r
,
g
,
b
);
}
public
void
dispose
(
GL3
gl
)
{
if
(
created
)
{
gl
.
glDeleteFramebuffers
(
1
,
new
int
[]{
fbo
},
0
);
gl
.
glDeleteTextures
(
1
,
new
int
[]{
colorTex
},
0
);
gl
.
glDeleteRenderbuffers
(
1
,
new
int
[]{
depthRbo
},
0
);
created
=
false
;
}
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/SceneBuilder.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
de.hft.stuttgart.citydoctor2.datastructure.BoundarySurfaceType
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel
;
import
de.hft.stuttgart.citydoctor2.datastructure.CityObject
;
import
de.hft.stuttgart.citydoctor2.datastructure.Geometry
;
import
de.hft.stuttgart.citydoctor2.datastructure.Lod
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
import
de.hft.stuttgart.citydoctor2.math.Triangle3d
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
import
de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon
;
import
de.hft.stuttgart.citydoctor2.utils.visitors.CityObjectCollector
;
import
java.util.ArrayList
;
import
java.util.Collection
;
import
java.util.List
;
import
java.util.stream.Stream
;
import
javafx.scene.paint.Color
;
/**
* Builds {@link SceneData} from model polygons: tesselate, bake shade colour, assign picking ids,
* accumulate vertices and per-triangle metadata, recentre by the bounding-box centre.
* Intended to run on a worker thread.
*/
public
final
class
SceneBuilder
{
private
SceneBuilder
()
{}
public
static
SceneData
fromPolygons
(
Collection
<?
extends
Polygon
>
polygons
,
Color
baseColor
)
{
return
build
(
polygons
,
baseColor
,
Lod
.
LOD2
.
ordinal
(),
false
);
}
/** General entry: caller supplies polygons already chosen, plus their lod/roof attributes. */
public
static
SceneData
build
(
Collection
<?
extends
Polygon
>
polygons
,
Color
baseColor
,
int
lod
,
boolean
roof
)
{
Vector3d
center
=
findCenter
(
polygons
);
MeshAccumulator
acc
=
new
MeshAccumulator
(
center
);
PickRegistry
registry
=
new
PickRegistry
();
List
<
TriangleMeta
>
metas
=
new
ArrayList
<>();
int
triangleIndex
=
0
;
for
(
Polygon
p
:
polygons
)
{
Color
base
=
chooseBase
(
p
,
baseColor
);
Color
shaded
=
ShadeColor
.
shade
(
p
.
calculateNormalNormalized
(),
base
);
float
r
=
(
float
)
shaded
.
getRed
();
float
g
=
(
float
)
shaded
.
getGreen
();
float
b
=
(
float
)
shaded
.
getBlue
();
int
id
=
registry
.
register
(
p
);
TesselatedPolygon
tp
=
p
.
tesselate
();
for
(
Triangle3d
t
:
tp
.
getTriangles
())
{
acc
.
addTriangle
(
t
,
id
,
r
,
g
,
b
);
metas
.
add
(
new
TriangleMeta
(
triangleIndex
++,
p
,
lod
,
roof
));
}
}
double
[]
centerArr
=
{
center
.
getX
(),
center
.
getY
(),
center
.
getZ
()};
return
new
SceneData
(
acc
.
positions
(),
acc
.
colors
(),
acc
.
ids
(),
metas
,
registry
,
centerArr
);
}
/**
* Builds ALL features of the model into a single {@link SceneData} with one shared center,
* one {@link PickRegistry} and continuous triangle indices. Filtering by LOD/roof happens later
* via the index set, not at build time. Replicates the per-feature base colours of the legacy
* {@code TriangulatedGeometry.of(model, filters)}.
*/
public
static
SceneData
fromModel
(
CityDoctorModel
model
)
{
Vector3d
center
=
findCenterOfModel
(
model
);
MeshAccumulator
acc
=
new
MeshAccumulator
(
center
);
PickRegistry
registry
=
new
PickRegistry
();
List
<
TriangleMeta
>
metas
=
new
ArrayList
<>();
int
[]
triangleIndex
=
{
0
};
appendFeatures
(
model
.
getBuildings
(),
Color
.
WHITE
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getBridges
(),
Color
.
LIGHTSTEELBLUE
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getTunnels
(),
Color
.
SLATEGRAY
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getLand
(),
Color
.
TAN
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getTransportation
(),
Color
.
DIMGRAY
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getVegetation
(),
Color
.
LIGHTGREEN
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getWater
(),
Color
.
LIGHTSKYBLUE
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getCityFurniture
(),
Color
.
BLUEVIOLET
,
acc
,
registry
,
metas
,
triangleIndex
);
appendFeatures
(
model
.
getOtherCityObjects
(),
Color
.
BLACK
,
acc
,
registry
,
metas
,
triangleIndex
);
double
[]
centerArr
=
{
center
.
getX
(),
center
.
getY
(),
center
.
getZ
()};
return
new
SceneData
(
acc
.
positions
(),
acc
.
colors
(),
acc
.
ids
(),
metas
,
registry
,
centerArr
);
}
private
static
void
appendFeatures
(
Stream
<?
extends
CityObject
>
cos
,
Color
color
,
MeshAccumulator
acc
,
PickRegistry
registry
,
List
<
TriangleMeta
>
metas
,
int
[]
triangleIndex
)
{
CityObjectCollector
collector
=
new
CityObjectCollector
();
cos
.
forEach
(
cityObject
->
cityObject
.
accept
(
collector
));
for
(
CityObject
co
:
collector
.
getCityObjects
())
{
for
(
Geometry
geom
:
co
.
getGeometries
())
{
int
lod
=
geom
.
getLod
()
==
null
?
Lod
.
LOD2
.
ordinal
()
:
geom
.
getLod
().
ordinal
();
for
(
Polygon
p
:
geom
.
getPolygons
())
{
if
(
p
.
isLink
())
{
continue
;
}
boolean
roof
=
p
.
getPartOfSurface
()
!=
null
&&
p
.
getPartOfSurface
().
getType
()
==
BoundarySurfaceType
.
ROOF
;
appendPolygon
(
p
,
color
,
lod
,
roof
,
acc
,
registry
,
metas
,
triangleIndex
);
}
}
}
}
private
static
void
appendPolygon
(
Polygon
p
,
Color
baseColor
,
int
lod
,
boolean
roof
,
MeshAccumulator
acc
,
PickRegistry
registry
,
List
<
TriangleMeta
>
metas
,
int
[]
triangleIndex
)
{
Color
base
=
chooseBase
(
p
,
baseColor
);
Color
shaded
=
ShadeColor
.
shade
(
p
.
calculateNormalNormalized
(),
base
);
float
r
=
(
float
)
shaded
.
getRed
();
float
g
=
(
float
)
shaded
.
getGreen
();
float
b
=
(
float
)
shaded
.
getBlue
();
int
id
=
registry
.
register
(
p
);
TesselatedPolygon
tp
=
p
.
tesselate
();
for
(
Triangle3d
t
:
tp
.
getTriangles
())
{
acc
.
addTriangle
(
t
,
id
,
r
,
g
,
b
);
metas
.
add
(
new
TriangleMeta
(
triangleIndex
[
0
]++,
p
,
lod
,
roof
));
}
}
private
static
Vector3d
findCenterOfModel
(
CityDoctorModel
model
)
{
double
xMin
=
Double
.
MAX_VALUE
,
yMin
=
Double
.
MAX_VALUE
,
zMin
=
Double
.
MAX_VALUE
;
double
xMax
=
-
Double
.
MAX_VALUE
,
yMax
=
-
Double
.
MAX_VALUE
,
zMax
=
-
Double
.
MAX_VALUE
;
boolean
any
=
false
;
for
(
CityObject
co
:
model
.
createFeatureStream
().
toList
())
{
CityObjectCollector
collector
=
new
CityObjectCollector
();
co
.
accept
(
collector
);
for
(
CityObject
inner
:
collector
.
getCityObjects
())
{
for
(
Geometry
geom
:
inner
.
getGeometries
())
{
for
(
Polygon
p
:
geom
.
getPolygons
())
{
if
(
p
.
isLink
())
{
continue
;
}
for
(
Vector3d
v
:
p
.
getExteriorRing
().
getVertices
())
{
xMin
=
Math
.
min
(
xMin
,
v
.
getX
());
xMax
=
Math
.
max
(
xMax
,
v
.
getX
());
yMin
=
Math
.
min
(
yMin
,
v
.
getY
());
yMax
=
Math
.
max
(
yMax
,
v
.
getY
());
zMin
=
Math
.
min
(
zMin
,
v
.
getZ
());
zMax
=
Math
.
max
(
zMax
,
v
.
getZ
());
any
=
true
;
}
}
}
}
}
if
(!
any
)
{
return
new
Vector3d
(
0
,
0
,
0
);
}
return
new
Vector3d
((
xMax
-
xMin
)
/
2
+
xMin
,
(
yMax
-
yMin
)
/
2
+
yMin
,
(
zMax
-
zMin
)
/
2
+
zMin
);
}
private
static
Color
chooseBase
(
Polygon
p
,
Color
fallback
)
{
Color
rc
=
p
.
getRenderColor
();
if
(
rc
!=
null
&&
!
rc
.
equals
(
Color
.
WHITE
))
{
return
rc
;
}
return
fallback
;
}
private
static
Vector3d
findCenter
(
Collection
<?
extends
Polygon
>
polygons
)
{
double
xMin
=
Double
.
MAX_VALUE
,
yMin
=
Double
.
MAX_VALUE
,
zMin
=
Double
.
MAX_VALUE
;
double
xMax
=
-
Double
.
MAX_VALUE
,
yMax
=
-
Double
.
MAX_VALUE
,
zMax
=
-
Double
.
MAX_VALUE
;
for
(
Polygon
p
:
polygons
)
{
for
(
Vector3d
v
:
p
.
getExteriorRing
().
getVertices
())
{
xMin
=
Math
.
min
(
xMin
,
v
.
getX
());
xMax
=
Math
.
max
(
xMax
,
v
.
getX
());
yMin
=
Math
.
min
(
yMin
,
v
.
getY
());
yMax
=
Math
.
max
(
yMax
,
v
.
getY
());
zMin
=
Math
.
min
(
zMin
,
v
.
getZ
());
zMax
=
Math
.
max
(
zMax
,
v
.
getZ
());
}
}
if
(
xMin
>
xMax
)
{
return
new
Vector3d
(
0
,
0
,
0
);
}
return
new
Vector3d
((
xMax
-
xMin
)
/
2
+
xMin
,
(
yMax
-
yMin
)
/
2
+
yMin
,
(
zMax
-
zMin
)
/
2
+
zMin
);
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/SceneData.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
java.util.List
;
/** Immutable CPU-side result of building the scene; uploaded to the GPU by the GL layer. */
public
class
SceneData
{
private
final
float
[]
positions
;
private
final
float
[]
colors
;
private
final
int
[]
ids
;
private
final
List
<
TriangleMeta
>
triangleMetas
;
private
final
PickRegistry
pickRegistry
;
private
final
double
[]
center
;
public
SceneData
(
float
[]
positions
,
float
[]
colors
,
int
[]
ids
,
List
<
TriangleMeta
>
triangleMetas
,
PickRegistry
pickRegistry
,
double
[]
center
)
{
this
.
positions
=
positions
;
this
.
colors
=
colors
;
this
.
ids
=
ids
;
this
.
triangleMetas
=
triangleMetas
;
this
.
pickRegistry
=
pickRegistry
;
this
.
center
=
center
;
}
public
float
[]
positions
()
{
return
positions
;
}
public
float
[]
colors
()
{
return
colors
;
}
public
int
[]
ids
()
{
return
ids
;
}
public
List
<
TriangleMeta
>
triangleMetas
()
{
return
triangleMetas
;
}
public
PickRegistry
pickRegistry
()
{
return
pickRegistry
;
}
public
double
[]
center
()
{
return
center
;
}
public
int
triangleCount
()
{
return
triangleMetas
.
size
();
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/ShadeColor.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
de.hft.stuttgart.citydoctor2.math.UnitVector3d
;
import
de.hft.stuttgart.citydoctor2.math.Vector3d
;
import
javafx.scene.paint.Color
;
/**
* Computes the surface-differentiation shade used by the legacy renderer: brightness derived from the
* angle between the polygon normal and a fixed axis. Not physical lighting; only makes adjacent faces
* distinguishable. Ported from {@code TriangulatedGeometry.calculateMaterial}.
*/
public
final
class
ShadeColor
{
private
static
final
UnitVector3d
AXIS
=
new
Vector3d
(
19
,
0.8
,
1.5
).
normalize
();
private
ShadeColor
()
{}
public
static
Color
shade
(
Vector3d
normal
,
Color
base
)
{
double
cos
=
normal
.
dot
(
AXIS
);
// clamp to acos domain; raw dot can drift slightly outside [-1,1] for near-parallel normals
cos
=
Math
.
max
(-
1.0
,
Math
.
min
(
1.0
,
cos
));
double
brightness
=
Math
.
acos
(
cos
)
/
Math
.
PI
*
0.6
+
0.3
;
// -> [0.3, 0.9]
return
base
.
deriveColor
(
0
,
1.0
,
brightness
,
1.0
);
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/ShaderProgram.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
com.jogamp.opengl.GL3
;
import
java.io.BufferedReader
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.InputStreamReader
;
import
java.io.UncheckedIOException
;
import
java.nio.charset.StandardCharsets
;
import
java.util.stream.Collectors
;
/** Compiles and links a GLSL program from classpath resources next to this class. */
public
class
ShaderProgram
{
private
final
int
program
;
public
ShaderProgram
(
GL3
gl
,
String
vertexResource
,
String
fragmentResource
)
{
this
(
gl
,
vertexResource
,
null
,
fragmentResource
);
}
/** Builds a program with an optional geometry stage ({@code geometryResource} may be null). */
public
ShaderProgram
(
GL3
gl
,
String
vertexResource
,
String
geometryResource
,
String
fragmentResource
)
{
int
vs
=
compile
(
gl
,
GL3
.
GL_VERTEX_SHADER
,
read
(
vertexResource
));
int
gs
=
geometryResource
!=
null
?
compile
(
gl
,
GL3
.
GL_GEOMETRY_SHADER
,
read
(
geometryResource
))
:
0
;
int
fs
=
compile
(
gl
,
GL3
.
GL_FRAGMENT_SHADER
,
read
(
fragmentResource
));
program
=
gl
.
glCreateProgram
();
gl
.
glAttachShader
(
program
,
vs
);
if
(
gs
!=
0
)
{
gl
.
glAttachShader
(
program
,
gs
);
}
gl
.
glAttachShader
(
program
,
fs
);
gl
.
glLinkProgram
(
program
);
checkLink
(
gl
,
program
);
gl
.
glDeleteShader
(
vs
);
if
(
gs
!=
0
)
{
gl
.
glDeleteShader
(
gs
);
}
gl
.
glDeleteShader
(
fs
);
}
public
void
use
(
GL3
gl
)
{
gl
.
glUseProgram
(
program
);
}
public
int
uniform
(
GL3
gl
,
String
name
)
{
return
gl
.
glGetUniformLocation
(
program
,
name
);
}
public
void
dispose
(
GL3
gl
)
{
gl
.
glDeleteProgram
(
program
);
}
private
static
int
compile
(
GL3
gl
,
int
type
,
String
src
)
{
int
shader
=
gl
.
glCreateShader
(
type
);
gl
.
glShaderSource
(
shader
,
1
,
new
String
[]{
src
},
new
int
[]{
src
.
length
()},
0
);
gl
.
glCompileShader
(
shader
);
int
[]
status
=
new
int
[
1
];
gl
.
glGetShaderiv
(
shader
,
GL3
.
GL_COMPILE_STATUS
,
status
,
0
);
if
(
status
[
0
]
==
0
)
{
throw
new
IllegalStateException
(
"Shader compile failed: "
+
infoLog
(
gl
,
shader
,
true
));
}
return
shader
;
}
private
static
void
checkLink
(
GL3
gl
,
int
program
)
{
int
[]
status
=
new
int
[
1
];
gl
.
glGetProgramiv
(
program
,
GL3
.
GL_LINK_STATUS
,
status
,
0
);
if
(
status
[
0
]
==
0
)
{
throw
new
IllegalStateException
(
"Program link failed: "
+
infoLog
(
gl
,
program
,
false
));
}
}
private
static
String
infoLog
(
GL3
gl
,
int
handle
,
boolean
shader
)
{
int
[]
len
=
new
int
[
1
];
if
(
shader
)
{
gl
.
glGetShaderiv
(
handle
,
GL3
.
GL_INFO_LOG_LENGTH
,
len
,
0
);
}
else
{
gl
.
glGetProgramiv
(
handle
,
GL3
.
GL_INFO_LOG_LENGTH
,
len
,
0
);
}
byte
[]
log
=
new
byte
[
Math
.
max
(
1
,
len
[
0
])];
if
(
shader
)
{
gl
.
glGetShaderInfoLog
(
handle
,
log
.
length
,
new
int
[
1
],
0
,
log
,
0
);
}
else
{
gl
.
glGetProgramInfoLog
(
handle
,
log
.
length
,
new
int
[
1
],
0
,
log
,
0
);
}
return
new
String
(
log
,
StandardCharsets
.
UTF_8
);
}
private
static
String
read
(
String
resource
)
{
try
(
InputStream
in
=
ShaderProgram
.
class
.
getResourceAsStream
(
resource
))
{
if
(
in
==
null
)
{
throw
new
IllegalStateException
(
"Shader resource not found: "
+
resource
);
}
try
(
BufferedReader
r
=
new
BufferedReader
(
new
InputStreamReader
(
in
,
StandardCharsets
.
UTF_8
)))
{
return
r
.
lines
().
collect
(
Collectors
.
joining
(
"\n"
));
}
}
catch
(
IOException
e
)
{
throw
new
UncheckedIOException
(
e
);
}
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/java/de/hft/stuttgart/citydoctor2/gui/gl/TriangleMeta.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
de.hft.stuttgart.citydoctor2.datastructure.Polygon
;
/**
* Metadata for one triangle in the global buffer.
* @param triangleIndex index of this triangle (vertices are 3*triangleIndex .. +2)
* @param polygon source polygon (for picking/highlight resolution); may be null in tests
* @param lod LOD ordinal (Lod.ordinal()) of the owning geometry
* @param roof true if the triangle belongs to a ROOF boundary surface
*/
public
record
TriangleMeta
(
int
triangleIndex
,
Polygon
polygon
,
int
lod
,
boolean
roof
)
{}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/resources/de/hft/stuttgart/citydoctor2/gui/gl/pick.frag
0 → 100644
View file @
854aab9b
#version 330 core
flat
in
int
vId
;
out
vec4
fragColor
;
void
main
()
{
int
id
=
vId
;
float
r
=
float
(
id
&
0xFF
)
/
255
.
0
;
float
g
=
float
((
id
>>
8
)
&
0xFF
)
/
255
.
0
;
float
b
=
float
((
id
>>
16
)
&
0xFF
)
/
255
.
0
;
fragColor
=
vec4
(
r
,
g
,
b
,
1
.
0
);
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/resources/de/hft/stuttgart/citydoctor2/gui/gl/pick.vert
0 → 100644
View file @
854aab9b
#version 330 core
layout
(
location
=
0
)
in
vec3
aPos
;
layout
(
location
=
2
)
in
int
aId
;
uniform
mat4
uMVP
;
flat
out
int
vId
;
void
main
()
{
gl_Position
=
uMVP
*
vec4
(
aPos
,
1
.
0
);
vId
=
aId
;
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/resources/de/hft/stuttgart/citydoctor2/gui/gl/point.frag
0 → 100644
View file @
854aab9b
#version 330 core
in
vec2
vQuad
;
in
vec3
vColor
;
out
vec4
fragColor
;
void
main
()
{
float
r2
=
dot
(
vQuad
,
vQuad
);
if
(
r2
>
1
.
0
)
{
discard
;
// outside the sphere's silhouette
}
fragColor
=
vec4
(
vColor
,
1
.
0
);
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/resources/de/hft/stuttgart/citydoctor2/gui/gl/point.geom
0 → 100644
View file @
854aab9b
#version 330 core
layout
(
points
)
in
;
layout
(
triangle_strip
,
max_vertices
=
4
)
out
;
in
vec3
vColorG
[];
uniform
vec2
uViewport
;
// framebuffer size in pixels
uniform
float
uPointSize
;
// sphere diameter in pixels
out
vec2
vQuad
;
// billboard-local coords in [-1, 1]
out
vec3
vColor
;
void
main
()
{
vec4
c
=
gl_in
[
0
].
gl_Position
;
if
(
c
.
w
<=
0
.
0
)
{
return
;
// behind the camera
}
// half-extent in clip space for a constant on-screen pixel size
vec2
h
=
vec2
(
uPointSize
/
uViewport
.
x
,
uPointSize
/
uViewport
.
y
)
*
c
.
w
;
vColor
=
vColorG
[
0
];
gl_Position
=
c
+
vec4
(
-
h
.
x
,
-
h
.
y
,
0
.
0
,
0
.
0
);
vQuad
=
vec2
(
-
1
.
0
,
-
1
.
0
);
EmitVertex
();
gl_Position
=
c
+
vec4
(
h
.
x
,
-
h
.
y
,
0
.
0
,
0
.
0
);
vQuad
=
vec2
(
1
.
0
,
-
1
.
0
);
EmitVertex
();
gl_Position
=
c
+
vec4
(
-
h
.
x
,
h
.
y
,
0
.
0
,
0
.
0
);
vQuad
=
vec2
(
-
1
.
0
,
1
.
0
);
EmitVertex
();
gl_Position
=
c
+
vec4
(
h
.
x
,
h
.
y
,
0
.
0
,
0
.
0
);
vQuad
=
vec2
(
1
.
0
,
1
.
0
);
EmitVertex
();
EndPrimitive
();
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/resources/de/hft/stuttgart/citydoctor2/gui/gl/point.vert
0 → 100644
View file @
854aab9b
#version 330 core
layout
(
location
=
0
)
in
vec3
aPos
;
layout
(
location
=
1
)
in
vec3
aColor
;
uniform
mat4
uMVP
;
out
vec3
vColorG
;
void
main
()
{
gl_Position
=
uMVP
*
vec4
(
aPos
,
1
.
0
);
vColorG
=
aColor
;
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/resources/de/hft/stuttgart/citydoctor2/gui/gl/scene.frag
0 → 100644
View file @
854aab9b
#version 330 core
in
vec3
vColor
;
out
vec4
fragColor
;
void
main
()
{
fragColor
=
vec4
(
vColor
,
1
.
0
);
}
CityDoctorParent/Extensions/CityDoctorGUI/src/main/resources/de/hft/stuttgart/citydoctor2/gui/gl/scene.vert
0 → 100644
View file @
854aab9b
#version 330 core
layout
(
location
=
0
)
in
vec3
aPos
;
layout
(
location
=
1
)
in
vec3
aColor
;
uniform
mat4
uMVP
;
out
vec3
vColor
;
void
main
()
{
gl_Position
=
uMVP
*
vec4
(
aPos
,
1
.
0
);
vColor
=
aColor
;
}
CityDoctorParent/Extensions/CityDoctorGUI/src/test/java/de/hft/stuttgart/citydoctor2/gui/gl/FloatListTest.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
static
org
.
junit
.
Assert
.
assertArrayEquals
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
org.junit.Test
;
public
class
FloatListTest
{
@Test
public
void
growsBeyondInitialCapacityAndPreservesOrder
()
{
FloatList
list
=
new
FloatList
(
2
);
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
list
.
add
(
i
);
}
assertEquals
(
10
,
list
.
size
());
float
[]
expected
=
{
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
};
assertArrayEquals
(
expected
,
list
.
toArray
(),
0.0f
);
}
@Test
public
void
addThreeAddsAllInOrder
()
{
FloatList
list
=
new
FloatList
(
1
);
list
.
add
(
1
f
,
2
f
,
3
f
);
assertArrayEquals
(
new
float
[]{
1
,
2
,
3
},
list
.
toArray
(),
0.0f
);
}
@Test
public
void
clearResetsSizeAndArray
()
{
FloatList
list
=
new
FloatList
(
4
);
list
.
add
(
1
f
,
2
f
,
3
f
);
list
.
add
(
4
f
);
list
.
clear
();
assertEquals
(
0
,
list
.
size
());
assertEquals
(
0
,
list
.
toArray
().
length
);
}
}
CityDoctorParent/Extensions/CityDoctorGUI/src/test/java/de/hft/stuttgart/citydoctor2/gui/gl/IndexSetBuilderTest.java
0 → 100644
View file @
854aab9b
package
de.hft.stuttgart.citydoctor2.gui.gl
;
import
static
org
.
junit
.
Assert
.
assertArrayEquals
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.function.Predicate
;
import
org.junit.Test
;
public
class
IndexSetBuilderTest
{
@Test
public
void
includesOnlyTrianglesMatchingThePredicate
()
{
List
<
TriangleMeta
>
metas
=
Arrays
.
asList
(
new
TriangleMeta
(
0
,
null
,
1
,
false
),
new
TriangleMeta
(
1
,
null
,
2
,
false
),
new
TriangleMeta
(
2
,
null
,
1
,
false
));
Predicate
<
TriangleMeta
>
onlyGroup1
=
m
->
m
.
lod
()
==
1
;
int
[]
indices
=
IndexSetBuilder
.
build
(
metas
,
onlyGroup1
);
assertArrayEquals
(
new
int
[]{
0
,
1
,
2
,
6
,
7
,
8
},
indices
);
}
@Test
public
void
emptyWhenNothingMatches
()
{
List
<
TriangleMeta
>
metas
=
Arrays
.
asList
(
new
TriangleMeta
(
0
,
null
,
1
,
false
));
int
[]
indices
=
IndexSetBuilder
.
build
(
metas
,
m
->
false
);
assertArrayEquals
(
new
int
[]{},
indices
);
}
}
Prev
1
2
3
Next
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