Skip to content

Commit

Permalink
feature/render-mesh
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
lessthanoptimal committed Jun 10, 2024
1 parent 48705cc commit dffd343
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 130 deletions.
34 changes: 25 additions & 9 deletions applications/src/main/java/boofcv/app/MeshViewerApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,23 +72,39 @@ private static void loadFile( File file ) {
}

// See if there should be a texture mapped file
InterleavedU8 rgb = null;
if (mesh.texture.size() > 0) {
InterleavedU8 textureImage = null;
escape:if (mesh.texture.size() > 0) {
System.out.println("Loading texture image");
String name = FilenameUtils.getBaseName(file.getName());
File textureFile = new File(file.getParentFile(), name + ".jpg");
rgb = UtilImageIO.loadImage(textureFile, true, ImageType.IL_U8);
if (rgb == null)
System.err.println("Failed to load texture image");
// try to load an image with the same basename
File[] children = file.getParentFile().listFiles();
if (children == null) {
break escape;
}

for (File child : children) {
// see if this file starts with the same name as the mesh file
if (!child.getName().startsWith(name))
continue;

// skip if it's not an image
if (!UtilImageIO.isImage(child))
continue;

textureImage = UtilImageIO.loadImage(child, true, ImageType.IL_U8);
if (textureImage != null)
break;
}
}
InterleavedU8 _rgb = rgb;

InterleavedU8 _image = textureImage;
SwingUtilities.invokeLater(() -> {
var panel = new MeshViewerPanel();
panel.setMesh(mesh, false);
if (colors.size > 0)
panel.setVertexColors("RGB", colors.data);
if (_rgb != null)
panel.setTextureImage(_rgb);
if (_image != null)
panel.setTextureImage(_image);
panel.setPreferredSize(new Dimension(500, 500));
ShowImages.showWindow(panel, "Mesh Viewer", true);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ private void renderLoop() {
}

// Stop it from sucking up all the CPU
Thread.yield();
BoofMiscOps.sleep(10);

// Can't sleep or wait here. That requires interrupting the thread to wake it up, unfortunately
// that conflicts with the concurrency code and interrupts that too
Expand Down Expand Up @@ -374,17 +374,31 @@ public void setHorizontalFov( double degrees ) {
* Each time this is called it will change the colorizer being used, if more than one has been specified
*/
public void cycleColorizer() {
int totalColors = colorizers.size();
if (mesh.isTextured()) {
totalColors++;
}

synchronized (colorizers) {
var list = new ArrayList<>(colorizers.keySet());

// go to the next one and make sure it's valid
activeColorizer++;
if (activeColorizer >= list.size()) {
if (activeColorizer >= totalColors) {
activeColorizer = 0;
}

// Change the colorizer
renderer.surfaceColor = Objects.requireNonNull(colorizers.get(list.get(activeColorizer)));
// If it has texture then that is the first possible colorizer
if (mesh.isTextured()) {
if (activeColorizer == 0) {
renderer.forceColorizer = false;
} else {
renderer.forceColorizer = true;
renderer.surfaceColor = Objects.requireNonNull(colorizers.get(list.get(activeColorizer - 1)));
}
} else {
renderer.surfaceColor = Objects.requireNonNull(colorizers.get(list.get(activeColorizer)));
}

// Re-render the image
requestRender();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Peter Abeles. All Rights Reserved.
* Copyright (c) 2024, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
Expand All @@ -21,16 +21,12 @@
import boofcv.alg.geo.PerspectiveOps;
import boofcv.struct.calib.CameraPinhole;
import georegression.geometry.ConvertRotation3D_F64;
import georegression.geometry.GeometryMath_F64;
import georegression.metric.UtilAngle;
import georegression.struct.EulerType;
import georegression.struct.point.Point2D_F64;
import georegression.struct.point.Point3D_F64;
import georegression.struct.point.Vector3D_F64;
import georegression.struct.se.Se3_F64;
import lombok.Getter;
import org.ejml.data.DMatrixRMaj;
import org.ejml.dense.row.CommonOps_DDRM;

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

DMatrixRMaj localRotation = new DMatrixRMaj(3, 3);
DMatrixRMaj rotationAroundTarget = new DMatrixRMaj(3, 3);
DMatrixRMaj tmp = new DMatrixRMaj(3, 3);

// Translation applied after the orbit has been done
Vector3D_F64 translateWorld = new Vector3D_F64();

// Point it's orbiting around
Point3D_F64 targetPoint = new Point3D_F64();

// Adjustment applied to distance from target point in final transform
double radiusScale = 1.0;

Point3D_F64 cameraLoc = new Point3D_F64();
// Point it's orbiting around in world coordinates
private Point3D_F64 targetWorld = new Point3D_F64();

// Storage for normalized image coordinates
Point2D_F64 norm1 = new Point2D_F64();
Point2D_F64 norm2 = new Point2D_F64();

Point3D_F64 targetInView = new Point3D_F64();
Point3D_F64 targetInOld = new Point3D_F64();
Se3_F64 viewToPoint = new Se3_F64();
Se3_F64 viewToPoint2 = new Se3_F64();
Se3_F64 worldToPoint = new Se3_F64();
Se3_F64 pointToRot = new Se3_F64();
Se3_F64 oldToNew = new Se3_F64();

public OrbitAroundPoint() {
resetView();
}

public void setTarget( double x, double y, double z ) {
targetWorld.setTo(x, y, z);

updateAfterExternalChange();
}

public void resetView() {
radiusScale = 1.0;
translateWorld.zero();
CommonOps_DDRM.setIdentity(rotationAroundTarget);
worldToView.reset();

// See if the principle point and the target are on top of each other
updateAfterExternalChange();
}

/**
* After an external change to the transform or target, make sure it's pointed at the target and a reasonable
* distance.
*/
private void updateAfterExternalChange() {
// See if the principle point and the target are on top of each other
if (Math.abs(worldToView.T.distance(targetWorld)) < 1e-16) {
// back the camera off a little bit
worldToView.T.z += 0.1;
}

pointAtTarget();
}

public void updateTransform() {
// Compute location of camera principle point with no rotation in target point reference frame
cameraLoc.x = -targetPoint.x*radiusScale;
cameraLoc.y = -targetPoint.y*radiusScale;
cameraLoc.z = -targetPoint.z*radiusScale;

// Apply rotation
GeometryMath_F64.mult(rotationAroundTarget, cameraLoc, cameraLoc);

// Compute the full transform
worldToView.T.setTo(
cameraLoc.x + targetPoint.x + translateWorld.x,
cameraLoc.y + targetPoint.y + translateWorld.y,
cameraLoc.z + targetPoint.z + translateWorld.z);
worldToView.R.setTo(rotationAroundTarget);
/**
* Points the camera at the target point
*/
private void pointAtTarget() {
oldToNew.reset();

worldToView.transform(targetWorld, targetInView);
PerspectiveOps.pointAt(targetInView.x, targetInView.y, targetInView.z, oldToNew.R);

Se3_F64 tmp = pointToRot;
worldToView.concatInvert(oldToNew, tmp);
worldToView.setTo(tmp);
}

public void mouseWheel( double ticks, double scale ) {
radiusScale = Math.max(0.005, radiusScale*(1.0 + 0.02*ticks*scale));
worldToView.transform(targetWorld, targetInView);

// How much it will move towards or away from the target
worldToView.T.z += targetInView.norm()*0.02*ticks*scale;

// Because it's moving towards the point it won't need to adjust its angle
}

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

// Set the local rotation
ConvertRotation3D_F64.eulerToMatrix(EulerType.XYZ, -rotY, rotX, 0, localRotation);

// Update the global rotation
CommonOps_DDRM.mult(localRotation, rotationAroundTarget, tmp);
rotationAroundTarget.setTo(tmp);
applyLocalEuler(rotX, -rotY, 0.0);
}

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

// convert into normalize image coordinates
PerspectiveOps.convertPixelToNorm(camera, x0, y0, norm1);
PerspectiveOps.convertPixelToNorm(camera, x1, y1, norm2);
worldToView.transform(targetWorld, targetInOld);

viewToPoint.T.setTo(-targetInOld.x, -targetInOld.y, -targetInOld.z);

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

translateWorld.x += (norm2.x - norm1.x)*z;
translateWorld.y += (norm2.y - norm1.y)*z;
// Set the local rotation
ConvertRotation3D_F64.eulerToMatrix(EulerType.XYZ, rotY, rotX, rotZ, pointToRot.R);

viewToPoint.concat(pointToRot, viewToPoint2);
worldToPoint.concatInvert(viewToPoint2, worldToView);

pointAtTarget();
}

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

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

// Perform roll around the z-axis
// Roll the camera
double rotX = UtilAngle.minus(Math.atan(norm1.x), Math.atan(norm2.x));
ConvertRotation3D_F64.eulerToMatrix(EulerType.XYZ, 0, 0, -rotX, localRotation);
CommonOps_DDRM.mult(localRotation, rotationAroundTarget, tmp);
rotationAroundTarget.setTo(tmp);

applyLocalEuler(0, 0, rotX);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Peter Abeles. All Rights Reserved.
* Copyright (c) 2024, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
Expand Down Expand Up @@ -76,7 +76,7 @@ public class OrbitAroundPointControl extends MouseAdapter implements Swing3dCame

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

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

synchronized (orbit) {
var target = new Point3D_F64();
for (int axis = 0; axis < 3; axis++) {
int _axis = axis;
sampled.forIdx(( idx, v ) -> values.set(idx, v.getIdx(_axis)));
values.sort();
orbit.targetPoint.setIdx(axis, values.getFraction(0.5));
target.setIdx(axis, values.getFraction(0.5));
}
orbit.setTarget(target.x, target.y, target.z);
}
}

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

@Override public Se3_F64 getWorldToCamera() {
synchronized (orbit) {
orbit.updateTransform();
return orbit.worldToView.copy();
}
}
Expand All @@ -134,9 +135,7 @@ public class OrbitAroundPointControl extends MouseAdapter implements Swing3dCame

@Override public void mouseDragged( MouseEvent e ) {
synchronized (orbit) {
if (e.isShiftDown() || SwingUtilities.isMiddleMouseButton(e))
orbit.mouseDragTranslate(prevX, prevY, e.getX(), e.getY());
else if (e.isControlDown() || SwingUtilities.isRightMouseButton(e))
if (e.isControlDown() || SwingUtilities.isRightMouseButton(e))
orbit.mouseDragZoomRoll(prevX, prevY, e.getX(), e.getY());
else
orbit.mouseDragRotate(prevX, prevY, e.getX(), e.getY());
Expand Down
18 changes: 15 additions & 3 deletions main/boofcv-geo/src/main/java/boofcv/alg/geo/PerspectiveOps.java
Original file line number Diff line number Diff line change
Expand Up @@ -1178,19 +1178,27 @@ public static DMatrixRMaj pointAt( double x, double y, double z, @Nullable DMatr
// There's a pathological case. Pick the option which if farthest from it
if (Math.abs(GeometryMath_F64.dot(axisX, axisZ)) < Math.abs(GeometryMath_F64.dot(axisY, axisZ))) {
GeometryMath_F64.cross(axisX, axisZ, axisY);
// There are two options here, pick the one that will result in the smallest rotation
if (dot(axisY,0,1,0) < 0)
axisY.scale(-1);

axisY.divideIP(axisY.norm());
GeometryMath_F64.cross(axisY, axisZ, axisX);
axisX.divideIP(axisX.norm());
} else {
GeometryMath_F64.cross(axisY, axisZ, axisX);
axisX.divideIP(axisX.norm());
GeometryMath_F64.cross(axisX, axisZ, axisY);
if (dot(axisX,1,0,0) < 0)
axisX.scale(-1);

GeometryMath_F64.cross(axisZ, axisX, axisY);
axisY.divideIP(axisY.norm());
}


if (R == null)
R = new DMatrixRMaj(3,3);
R = new DMatrixRMaj(3, 3);

R.set(0, 0, axisX.x);
R.set(1, 0, axisX.y);
R.set(2, 0, axisX.z);
Expand All @@ -1203,4 +1211,8 @@ public static DMatrixRMaj pointAt( double x, double y, double z, @Nullable DMatr

return R;
}

private static double dot(Point3D_F64 p, double x, double y , double z) {
return p.x*x + p.y*y + p.z*z;
}
}
Loading

0 comments on commit dffd343

Please sign in to comment.