diff --git a/build.gradle.kts b/build.gradle.kts index efec366dd..bfa2aef5a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { ihmc { group = "us.ihmc" - version = "alpha-20191117" + version = "alpha-20191122" vcsUrl = "https://github.com/ihmcrobotics/simulation-construction-set-2" openSource = true diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/JavaFXPictureConverter.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/JavaFXPictureConverter.java new file mode 100644 index 000000000..bd1a197fb --- /dev/null +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/JavaFXPictureConverter.java @@ -0,0 +1,89 @@ +package us.ihmc.scs2.sessionVisualizer.session.log; + +import java.nio.ByteBuffer; + +import javafx.scene.image.PixelWriter; +import javafx.scene.image.WritableImage; +import us.ihmc.codecs.generated.RGBPicture; +import us.ihmc.codecs.generated.YUVPicture; +import us.ihmc.codecs.loader.NativeLibraryLoader; +import us.ihmc.codecs.util.ByteBufferProvider; + +public class JavaFXPictureConverter +{ + static + { + NativeLibraryLoader.loadIHMCVideoCodecsLibrary(); + } + + private ByteBufferProvider byteBufferProvider = new ByteBufferProvider(); + + /** + * Convert YUVPicture to BufferedImage, minimizing object allocation + * + * @param picture YUVPicture to convert + * @return new BufferedImage. + */ + public WritableImage toFXImage(YUVPicture picture) + { + return toFXImage(picture, null); + } + + /** + * Convert YUVPicture to BufferedImage, minimizing object allocation + * + * @param picture YUVPicture to convert + * @param imageToPack Image to output to. If picture.size() != imageToPack.size() then a new + * BufferedImage is allocated + * @return imageToPack if sizes match, new BufferedImage otherwise. + */ + public WritableImage toFXImage(YUVPicture picture, WritableImage imageToPack) + { + RGBPicture rgb = picture.toRGB(); + WritableImage img = toFXImage(rgb, imageToPack); + rgb.delete(); + return img; + } + + /** + * Convert RGBPicture to BufferedImage, minimizing object allocation + * + * @param picture RGBPicture to convert + * @param imageToPack Image to output to. If picture.size() != imageToPack.size() then a new + * BufferedImage is allocated + * @return imageToPack if sizes match, new BufferedImage otherwise. + */ + public WritableImage toFXImage(RGBPicture picture, WritableImage imageToPack) + { + WritableImage target = imageToPack; + int w = picture.getWidth(); + int h = picture.getHeight(); + if (target == null || target.getWidth() != w || target.getHeight() != h) + { + target = new WritableImage(w, h); + } + PixelWriter pixelWriter = target.getPixelWriter(); + + ByteBuffer dstBuffer = byteBufferProvider.getOrCreateBuffer(w * h * 3); + picture.get(dstBuffer); + + int x = 0; + int y = 0; + + while (dstBuffer.position() < dstBuffer.limit()) + { + int b = dstBuffer.get() & 0xff; + int g = dstBuffer.get() & 0xff; + int r = dstBuffer.get() & 0xff; + int argb = (0xff << 24) | (r << 16) | (g << 8) | b; + pixelWriter.setArgb(x, y, argb); + x++; + if (x >= w) + { + x = 0; + y++; + } + } + return target; + } +} diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/LogSessionManagerController.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/LogSessionManagerController.java index a232d84cc..42d8f45ed 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/LogSessionManagerController.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/LogSessionManagerController.java @@ -8,7 +8,6 @@ import java.util.function.ToLongFunction; import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXSlider; import com.jfoenix.controls.JFXSpinner; import com.jfoenix.controls.JFXToggleButton; @@ -35,10 +34,12 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; +import javafx.stage.FileChooser.ExtensionFilter; import javafx.stage.Stage; import javafx.stage.WindowEvent; -import javafx.util.Callback; import us.ihmc.log.LogTools; +import us.ihmc.robotDataLogger.LogProperties; import us.ihmc.robotDataLogger.logger.LogPropertiesReader; import us.ihmc.scs2.session.Session; import us.ihmc.scs2.sessionVisualizer.SessionVisualizerIOTools; @@ -93,22 +94,13 @@ public void initialize(SessionVisualizerToolkit toolkit) backgroundExecutorManager = toolkit.getBackgroundExecutorManager(); - mainPane.getStylesheets().add(SessionVisualizerIOTools.GENERAL_STYLESHEET.toExternalForm()); - - logPositionSlider.setValueFactory(new Callback() + logPositionSlider.setValueFactory(param -> new TimeStringBinding(param.valueProperty(), position -> { - @Override - public StringBinding call(JFXSlider param) - { - return new TimeStringBinding(param.valueProperty(), position -> - { - if (activeSessionProperty.get() == null) - return 0; - LogDataReader logDataReader = activeSessionProperty.get().getLogDataReader(); - return logDataReader.getRelativeTimestamp(position.intValue()); - }); - } - }); + if (activeSessionProperty.get() == null) + return 0; + LogDataReader logDataReader = activeSessionProperty.get().getLogDataReader(); + return logDataReader.getRelativeTimestamp(position.intValue()); + })); ChangeListener activeSessionListener = (o, oldValue, newValue) -> { @@ -137,7 +129,7 @@ public StringBinding call(JFXSlider param) LogPropertiesReader logProperties = newValue.getLogProperties(); sessionNameLabel.setText(newValue.getSessionName()); - dateLabel.setText(logProperties.getTimestampAsString()); + dateLabel.setText(getDate(logProperties)); logPathLabel.setText(logDirectory.getAbsolutePath()); endSessionButton.setDisable(false); logPositionSlider.setDisable(false); @@ -231,10 +223,11 @@ public StringBinding call(JFXSlider param) openSessionButton.disableProperty().bind(loadingSpinner.visibleProperty()); openSessionButton.setOnAction(e -> { - DirectoryChooser directoryChooser = new DirectoryChooser(); - directoryChooser.setInitialDirectory(SessionVisualizerIOTools.getDefaultFilePath(LOG_FILE_KEY)); - directoryChooser.setTitle("Choose log directory"); - File result = directoryChooser.showDialog(stage); + FileChooser fileChooser = new FileChooser(); + fileChooser.setInitialDirectory(SessionVisualizerIOTools.getDefaultFilePath(LOG_FILE_KEY)); + fileChooser.getExtensionFilters().add(new ExtensionFilter("Log property file", "*.log")); + fileChooser.setTitle("Choose log directory"); + File result = fileChooser.showOpenDialog(stage); if (result == null) return; @@ -247,7 +240,7 @@ public StringBinding call(JFXSlider param) try { LogTools.info("Creating log session."); - newSession = new LogSession(result, null); // TODO Need a progress window + newSession = new LogSession(result.getParentFile(), null); // TODO Need a progress window LogTools.info("Created log session."); Platform.runLater(() -> activeSessionProperty.set(newSession)); SessionVisualizerIOTools.setDefaultFilePath(LOG_FILE_KEY, result); @@ -378,4 +371,18 @@ protected String computeValue() return time; } } + + private static String getDate(LogProperties logProperties) + { + String timestampAsString = logProperties.getTimestampAsString(); + + String year = timestampAsString.substring(0, 4); + String month = timestampAsString.substring(4, 6); + String day = timestampAsString.substring(6, 8); + String hour = timestampAsString.substring(9, 11); + String minute = timestampAsString.substring(11, 13); + String second = timestampAsString.substring(13, 15); + + return year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second; + } } diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoConverter.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoConverter.java index 0df6c4036..b7b585d8b 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoConverter.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoConverter.java @@ -51,7 +51,8 @@ public static int crop(File source, File target, long startPTS, long endPTS, Pro } } - builder.close(); + if (builder != null) + builder.close(); demuxer.delete(); return frameRate; diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoDataReader.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoDataReader.java index b9200bca4..7fec5e3e7 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoDataReader.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoDataReader.java @@ -1,6 +1,5 @@ package us.ihmc.scs2.sessionVisualizer.session.log; -import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; @@ -8,12 +7,14 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.mutable.MutableObject; import gnu.trove.list.array.TLongArrayList; +import javafx.scene.image.WritableImage; import us.ihmc.codecs.demuxer.MP4VideoDemuxer; import us.ihmc.codecs.generated.YUVPicture; -import us.ihmc.codecs.yuv.YUVPictureConverter; +import us.ihmc.concurrent.ConcurrentCopier; import us.ihmc.robotDataLogger.Camera; public class VideoDataReader @@ -29,8 +30,7 @@ public class VideoDataReader private long bmdTimeBaseDen; private final MP4VideoDemuxer demuxer; - - private final YUVPictureConverter converter = new YUVPictureConverter(); + private final JavaFXPictureConverter converter = new JavaFXPictureConverter(); private int currentlyShowingIndex = 0; private long currentlyShowingRobotTimestamp = 0; @@ -38,7 +38,7 @@ public class VideoDataReader private final File videoFile; private final Camera camera; - private final AtomicReference currentFrame = new AtomicReference<>(null); + private final ConcurrentCopier> imageBuffer = new ConcurrentCopier<>(MutableObject::new); public VideoDataReader(Camera camera, File dataDirectory, boolean hasTimeBase) throws IOException { @@ -100,7 +100,10 @@ public void readVideoFrame(long timestamp) { demuxer.seekToPTS(videoTimestamp); YUVPicture nextFrame = demuxer.getNextFrame(); - currentFrame.set(converter.toBufferedImage(nextFrame, null)); + MutableObject copyForWriting = imageBuffer.getCopyForWriting(); + copyForWriting.setValue(converter.toFXImage(nextFrame, copyForWriting.getValue())); + + imageBuffer.commit(); } catch (IOException e) { @@ -276,8 +279,8 @@ public Camera getCamera() return camera; } - public BufferedImage pollCurrentFrame() + public WritableImage pollCurrentFrame() { - return currentFrame.getAndSet(null); + return imageBuffer.getCopyForReading().getValue(); } } diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoViewer.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoViewer.java index d2900b160..e42070000 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoViewer.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/VideoViewer.java @@ -1,7 +1,6 @@ package us.ihmc.scs2.sessionVisualizer.session.log; -import java.awt.image.BufferedImage; - +import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; @@ -9,15 +8,14 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.image.WritableImage; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.Window; @@ -26,27 +24,33 @@ public class VideoViewer { + private static final double THUMBNAIL_HIGHLIGHT_SCALE = 1.05; + private final ImageView thumbnail = new ImageView(); + private final StackPane thumbnailContainer = new StackPane(thumbnail); private final ImageView videoView = new ImageView(); private final BooleanProperty updateVideoView = new SimpleBooleanProperty(this, "updateVideoView", false); private final ObjectProperty videoWindowProperty = new SimpleObjectProperty<>(this, "videoWindow", null); private final VideoDataReader reader; + private final double defaultThumbnailSize; public VideoViewer(Window owner, VideoDataReader reader, double defaultThumbnailSize) { this.reader = reader; + this.defaultThumbnailSize = defaultThumbnailSize; thumbnail.setPreserveRatio(true); videoView.setPreserveRatio(true); thumbnail.setFitWidth(defaultThumbnailSize); thumbnail.setOnMouseEntered(e -> { - Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), new KeyValue(thumbnail.fitWidthProperty(), 1.05 * defaultThumbnailSize))); + Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), + new KeyValue(thumbnail.fitWidthProperty(), THUMBNAIL_HIGHLIGHT_SCALE * defaultThumbnailSize, Interpolator.EASE_BOTH))); timeline.playFromStart(); }); thumbnail.setOnMouseExited(e -> { - Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), new KeyValue(thumbnail.fitWidthProperty(), defaultThumbnailSize))); + Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.1), new KeyValue(thumbnail.fitWidthProperty(), defaultThumbnailSize, Interpolator.EASE_BOTH))); timeline.playFromStart(); }); @@ -117,15 +121,18 @@ protected void layoutChildren() public void update() { - BufferedImage currentFrame = reader.pollCurrentFrame(); + Image currentFrame = reader.pollCurrentFrame(); if (currentFrame == null) return; - WritableImage newFrame = SwingFXUtils.toFXImage(currentFrame, null); - thumbnail.setImage(newFrame); + thumbnailContainer.setPrefWidth(THUMBNAIL_HIGHLIGHT_SCALE * defaultThumbnailSize); + thumbnailContainer.setPrefHeight(THUMBNAIL_HIGHLIGHT_SCALE * defaultThumbnailSize * currentFrame.getHeight() / currentFrame.getWidth()); + + thumbnail.setImage(currentFrame); + if (updateVideoView.get()) - videoView.setImage(newFrame); + videoView.setImage(currentFrame); } public void stop() @@ -139,6 +146,6 @@ public void stop() public Node getThumbnail() { - return thumbnail; + return thumbnailContainer; } } diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/YoVariableLogCropper.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/YoVariableLogCropper.java index ecc93bb79..42123a775 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/YoVariableLogCropper.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/session/log/YoVariableLogCropper.java @@ -40,20 +40,20 @@ public void crop(File destination, int from, int to, ProgressConsumer progressCo if (!destination.isDirectory()) { progressConsumer.error("Destination " + destination.getAbsolutePath() + " already exists."); - progressConsumer.progress(0.0); + progressConsumer.done(); return; } else if (destination.list().length > 0) { progressConsumer.error("Destination " + destination.getAbsolutePath() + " is not empty."); - progressConsumer.progress(0.0); + progressConsumer.done(); return; } } else if (!destination.mkdir()) { progressConsumer.error("Cannot make directory " + destination.getAbsolutePath()); - progressConsumer.progress(0.0); + progressConsumer.done(); return; } diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoArrowFX3D.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoArrowFX3D.java index 72278de26..fcf611397 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoArrowFX3D.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoArrowFX3D.java @@ -41,6 +41,8 @@ public YoArrowFX3D() { body.setMaterial(material); head.setMaterial(material); + body.idProperty().bind(nameProperty().concat(" (body)")); + head.idProperty().bind(nameProperty().concat(" (head)")); Affine bodyAffineToZUp = new Affine(); bodyAffineToZUp.appendTranslation(0.0, 0.0, 0.5); diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCapsuleFX3D.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCapsuleFX3D.java index 81c1aa3bd..94cdf81b5 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCapsuleFX3D.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCapsuleFX3D.java @@ -38,6 +38,7 @@ public YoCapsuleFX3D() { capsuleNode.setMaterial(material); capsuleNode.getTransforms().addAll(translate, rotate); + capsuleNode.idProperty().bind(nameProperty()); } @Override diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCoordinateSystemFX3D.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCoordinateSystemFX3D.java index 3f1df13d8..91a39e52f 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCoordinateSystemFX3D.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoCoordinateSystemFX3D.java @@ -1,6 +1,7 @@ package us.ihmc.scs2.sessionVisualizer.yoGraphic; import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.Group; import javafx.scene.Node; @@ -137,10 +138,10 @@ else if (newDataLocal.equals(oldData) && !coordinateSystemNode.getChildren().isE } oldData = newDataLocal; - newNodes = createCoordinateSystem(newDataLocal, material); + newNodes = createCoordinateSystem(newDataLocal, material, nameProperty()); } - static Node[] createCoordinateSystem(CoordinateSystemData data, Material material) + static Node[] createCoordinateSystem(CoordinateSystemData data, Material material, ReadOnlyStringProperty nameProperty) { Node[] nodes = new Node[6]; @@ -150,6 +151,7 @@ static Node[] createCoordinateSystem(CoordinateSystemData data, Material materia { Cylinder body = new Cylinder(data.bodyRadius, data.bodyLength); body.setMaterial(material); + body.idProperty().bind(nameProperty.concat(" (").concat(Axis.values[axis].name()).concat("-body)")); if (axisBodyRotates[axis] != null) body.getTransforms().add(axisBodyRotates[axis]); @@ -161,6 +163,7 @@ static Node[] createCoordinateSystem(CoordinateSystemData data, Material materia meshBuilder.addCone(data.headLength, data.headRadius, headPosition, axisHeadOrientations[axis]); MeshView head = new MeshView(meshBuilder.generateMesh()); head.setMaterial(new PhongMaterial(axisColors[axis])); + head.idProperty().bind(nameProperty.concat(" (").concat(Axis.values[axis].name()).concat("-head)")); nodes[2 * axis] = body; nodes[2 * axis + 1] = head; diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointFX3D.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointFX3D.java index 87932041c..5e705b7d5 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointFX3D.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointFX3D.java @@ -44,7 +44,10 @@ public void setGraphicResource(YoGraphicFXResource graphicResource) List shapes = YoGraphicTools.extractShape3Ds(Arrays.asList(nodes)); for (Shape3D shape : shapes) + { shape.setMaterial(material); + shape.idProperty().bind(nameProperty()); + } pointNode.getChildren().addAll(nodes); } diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointcloudFX3D.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointcloudFX3D.java index c8c4f4666..0a0722a82 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointcloudFX3D.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPointcloudFX3D.java @@ -103,6 +103,7 @@ private void refreshGraphics() shape.setMaterial(material); // The importer may have added transforms, we want to be before these. shape.getTransforms().addAll(0, Arrays.asList(translate, scale)); + shape.idProperty().bind(nameProperty().concat(" (").concat(Integer.toString(i)).concat(")")); } pointcloudNode.getChildren().addAll(shapes); diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolygonExtrudedFX3D.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolygonExtrudedFX3D.java index 7a7ca1099..6422581d1 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolygonExtrudedFX3D.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolygonExtrudedFX3D.java @@ -45,6 +45,7 @@ public YoPolygonExtrudedFX3D() { polygonNode.setMaterial(material); polygonNode.getTransforms().add(affine); + polygonNode.idProperty().bind(nameProperty()); } @Override diff --git a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolynomialFX3D.java b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolynomialFX3D.java index b7903aa17..6fbdc67c0 100644 --- a/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolynomialFX3D.java +++ b/src/session-visualizer/java/us/ihmc/scs2/sessionVisualizer/yoGraphic/YoPolynomialFX3D.java @@ -122,6 +122,7 @@ else if (newPolynomialLocal.coefficientsX == null) { meshViews[i] = new MeshView(meshes[i]); meshViews[i].setMaterial(material); + meshViews[i].idProperty().bind(nameProperty().concat(" (").concat(Integer.toString(i)).concat(")")); } oldPolynomial = newPolynomialLocal; diff --git a/src/session-visualizer/resources/fxml/session/LogSessionManagerPane.fxml b/src/session-visualizer/resources/fxml/session/LogSessionManagerPane.fxml index a4ea6b15f..1d6aa63a1 100644 --- a/src/session-visualizer/resources/fxml/session/LogSessionManagerPane.fxml +++ b/src/session-visualizer/resources/fxml/session/LogSessionManagerPane.fxml @@ -15,57 +15,57 @@ - + - - - + + + - - + + - - - + + + - - - - + + + - + - + - + - + diff --git a/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternEditorPane.fxml b/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternEditorPane.fxml index 865336e9f..b3dc17add 100644 --- a/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternEditorPane.fxml +++ b/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternEditorPane.fxml @@ -4,6 +4,7 @@ + @@ -29,9 +30,13 @@ @@ -40,9 +45,13 @@ diff --git a/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternPropertyWindow.fxml b/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternPropertyWindow.fxml index a3f7d2dd4..2531f3b9a 100644 --- a/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternPropertyWindow.fxml +++ b/src/session-visualizer/resources/fxml/yoComposite/pattern/YoCompositePatternPropertyWindow.fxml @@ -72,8 +72,8 @@ - - + + diff --git a/src/session/java/us/ihmc/scs2/session/Session.java b/src/session/java/us/ihmc/scs2/session/Session.java index 37e255952..ea5271192 100644 --- a/src/session/java/us/ihmc/scs2/session/Session.java +++ b/src/session/java/us/ihmc/scs2/session/Session.java @@ -34,6 +34,7 @@ public abstract class Session private final AtomicReference activeMode = new AtomicReference<>(SessionMode.PAUSE); private final AtomicBoolean runAtRealTimeRate = new AtomicBoolean(false); private final AtomicReference playbackRealTimeRate = new AtomicReference<>(2.0); + private int stepSizePerPlaybackTick = 1; /** * Map from one session tick to the time increment in the data. *

@@ -339,7 +340,15 @@ protected void finalizeRunTick() protected long computePlaybackTaskPeriod() { - return (long) (sessionTickToTimeIncrement.get() / playbackRealTimeRate.get().doubleValue()); + if (playbackRealTimeRate.get().doubleValue() <= 0.5) + { + stepSizePerPlaybackTick = 1; + return (long) (sessionTickToTimeIncrement.get() / playbackRealTimeRate.get().doubleValue()); + } + + stepSizePerPlaybackTick = 2 * Math.max(1, (int) Math.floor(playbackRealTimeRate.get().doubleValue())); + long timeIncrement = sessionTickToTimeIncrement.longValue() * stepSizePerPlaybackTick; + return (long) (timeIncrement / playbackRealTimeRate.get().doubleValue()); } public void playbackTick() @@ -385,7 +394,7 @@ protected void finalizePlaybackTick() lastPublishedBufferTimestamp = currentTimestamp; } - sharedBuffer.incrementBufferIndex(false); + sharedBuffer.incrementBufferIndex(false, stepSizePerPlaybackTick); processBufferRequests(false); publishBufferProperties(sharedBuffer.getProperties()); }