Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@
/jsplat-examples/.project
/jsplat-examples/.settings
/jsplat-examples/.classpath

/jsplat-examples/data/*
!/jsplat-examples/data/unitCube-ascii.ply


/jsplat-examples-gltf/target
/jsplat-examples-gltf/.project
/jsplat-examples-gltf/.settings
/jsplat-examples-gltf/.classpath
/jsplat-examples-gltf/data/*


/jsplat/target
/jsplat/.project
/jsplat/.settings
Expand Down
10 changes: 10 additions & 0 deletions jsplat-app/src/main/java/de/javagl/jsplat/app/DataSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ void setTransform(Transform transform)
{
this.currentTransform = transform;
double[] matrix = Transforms.toMatrix(transform);
setTransformMatrixInternal(matrix);
}

/**
* Highly preliminary, internal method to set the transform matrix.
*
* @param matrix The matrix
*/
void setTransformMatrixInternal(double matrix[])
{
int shDimensions = Splats.dimensionsForDegree(shDegree);
Consumer<MutableSplat> t =
SplatTransforms.createTransform(matrix, shDimensions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public List<MutableSplat> readList(InputStream inputStream)
* @param glbData The GLB data
* @return The result
*/
private static boolean usesSpz(byte glbData[])
static boolean usesSpz(byte glbData[])
{
GltfAssetReader ar = new GltfAssetReader();
try (ByteArrayInputStream bais = new ByteArrayInputStream(glbData))
Expand Down Expand Up @@ -103,7 +103,7 @@ private static boolean usesSpz(byte glbData[])
* @return The byte array
* @throws IOException If an IO error occurs
*/
private static byte[] readFully(InputStream inputStream) throws IOException
static byte[] readFully(InputStream inputStream) throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] data = new byte[16384];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
Expand All @@ -41,7 +42,10 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.logging.Logger;

Expand All @@ -57,14 +61,18 @@
import javax.swing.JSeparator;
import javax.swing.filechooser.FileNameExtensionFilter;

import de.javagl.jgltf.model.NodeModel;
import de.javagl.jsplat.MutableSplat;
import de.javagl.jsplat.Splat;
import de.javagl.jsplat.SplatListReader;
import de.javagl.jsplat.SplatListWriter;
import de.javagl.jsplat.app.common.UriLoading;
import de.javagl.jsplat.app.common.UriTransferHandler;
import de.javagl.jsplat.app.common.UriUtils;
import de.javagl.jsplat.io.gltf.GltfSplatReader;
import de.javagl.jsplat.io.gltf.GltfSplatReader.Result;
import de.javagl.jsplat.io.gltf.GltfSplatWriter;
import de.javagl.jsplat.io.gltf.spz.GltfSpzSplatReader;
import de.javagl.jsplat.io.gltf.spz.GltfSpzSplatWriter;
import de.javagl.jsplat.io.gsplat.GsplatSplatReader;
import de.javagl.jsplat.io.gsplat.GsplatSplatWriter;
Expand Down Expand Up @@ -361,7 +369,15 @@ void openUrisInBackground(List<? extends URI> uris)
try
{
long beforeNs = System.nanoTime();
List<MutableSplat> result = reader.readList(inputStream);
List<MutableSplat> result;
if (reader instanceof GlbSplatListReader)
{
result = processGltfSplats(inputStream);
}
else
{
result = reader.readList(inputStream);
}
long afterNs = System.nanoTime();
double ms = (afterNs - beforeNs) / 1e6;
logger.info(
Expand All @@ -380,6 +396,68 @@ void openUrisInBackground(List<? extends URI> uris)
});
}

/**
* Quirky, proof-of-concept function for splats that are contained in
* glTF with node animations
*
* @param inputStream The input stream
* @return An empty list. I said it's quirky...
* @throws IOException If an IO error occurs
*/
private List<MutableSplat> processGltfSplats(InputStream inputStream)
throws IOException
{
byte data[] = GlbSplatListReader.readFully(inputStream);
boolean usesSpz = GlbSplatListReader.usesSpz(data);
if (usesSpz)
{
GltfSpzSplatReader sr = new GltfSpzSplatReader();
return sr.readList(new ByteArrayInputStream(data));
}
GltfSplatReader sr = new GltfSplatReader();
Result result = sr.read(new ByteArrayInputStream(data));
Map<NodeModel, List<MutableSplat>> nodeSplatLists = result.splatLists;

// The result here contains mappings from nodes to splat lists.
// Assign consecutive names to the nodes, and add the splat
// lists to the application panel
int counterA = 0;
List<String> names = new ArrayList<String>();
List<List<MutableSplat>> splatLists =
new ArrayList<List<MutableSplat>>();
for (Entry<NodeModel, List<MutableSplat>> entry : nodeSplatLists
.entrySet())
{
String name = "node-" + counterA;
List<MutableSplat> splatList = entry.getValue();
names.add(name);
splatLists.add(splatList);
counterA++;
}

// Returns a mapping from names to data sets
Map<String, DataSet> dataSets =
applicationPanel.addSplatLists(names, splatLists);

// Convert the mapping from "names to data sets" into a mapping
// of "nodes to data sets", and pass that to the animation handler
Map<NodeModel, DataSet> nodeDataSets =
new LinkedHashMap<NodeModel, DataSet>();
int counterB = 0;
for (Entry<NodeModel, List<MutableSplat>> entry : nodeSplatLists
.entrySet())
{
String name = "node-" + counterB;
DataSet dataSet = dataSets.get(name);
NodeModel key = entry.getKey();
nodeDataSets.put(key, dataSet);
counterB++;
}
SplatAnimationHandler.handleAnimation(result.gltfModel, nodeDataSets,
() -> applicationPanel.updateSplats());
return Collections.emptyList();
}

/**
* Process the given splats that have been loaded from a URI
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
import java.util.logging.Logger;

Expand Down Expand Up @@ -464,20 +466,31 @@ private JPanel createTransformPanel()
transformPanel.addChangeListener(listener);
return transformPanel;
}

/**
* Update the splats in the viewer for the case that the properties
* of the splat lists changed
*/
void updateSplats()
{
splatViewer.updateSplats();
}

/**
* Add the splats that should be displayed
*
* @param names The name
* @param splatLists The splat lists
* @return A mapping from names to data sets
*/
void addSplatLists(List<String> names,
Map<String, DataSet> addSplatLists(List<String> names,
List<? extends List<? extends Splat>> splatLists)
{
Map<String, DataSet> dataSets = new LinkedHashMap<String, DataSet>();
if (splatViewer == null)
{
logger.warning("No SplatViewer was created");
return;
return dataSets;
}

List<List<? extends Splat>> currentSplatLists =
Expand All @@ -492,6 +505,8 @@ void addSplatLists(List<String> names,

List<MutableSplat> currentSplats = dataSet.getCurrentSplats();
currentSplatLists.add(currentSplats);

dataSets.put(name, dataSet);
}
splatViewer.addSplatLists(currentSplatLists);

Expand All @@ -501,6 +516,7 @@ void addSplatLists(List<String> names,
doFit = false;
}
updateStatus();
return dataSets;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* www.javagl.de - JSplat
*
* Copyright 2025 Marco Hutter - http://www.javagl.de
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package de.javagl.jsplat.app;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;

import de.javagl.jgltf.model.GltfAnimations;
import de.javagl.jgltf.model.GltfModel;
import de.javagl.jgltf.model.NodeModel;
import de.javagl.jgltf.model.animation.Animation;
import de.javagl.jgltf.model.animation.AnimationManager;
import de.javagl.jgltf.model.animation.AnimationManager.AnimationPolicy;
import de.javagl.jgltf.model.animation.AnimationRunner;

/**
* HIGHLY preliminary, internal class for handling splats that are attached to
* animated glTF nodes.
*/
class SplatAnimationHandler
{
/**
* EXPERIMENTAL: Handle the animations in the given model
*
* @param gltfModel The model
* @param nodeDataSets Mapping from nodes to splat data sets
* @param callback A callback for updates
*/
public static void handleAnimation(GltfModel gltfModel,
Map<NodeModel, DataSet> nodeDataSets, Runnable callback)
{
List<Animation> animations = GltfAnimations
.createModelAnimations(gltfModel.getAnimationModels());
if (animations.isEmpty())
{
return;
}

long updateNanos = 50 * 1000 * 1000;
AtomicLong previousUpdateNs = new AtomicLong();
AnimationManager animationManager =
GltfAnimations.createAnimationManager(AnimationPolicy.LOOP);
animationManager.addAnimationManagerListener(a ->
{
long p = previousUpdateNs.get();
long c = System.nanoTime();
if (c < p + updateNanos)
{
return;
}
previousUpdateNs.set(c);
for (Entry<NodeModel, DataSet> entry : nodeDataSets.entrySet())
{
NodeModel nodeModel = entry.getKey();
DataSet dataSet = entry.getValue();
float[] transformMatrix = new float[16];
nodeModel.computeGlobalTransform(transformMatrix);
dataSet.setTransformMatrixInternal(toDouble(transformMatrix));
}
callback.run();
});
AnimationRunner animationRunner = new AnimationRunner(animationManager);
animationManager.addAnimations(animations);
animationRunner.start();
}

/**
* Convert the given array into a double array
*
* @param array The array
* @return The result
*/
private static double[] toDouble(float array[])
{
if (array == null)
{
return null;
}
double result[] = new double[array.length];
for (int i = 0; i < array.length; i++)
{
result[i] = array[i];
}
return result;
}

}
3 changes: 3 additions & 0 deletions jsplat-examples-gltf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# jsplat-examples-gltf

Examples for creating glTF files with splats
Loading