Skip to content

Commit 371e10d

Browse files
feature/render-mesh
- Fixed bug in pointAt() - OrbitAroundPoint has much better behavior and actually shows the mesh - MeshViewerApp can now cycle through different colorization methods with texture on
1 parent 043d300 commit 371e10d

File tree

10 files changed

+331
-130
lines changed

10 files changed

+331
-130
lines changed

applications/src/main/java/boofcv/app/MeshViewerApp.java

+25-9
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,39 @@ private static void loadFile( File file ) {
7272
}
7373

7474
// See if there should be a texture mapped file
75-
InterleavedU8 rgb = null;
76-
if (mesh.texture.size() > 0) {
75+
InterleavedU8 textureImage = null;
76+
escape:if (mesh.texture.size() > 0) {
7777
System.out.println("Loading texture image");
7878
String name = FilenameUtils.getBaseName(file.getName());
79-
File textureFile = new File(file.getParentFile(), name + ".jpg");
80-
rgb = UtilImageIO.loadImage(textureFile, true, ImageType.IL_U8);
81-
if (rgb == null)
82-
System.err.println("Failed to load texture image");
79+
// try to load an image with the same basename
80+
File[] children = file.getParentFile().listFiles();
81+
if (children == null) {
82+
break escape;
83+
}
84+
85+
for (File child : children) {
86+
// see if this file starts with the same name as the mesh file
87+
if (!child.getName().startsWith(name))
88+
continue;
89+
90+
// skip if it's not an image
91+
if (!UtilImageIO.isImage(child))
92+
continue;
93+
94+
textureImage = UtilImageIO.loadImage(child, true, ImageType.IL_U8);
95+
if (textureImage != null)
96+
break;
97+
}
8398
}
84-
InterleavedU8 _rgb = rgb;
99+
100+
InterleavedU8 _image = textureImage;
85101
SwingUtilities.invokeLater(() -> {
86102
var panel = new MeshViewerPanel();
87103
panel.setMesh(mesh, false);
88104
if (colors.size > 0)
89105
panel.setVertexColors("RGB", colors.data);
90-
if (_rgb != null)
91-
panel.setTextureImage(_rgb);
106+
if (_image != null)
107+
panel.setTextureImage(_image);
92108
panel.setPreferredSize(new Dimension(500, 500));
93109
ShowImages.showWindow(panel, "Mesh Viewer", true);
94110
});

integration/boofcv-swing/src/main/java/boofcv/gui/mesh/MeshViewerPanel.java

+18-4
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ private void renderLoop() {
281281
}
282282

283283
// Stop it from sucking up all the CPU
284-
Thread.yield();
284+
BoofMiscOps.sleep(10);
285285

286286
// Can't sleep or wait here. That requires interrupting the thread to wake it up, unfortunately
287287
// that conflicts with the concurrency code and interrupts that too
@@ -374,17 +374,31 @@ public void setHorizontalFov( double degrees ) {
374374
* Each time this is called it will change the colorizer being used, if more than one has been specified
375375
*/
376376
public void cycleColorizer() {
377+
int totalColors = colorizers.size();
378+
if (mesh.isTextured()) {
379+
totalColors++;
380+
}
381+
377382
synchronized (colorizers) {
378383
var list = new ArrayList<>(colorizers.keySet());
379384

380385
// go to the next one and make sure it's valid
381386
activeColorizer++;
382-
if (activeColorizer >= list.size()) {
387+
if (activeColorizer >= totalColors) {
383388
activeColorizer = 0;
384389
}
385390

386-
// Change the colorizer
387-
renderer.surfaceColor = Objects.requireNonNull(colorizers.get(list.get(activeColorizer)));
391+
// If it has texture then that is the first possible colorizer
392+
if (mesh.isTextured()) {
393+
if (activeColorizer == 0) {
394+
renderer.forceColorizer = false;
395+
} else {
396+
renderer.forceColorizer = true;
397+
renderer.surfaceColor = Objects.requireNonNull(colorizers.get(list.get(activeColorizer - 1)));
398+
}
399+
} else {
400+
renderer.surfaceColor = Objects.requireNonNull(colorizers.get(list.get(activeColorizer)));
401+
}
388402

389403
// Re-render the image
390404
requestRender();
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2024, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -21,16 +21,12 @@
2121
import boofcv.alg.geo.PerspectiveOps;
2222
import boofcv.struct.calib.CameraPinhole;
2323
import georegression.geometry.ConvertRotation3D_F64;
24-
import georegression.geometry.GeometryMath_F64;
2524
import georegression.metric.UtilAngle;
2625
import georegression.struct.EulerType;
2726
import georegression.struct.point.Point2D_F64;
2827
import georegression.struct.point.Point3D_F64;
29-
import georegression.struct.point.Vector3D_F64;
3028
import georegression.struct.se.Se3_F64;
3129
import lombok.Getter;
32-
import org.ejml.data.DMatrixRMaj;
33-
import org.ejml.dense.row.CommonOps_DDRM;
3430

3531
/**
3632
* Contains the mathematics for controlling a camera by orbiting around a point in 3D space
@@ -44,54 +40,73 @@ public class OrbitAroundPoint {
4440
/** Transform from world to camera view reference frames */
4541
Se3_F64 worldToView = new Se3_F64();
4642

47-
DMatrixRMaj localRotation = new DMatrixRMaj(3, 3);
48-
DMatrixRMaj rotationAroundTarget = new DMatrixRMaj(3, 3);
49-
DMatrixRMaj tmp = new DMatrixRMaj(3, 3);
50-
51-
// Translation applied after the orbit has been done
52-
Vector3D_F64 translateWorld = new Vector3D_F64();
53-
54-
// Point it's orbiting around
55-
Point3D_F64 targetPoint = new Point3D_F64();
56-
57-
// Adjustment applied to distance from target point in final transform
58-
double radiusScale = 1.0;
59-
60-
Point3D_F64 cameraLoc = new Point3D_F64();
43+
// Point it's orbiting around in world coordinates
44+
private Point3D_F64 targetWorld = new Point3D_F64();
6145

6246
// Storage for normalized image coordinates
6347
Point2D_F64 norm1 = new Point2D_F64();
6448
Point2D_F64 norm2 = new Point2D_F64();
6549

50+
Point3D_F64 targetInView = new Point3D_F64();
51+
Point3D_F64 targetInOld = new Point3D_F64();
52+
Se3_F64 viewToPoint = new Se3_F64();
53+
Se3_F64 viewToPoint2 = new Se3_F64();
54+
Se3_F64 worldToPoint = new Se3_F64();
55+
Se3_F64 pointToRot = new Se3_F64();
56+
Se3_F64 oldToNew = new Se3_F64();
57+
6658
public OrbitAroundPoint() {
6759
resetView();
6860
}
6961

62+
public void setTarget( double x, double y, double z ) {
63+
targetWorld.setTo(x, y, z);
64+
65+
updateAfterExternalChange();
66+
}
67+
7068
public void resetView() {
71-
radiusScale = 1.0;
72-
translateWorld.zero();
73-
CommonOps_DDRM.setIdentity(rotationAroundTarget);
69+
worldToView.reset();
70+
71+
// See if the principle point and the target are on top of each other
72+
updateAfterExternalChange();
73+
}
74+
75+
/**
76+
* After an external change to the transform or target, make sure it's pointed at the target and a reasonable
77+
* distance.
78+
*/
79+
private void updateAfterExternalChange() {
80+
// See if the principle point and the target are on top of each other
81+
if (Math.abs(worldToView.T.distance(targetWorld)) < 1e-16) {
82+
// back the camera off a little bit
83+
worldToView.T.z += 0.1;
84+
}
85+
86+
pointAtTarget();
7487
}
7588

76-
public void updateTransform() {
77-
// Compute location of camera principle point with no rotation in target point reference frame
78-
cameraLoc.x = -targetPoint.x*radiusScale;
79-
cameraLoc.y = -targetPoint.y*radiusScale;
80-
cameraLoc.z = -targetPoint.z*radiusScale;
81-
82-
// Apply rotation
83-
GeometryMath_F64.mult(rotationAroundTarget, cameraLoc, cameraLoc);
84-
85-
// Compute the full transform
86-
worldToView.T.setTo(
87-
cameraLoc.x + targetPoint.x + translateWorld.x,
88-
cameraLoc.y + targetPoint.y + translateWorld.y,
89-
cameraLoc.z + targetPoint.z + translateWorld.z);
90-
worldToView.R.setTo(rotationAroundTarget);
89+
/**
90+
* Points the camera at the target point
91+
*/
92+
private void pointAtTarget() {
93+
oldToNew.reset();
94+
95+
worldToView.transform(targetWorld, targetInView);
96+
PerspectiveOps.pointAt(targetInView.x, targetInView.y, targetInView.z, oldToNew.R);
97+
98+
Se3_F64 tmp = pointToRot;
99+
worldToView.concatInvert(oldToNew, tmp);
100+
worldToView.setTo(tmp);
91101
}
92102

93103
public void mouseWheel( double ticks, double scale ) {
94-
radiusScale = Math.max(0.005, radiusScale*(1.0 + 0.02*ticks*scale));
104+
worldToView.transform(targetWorld, targetInView);
105+
106+
// How much it will move towards or away from the target
107+
worldToView.T.z += targetInView.norm()*0.02*ticks*scale;
108+
109+
// Because it's moving towards the point it won't need to adjust its angle
95110
}
96111

97112
public void mouseDragRotate( double x0, double y0, double x1, double y1 ) {
@@ -107,31 +122,29 @@ public void mouseDragRotate( double x0, double y0, double x1, double y1 ) {
107122
double rotX = UtilAngle.minus(Math.atan(norm1.x), Math.atan(norm2.x));
108123
double rotY = UtilAngle.minus(Math.atan(norm1.y), Math.atan(norm2.y));
109124

110-
// Set the local rotation
111-
ConvertRotation3D_F64.eulerToMatrix(EulerType.XYZ, -rotY, rotX, 0, localRotation);
112-
113-
// Update the global rotation
114-
CommonOps_DDRM.mult(localRotation, rotationAroundTarget, tmp);
115-
rotationAroundTarget.setTo(tmp);
125+
applyLocalEuler(rotX, -rotY, 0.0);
116126
}
117127

118128
/**
119-
* Uses mouse drag motion to translate the view
129+
* Applies a X Y Z Euler rotation in the point's reference frame so that it will appear to be rotating
130+
* around that point
120131
*/
121-
public void mouseDragTranslate( double x0, double y0, double x1, double y1 ) {
122-
// do nothing if the camera isn't configured yet
123-
if (camera.fx == 0.0 || camera.fy == 0.0)
124-
return;
132+
private void applyLocalEuler( double rotX, double rotY, double rotZ ) {
133+
pointToRot.reset();
125134

126-
// convert into normalize image coordinates
127-
PerspectiveOps.convertPixelToNorm(camera, x0, y0, norm1);
128-
PerspectiveOps.convertPixelToNorm(camera, x1, y1, norm2);
135+
worldToView.transform(targetWorld, targetInOld);
136+
137+
viewToPoint.T.setTo(-targetInOld.x, -targetInOld.y, -targetInOld.z);
129138

130-
// Figure out the distance along the projection at the plane at the distance of the target point
131-
double z = targetPoint.plus(translateWorld).norm()*radiusScale;
139+
worldToView.concat(viewToPoint, worldToPoint);
132140

133-
translateWorld.x += (norm2.x - norm1.x)*z;
134-
translateWorld.y += (norm2.y - norm1.y)*z;
141+
// Set the local rotation
142+
ConvertRotation3D_F64.eulerToMatrix(EulerType.XYZ, rotY, rotX, rotZ, pointToRot.R);
143+
144+
viewToPoint.concat(pointToRot, viewToPoint2);
145+
worldToPoint.concatInvert(viewToPoint2, worldToView);
146+
147+
pointAtTarget();
135148
}
136149

137150
/**
@@ -147,14 +160,11 @@ public void mouseDragZoomRoll( double x0, double y0, double x1, double y1 ) {
147160
PerspectiveOps.convertPixelToNorm(camera, x1, y1, norm2);
148161

149162
// Zoom in and out using the mouse
150-
double z = targetPoint.plus(translateWorld).norm()*radiusScale;
151-
translateWorld.z += (norm2.y - norm1.y)*z;
163+
worldToView.transform(targetWorld, targetInView);
164+
worldToView.T.z += (norm2.y - norm1.y)*targetInView.norm();
152165

153-
// Perform roll around the z-axis
166+
// Roll the camera
154167
double rotX = UtilAngle.minus(Math.atan(norm1.x), Math.atan(norm2.x));
155-
ConvertRotation3D_F64.eulerToMatrix(EulerType.XYZ, 0, 0, -rotX, localRotation);
156-
CommonOps_DDRM.mult(localRotation, rotationAroundTarget, tmp);
157-
rotationAroundTarget.setTo(tmp);
158-
168+
applyLocalEuler(0, 0, rotX);
159169
}
160170
}

integration/boofcv-swing/src/main/java/boofcv/gui/mesh/OrbitAroundPointControl.java

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, Peter Abeles. All Rights Reserved.
2+
* Copyright (c) 2024, Peter Abeles. All Rights Reserved.
33
*
44
* This file is part of BoofCV (http://boofcv.org).
55
*
@@ -76,7 +76,7 @@ public class OrbitAroundPointControl extends MouseAdapter implements Swing3dCame
7676

7777
// Give tell it to look in front of the camera if there is nothing to look at
7878
if (N == 0) {
79-
orbit.targetPoint.setTo(0, 0, 1);
79+
orbit.setTarget(0, 0, 1);
8080
return;
8181
}
8282

@@ -94,12 +94,14 @@ public class OrbitAroundPointControl extends MouseAdapter implements Swing3dCame
9494
values.resize(sampled.size);
9595

9696
synchronized (orbit) {
97+
var target = new Point3D_F64();
9798
for (int axis = 0; axis < 3; axis++) {
9899
int _axis = axis;
99100
sampled.forIdx(( idx, v ) -> values.set(idx, v.getIdx(_axis)));
100101
values.sort();
101-
orbit.targetPoint.setIdx(axis, values.getFraction(0.5));
102+
target.setIdx(axis, values.getFraction(0.5));
102103
}
104+
orbit.setTarget(target.x, target.y, target.z);
103105
}
104106
}
105107

@@ -111,7 +113,6 @@ public class OrbitAroundPointControl extends MouseAdapter implements Swing3dCame
111113

112114
@Override public Se3_F64 getWorldToCamera() {
113115
synchronized (orbit) {
114-
orbit.updateTransform();
115116
return orbit.worldToView.copy();
116117
}
117118
}
@@ -134,9 +135,7 @@ public class OrbitAroundPointControl extends MouseAdapter implements Swing3dCame
134135

135136
@Override public void mouseDragged( MouseEvent e ) {
136137
synchronized (orbit) {
137-
if (e.isShiftDown() || SwingUtilities.isMiddleMouseButton(e))
138-
orbit.mouseDragTranslate(prevX, prevY, e.getX(), e.getY());
139-
else if (e.isControlDown() || SwingUtilities.isRightMouseButton(e))
138+
if (e.isControlDown() || SwingUtilities.isRightMouseButton(e))
140139
orbit.mouseDragZoomRoll(prevX, prevY, e.getX(), e.getY());
141140
else
142141
orbit.mouseDragRotate(prevX, prevY, e.getX(), e.getY());

main/boofcv-geo/src/main/java/boofcv/alg/geo/PerspectiveOps.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -1178,19 +1178,27 @@ public static DMatrixRMaj pointAt( double x, double y, double z, @Nullable DMatr
11781178
// There's a pathological case. Pick the option which if farthest from it
11791179
if (Math.abs(GeometryMath_F64.dot(axisX, axisZ)) < Math.abs(GeometryMath_F64.dot(axisY, axisZ))) {
11801180
GeometryMath_F64.cross(axisX, axisZ, axisY);
1181+
// There are two options here, pick the one that will result in the smallest rotation
1182+
if (dot(axisY,0,1,0) < 0)
1183+
axisY.scale(-1);
1184+
11811185
axisY.divideIP(axisY.norm());
11821186
GeometryMath_F64.cross(axisY, axisZ, axisX);
11831187
axisX.divideIP(axisX.norm());
11841188
} else {
11851189
GeometryMath_F64.cross(axisY, axisZ, axisX);
11861190
axisX.divideIP(axisX.norm());
1187-
GeometryMath_F64.cross(axisX, axisZ, axisY);
1191+
if (dot(axisX,1,0,0) < 0)
1192+
axisX.scale(-1);
1193+
1194+
GeometryMath_F64.cross(axisZ, axisX, axisY);
11881195
axisY.divideIP(axisY.norm());
11891196
}
11901197

1198+
11911199
if (R == null)
1192-
R = new DMatrixRMaj(3,3);
1193-
1200+
R = new DMatrixRMaj(3, 3);
1201+
11941202
R.set(0, 0, axisX.x);
11951203
R.set(1, 0, axisX.y);
11961204
R.set(2, 0, axisX.z);
@@ -1203,4 +1211,8 @@ public static DMatrixRMaj pointAt( double x, double y, double z, @Nullable DMatr
12031211

12041212
return R;
12051213
}
1214+
1215+
private static double dot(Point3D_F64 p, double x, double y , double z) {
1216+
return p.x*x + p.y*y + p.z*z;
1217+
}
12061218
}

0 commit comments

Comments
 (0)