32 package eu.mihosoft.vrl.v3d.ext.openjfx.importers;
34 import java.util.ArrayList;
35 import java.util.Comparator;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.List;
42 import javafx.animation.Interpolator;
43 import javafx.animation.KeyFrame;
44 import javafx.animation.KeyValue;
45 import javafx.animation.Timeline;
46 import javafx.beans.property.Property;
47 import javafx.beans.value.WritableValue;
48 import javafx.collections.FXCollections;
49 import javafx.collections.ObservableFloatArray;
50 import javafx.collections.ObservableIntegerArray;
51 import javafx.collections.ObservableList;
52 import javafx.collections.transformation.SortedList;
53 import javafx.geometry.Point2D;
54 import javafx.geometry.Point3D;
55 import javafx.scene.Group;
56 import javafx.scene.Node;
57 import javafx.scene.Parent;
58 import javafx.scene.shape.MeshView;
59 import javafx.scene.shape.TriangleMesh;
60 import javafx.scene.transform.Transform;
61 import javafx.util.Duration;
77 private Set<Transform>
bound =
new HashSet<>();
83 private List<MeshView>
meshViews =
new ArrayList<>();
112 private int trRemoved, trTotal, groupsTotal, trCandidate, trEmpty;
130 System.out.printf(
"removed %d (%.2f%%) out of total %d transforms\n",
trRemoved, 100d *
trRemoved / trTotal, trTotal);
131 System.out.printf(
"there are %d more multiplications that can be done of matrices that never change\n", trCandidate);
132 System.out.printf(
"there are %d (%.2f%%) out of total %d groups with no transforms in them\n", trEmpty, 100d * trEmpty / groupsTotal, groupsTotal);
141 ObservableList<Transform> transforms = node.getTransforms();
142 Iterator<Transform> iterator = transforms.iterator();
143 boolean prevIsStatic =
false;
144 while (iterator.hasNext()) {
145 Transform transform = iterator.next();
147 if (transform.isIdentity()) {
159 prevIsStatic =
false;
163 if (node instanceof Parent) {
165 Parent p = (Parent) node;
166 for (Node n : p.getChildrenUnmodifiable()) {
169 if (transforms.isEmpty()) {
170 Parent parent = p.getParent();
171 if (parent instanceof Group) {
180 if (node instanceof MeshView) {
198 int total = 0, sameIndexes = 0, samePoints = 0, smallArea = 0;
199 ObservableIntegerArray newFaces = FXCollections.observableIntegerArray();
200 ObservableIntegerArray newFaceSmoothingGroups = FXCollections.observableIntegerArray();
202 TriangleMesh mesh = (TriangleMesh) meshView.getMesh();
203 ObservableIntegerArray faces = mesh.getFaces();
204 ObservableIntegerArray faceSmoothingGroups = mesh.getFaceSmoothingGroups();
205 ObservableFloatArray points = mesh.getPoints();
207 newFaces.ensureCapacity(faces.size());
208 newFaceSmoothingGroups.clear();
209 newFaceSmoothingGroups.ensureCapacity(faceSmoothingGroups.size());
210 int pointElementSize = mesh.getPointElementSize();
211 int faceElementSize = mesh.getFaceElementSize();
212 for (
int i = 0; i < faces.size(); i += faceElementSize) {
214 int i1 = faces.get(i) * pointElementSize;
215 int i2 = faces.get(i + 2) * pointElementSize;
216 int i3 = faces.get(i + 4) * pointElementSize;
217 if (i1 == i2 || i1 == i3 || i2 == i3) {
221 Point3D p1 =
new Point3D(points.get(i1), points.get(i1 + 1), points.get(i1 + 2));
222 Point3D p2 =
new Point3D(points.get(i2), points.get(i2 + 1), points.get(i2 + 2));
223 Point3D p3 =
new Point3D(points.get(i3), points.get(i3 + 1), points.get(i3 + 2));
224 if (p1.equals(p2) || p1.equals(p3) || p2.equals(p3)) {
228 double a = p1.distance(p2);
229 double b = p2.distance(p3);
230 double c = p3.distance(p1);
231 double p = (a + b + c) / 2;
232 double sqarea = p * (p - a) * (p - b) * (p - c);
234 final float DEAD_FACE = 1.f/1024/1024/1024/1024;
236 if (sqarea < DEAD_FACE) {
242 newFaces.addAll(faces, i, faceElementSize);
243 int fIndex = i / faceElementSize;
244 if (fIndex < faceSmoothingGroups.size()) {
245 newFaceSmoothingGroups.addAll(faceSmoothingGroups.get(fIndex));
248 faces.setAll(newFaces);
249 faceSmoothingGroups.setAll(newFaceSmoothingGroups);
251 faceSmoothingGroups.trimToSize();
253 int badTotal = sameIndexes + samePoints + smallArea;
254 System.out.printf(
"Removed %d (%.2f%%) faces with same point indexes, "
255 +
"%d (%.2f%%) faces with same points, "
256 +
"%d (%.2f%%) faces with small area. "
257 +
"Total %d (%.2f%%) bad faces out of %d total.\n",
258 sameIndexes, 100d * sameIndexes / total,
259 samePoints, 100d * samePoints / total,
260 smallArea, 100d * smallArea / total,
261 badTotal, 100d * badTotal / total, total);
268 int total = 0, duplicates = 0, check = 0;
270 Map<Point3D, Integer> pp =
new HashMap<>();
271 ObservableIntegerArray reindex = FXCollections.observableIntegerArray();
272 ObservableFloatArray newPoints = FXCollections.observableFloatArray();
275 TriangleMesh mesh = (TriangleMesh) meshView.getMesh();
276 ObservableFloatArray points = mesh.getPoints();
277 int pointElementSize = mesh.getPointElementSize();
278 int os = points.size() / pointElementSize;
282 newPoints.ensureCapacity(points.size());
286 for (
int i = 0, oi = 0, ni = 0; i < points.size(); i += pointElementSize, oi++) {
287 float x = points.get(i);
288 float y = points.get(i + 1);
289 float z = points.get(i + 2);
290 Point3D p =
new Point3D(x, y, z);
291 Integer index = pp.get(p);
295 newPoints.addAll(x, y, z);
298 reindex.set(oi, index);
302 int ns = newPoints.size() / pointElementSize;
308 points.setAll(newPoints);
311 ObservableIntegerArray faces = mesh.getFaces();
312 for (
int i = 0; i < faces.size(); i += 2) {
313 faces.set(i, reindex.get(faces.get(i)));
319 check += mesh.getPoints().size() / pointElementSize;
321 System.out.printf(
"There are %d (%.2f%%) duplicate points out of %d total.\n",
322 duplicates, 100d * duplicates / total, total);
323 System.out.printf(
"Now we have %d points.\n", check);
330 int total = 0, duplicates = 0, check = 0;
332 Map<Point2D, Integer> pp =
new HashMap<>();
333 ObservableIntegerArray reindex = FXCollections.observableIntegerArray();
334 ObservableFloatArray newTexCoords = FXCollections.observableFloatArray();
337 TriangleMesh mesh = (TriangleMesh) meshView.getMesh();
338 ObservableFloatArray texcoords = mesh.getTexCoords();
339 int texcoordElementSize = mesh.getTexCoordElementSize();
340 int os = texcoords.size() / texcoordElementSize;
343 newTexCoords.clear();
344 newTexCoords.ensureCapacity(texcoords.size());
348 for (
int i = 0, oi = 0, ni = 0; i < texcoords.size(); i += texcoordElementSize, oi++) {
349 float x = texcoords.get(i);
350 float y = texcoords.get(i + 1);
351 Point2D p =
new Point2D(x, y);
352 Integer index = pp.get(p);
356 newTexCoords.addAll(x, y);
359 reindex.set(oi, index);
363 int ns = newTexCoords.size() / texcoordElementSize;
369 texcoords.setAll(newTexCoords);
370 texcoords.trimToSize();
372 ObservableIntegerArray faces = mesh.getFaces();
373 for (
int i = 1; i < faces.size(); i += 2) {
374 faces.set(i, reindex.get(faces.get(i)));
380 check += mesh.getTexCoords().size() / texcoordElementSize;
382 System.out.printf(
"There are %d (%.2f%%) duplicate texcoords out of %d total.\n",
383 duplicates, 100d * duplicates / total, total);
384 System.out.printf(
"Now we have %d texcoords.\n", check);
391 ObservableList<KeyFrame> timelineKeyFrames =
timeline.getKeyFrames().sorted(
new KeyFrameComparator());
393 int kfTotal = timelineKeyFrames.size(), kfRemoved = 0;
394 int kvTotal = 0, kvRemoved = 0;
395 Map<Duration, KeyFrame> kfUnique =
new HashMap<>();
396 Map<WritableValue, KeyValue> kvUnique =
new HashMap<>();
397 MapOfLists<KeyFrame, KeyFrame> duplicates =
new MapOfLists<>();
398 Iterator<KeyFrame> iterator = timelineKeyFrames.iterator();
399 while (iterator.hasNext()) {
400 KeyFrame duplicate = iterator.next();
401 KeyFrame original = kfUnique.put(duplicate.getTime(), duplicate);
402 if (original !=
null) {
405 duplicates.add(original, duplicate);
407 kfUnique.put(duplicate.getTime(), original);
410 for (KeyValue kvDup : duplicate.getValues()) {
412 KeyValue kvOrig = kvUnique.put(kvDup.getTarget(), kvDup);
413 if (kvOrig !=
null) {
415 if (!kvOrig.getEndValue().equals(kvDup.getEndValue()) && kvOrig.getTarget() == kvDup.getTarget()) {
416 System.err.println(
"KeyValues set different values for KeyFrame " + duplicate.getTime() +
":"
417 +
"\n kvOrig = " + kvOrig +
", \nkvDup = " + kvDup);
422 for (KeyFrame orig : duplicates.keySet()) {
423 List<KeyValue> keyValues =
new ArrayList<>();
424 for (KeyFrame dup : duplicates.get(orig)) {
425 keyValues.addAll(dup.getValues());
427 timelineKeyFrames.set(timelineKeyFrames.indexOf(orig),
428 new KeyFrame(orig.getTime(), keyValues.toArray(
new KeyValue[keyValues.size()])));
430 System.out.printf(
"Removed %d (%.2f%%) duplicate KeyFrames out of total %d.\n",
431 kfRemoved, 100d * kfRemoved / kfTotal, kfTotal);
432 System.out.printf(
"Identified %d (%.2f%%) duplicate KeyValues out of total %d.\n",
433 kvRemoved, 100d * kvRemoved / kvTotal, kvTotal);
439 private static class KeyInfo {
456 public KeyInfo(KeyFrame keyFrame, KeyValue keyValue) {
457 this.keyFrame = keyFrame;
458 this.keyValue = keyValue;
469 public KeyInfo(KeyFrame keyFrame, KeyValue keyValue,
boolean first) {
470 this.keyFrame = keyFrame;
471 this.keyValue = keyValue;
482 private static class MapOfLists<K, V>
extends HashMap<K, List<V>> {
490 public void add(K key, V value) {
491 List<V> p =
get(key);
493 p =
new ArrayList<>();
509 SortedList<KeyFrame> sortedKeyFrames =
timeline.getKeyFrames().sorted(
new KeyFrameComparator());
510 MapOfLists<KeyFrame, KeyValue> toRemove =
new MapOfLists<>();
511 Map<WritableValue, KeyInfo> prevValues =
new HashMap<>();
512 Map<WritableValue, KeyInfo> prevPrevValues =
new HashMap<>();
514 for (KeyFrame keyFrame : sortedKeyFrames) {
515 for (KeyValue keyValue : keyFrame.getValues()) {
516 WritableValue<?> target = keyValue.getTarget();
517 KeyInfo prev = prevValues.get(target);
519 if (prev !=
null && prev.keyValue.getEndValue().equals(keyValue.getEndValue())) {
521 KeyInfo prevPrev = prevPrevValues.get(target);
522 if ((prevPrev !=
null && prevPrev.keyValue.getEndValue().equals(keyValue.getEndValue()))
523 || (prev.first && target.getValue().equals(prev.keyValue.getEndValue()))) {
526 toRemove.add(prev.keyFrame, prev.keyValue);
528 prevPrevValues.put(target, prev);
538 KeyInfo oldPrev = prevValues.put(target,
new KeyInfo(keyFrame, keyValue, prev ==
null));
539 if (oldPrev !=
null) prevPrevValues.put(target, oldPrev);
543 for (WritableValue target : prevValues.keySet()) {
544 KeyInfo prev = prevValues.get(target);
545 KeyInfo prevPrev = prevPrevValues.get(target);
546 if (prevPrev !=
null && prevPrev.keyValue.getEndValue().equals(prev.keyValue.getEndValue())) {
548 toRemove.add(prev.keyFrame, prev.keyValue);
552 int kfRemoved = 0, kfTotal =
timeline.getKeyFrames().size(), kfSimplified = 0, kfNotRemoved = 0;
554 List<KeyValue> newKeyValues =
new ArrayList<>();
555 for (
int i = 0; i <
timeline.getKeyFrames().size(); i++) {
556 KeyFrame keyFrame =
timeline.getKeyFrames().get(i);
557 List<KeyValue> keyValuesToRemove = toRemove.get(keyFrame);
558 if (keyValuesToRemove !=
null) {
559 newKeyValues.clear();
560 for (KeyValue keyValue : keyFrame.getValues()) {
561 if (keyValuesToRemove.remove(keyValue)) {
565 newKeyValues.add(
new KeyValue((WritableValue)keyValue.getTarget(), keyValue.getEndValue(), Interpolator.DISCRETE));
567 newKeyValues.add(keyValue);
572 newKeyValues.clear();
573 for (KeyValue keyValue : keyFrame.getValues()) {
574 newKeyValues.add(
new KeyValue((WritableValue)keyValue.getTarget(), keyValue.getEndValue(), Interpolator.DISCRETE));
578 if (newKeyValues.isEmpty()) {
579 if (keyFrame.getOnFinished() ==
null) {
580 if (keyFrame.getName() !=
null) {
581 System.err.println(
"Removed KeyFrame with name = " + keyFrame.getName());
591 keyFrame =
new KeyFrame(keyFrame.getTime(), keyFrame.getName(), keyFrame.getOnFinished(), newKeyValues);
592 timeline.getKeyFrames().set(i, keyFrame);
597 for (KeyValue keyValue : keyFrame.getValues()) {
598 WritableValue<?> target = keyValue.getTarget();
599 if (target instanceof Property) {
600 Property p = (Property) target;
601 Object bean = p.getBean();
602 if (bean instanceof Transform) {
603 bound.add((Transform) bean);
605 throw new UnsupportedOperationException(
"Bean is not transform, bean = " + bean);
608 throw new UnsupportedOperationException(
"WritableValue is not property, can't identify what it changes, target = " + target);
613 System.out.printf(
"Removed %d (%.2f%%) repeating KeyValues out of total %d.\n", kvRemoved, 100d * kvRemoved / kvTotal, kvTotal);
614 System.out.printf(
"Removed %d (%.2f%%) and simplified %d (%.2f%%) KeyFrames out of total %d. %d (%.2f%%) were not removed due to event handler attached.\n",
615 kfRemoved, 100d * kfRemoved / kfTotal,
616 kfSimplified, 100d * kfSimplified / kfTotal, kfTotal, kfNotRemoved, 100d * kfNotRemoved / kfTotal);
618 for (KeyFrame keyFrame :
timeline.getKeyFrames()) {
619 check += keyFrame.getValues().size();
626 System.out.printf(
"Now there are %d KeyValues and %d KeyFrames.\n", check,
timeline.getKeyFrames().size());
634 Parent parent = p.getParent();
635 Group g = (Group) parent;
636 g.getChildren().addAll(p.getChildrenUnmodifiable());
637 g.getChildren().remove(p);
644 private static class KeyFrameComparator
implements Comparator<KeyFrame> {
649 public KeyFrameComparator() {
655 @Override
public int compare(KeyFrame o1, KeyFrame o2) {
660 return o1.getTime().compareTo(o2.getTime());
List< Parent > emptyParents
Optimizer(Timeline timeline, Node root, boolean convertToDiscrete)
List< MeshView > meshViews
boolean convertToDiscrete
Optimizer(Timeline timeline, Node root)
void cleanUpRepeatingFramesAndValues()