From 6075e4639d3c15f7ed497db13c5a60aeeb282b82 Mon Sep 17 00:00:00 2001 From: William Linna Date: Sat, 14 Nov 2015 16:19:54 +0200 Subject: [PATCH 01/24] Interpolate particle positions NOTE: This change is not invented by me. All credit goes to methusalah. See this thread: http://hub.jmonkeyengine.org/t/interpolation-of-particle-spawning-point/30385/7 --- .../main/java/com/jme3/effect/ParticleEmitter.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index 28bfd595d..6a05e359c 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -106,6 +106,7 @@ public class ParticleEmitter extends Geometry { private boolean worldSpace = true; //variable that helps with computations private transient Vector3f temp = new Vector3f(); + private transient Vector3f lastPos; public static class ParticleEmitterControl implements Control { @@ -1013,12 +1014,16 @@ public class ParticleEmitter extends Geometry { // Spawns particles within the tpf timeslot with proper age float interval = 1f / particlesPerSec; + float originalTpf = tpf; tpf += timeDifference; while (tpf > interval){ tpf -= interval; Particle p = emitParticle(min, max); if (p != null){ p.life -= tpf; + if (lastPos != null) { + p.position.interpolateLocal(lastPos, 1 - tpf / originalTpf); + } if (p.life <= 0){ freeParticle(lastUsed); }else{ @@ -1028,6 +1033,12 @@ public class ParticleEmitter extends Geometry { } timeDifference = tpf; + if (lastPos == null) { + lastPos = new Vector3f(); + } + + lastPos.set(getWorldTranslation()); + BoundingBox bbox = (BoundingBox) this.getMesh().getBound(); bbox.setMinMax(min, max); this.setBoundRefresh(); From c8a16c940f877728198f7da8e976cd117432b51e Mon Sep 17 00:00:00 2001 From: William Linna Date: Sat, 14 Nov 2015 16:32:44 +0200 Subject: [PATCH 02/24] Only interpolate if particles are in world space --- jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index 6a05e359c..ca3467781 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -1021,7 +1021,7 @@ public class ParticleEmitter extends Geometry { Particle p = emitParticle(min, max); if (p != null){ p.life -= tpf; - if (lastPos != null) { + if (lastPos != null && isInWorldSpace()) { p.position.interpolateLocal(lastPos, 1 - tpf / originalTpf); } if (p.life <= 0){ From 142d7bebae3ed8f988557294093eff9e516fc7b1 Mon Sep 17 00:00:00 2001 From: David Bernard Date: Thu, 28 Jan 2016 22:06:21 +0100 Subject: [PATCH 03/24] build: upload on bintray from travis when a git tag is set and it starts by jmeMainVersion --- .travis.yml | 3 +++ bintray.gradle | 26 ++++++++++++++++++++++++++ build.gradle | 8 ++++++-- common.gradle | 2 +- gradle.properties | 4 ++++ version.gradle | 6 +++++- 6 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 bintray.gradle diff --git a/.travis.yml b/.travis.yml index 48e75d0b4..8874544b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,3 +54,6 @@ before_install: # wget http://dl.google.com/android/ndk/android-ndk-r10c-linux-x86_64.bin -O ndk.bin # 7z x ndk.bin -y > /dev/null # export ANDROID_NDK=`pwd`/android-ndk-r10c + +after_success: + - '[ -n "$TRAVIS_TAG"" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew bintrayUpload || :' diff --git a/bintray.gradle b/bintray.gradle new file mode 100644 index 000000000..74671c6a5 --- /dev/null +++ b/bintray.gradle @@ -0,0 +1,26 @@ +// +// This file is to be applied to some subproject. +// + +apply plugin: 'com.jfrog.bintray' + +bintray { + user = bintray_user + key = bintray_api_key + configurations = ['archives'] + pkg { + repo = 'org.jmonkeyengine' + userOrg = 'jmonkeyengine' + name = project.name + desc = POM_DESCRIPTION + websiteUrl = POM_URL + licenses = ['BSD New'] + vcsUrl = POM_SCM_CONNECTION + labels = ['jmonkeyengine'] + } +} + +bintrayUpload.onlyIf { + (bintray_api_key.length() > 0) && + !(version ==~ /.*SNAPSHOT/) +} diff --git a/build.gradle b/build.gradle index 192bc32d3..2d4fe1e3d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,11 @@ import org.gradle.api.artifacts.* buildscript { repositories { - mavenCentral() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.1.0' + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.5' } } @@ -17,6 +18,9 @@ apply from: file('upload.gradle') subprojects { if(!project.name.equals('jme3-android-examples')) { apply from: rootProject.file('common.gradle') + if (!['jme3-testdata', 'sdk'].contains(project.name)) { + apply from: rootProject.file('bintray.gradle') + } } else { apply from: rootProject.file('common-android-app.gradle') } @@ -174,4 +178,4 @@ task configureAndroidNDK { // tasks.withType(Test) { // enableAssertions = true // true by default // } -//} \ No newline at end of file +//} diff --git a/common.gradle b/common.gradle index 237ee5e7f..5da5ce9d1 100644 --- a/common.gradle +++ b/common.gradle @@ -5,7 +5,7 @@ apply plugin: 'java' apply plugin: 'maven' -group = 'com.jme3' +group = 'org.jmonkeyengine' version = jmePomVersion sourceCompatibility = '1.6' diff --git a/gradle.properties b/gradle.properties index 4380c31a0..f4c74445a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -37,3 +37,7 @@ POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine. POM_LICENSE_NAME=New BSD (3-clause) License POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause POM_LICENSE_DISTRIBUTION=repo + +# Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline +bintray_user= +bintray_api_key= diff --git a/version.gradle b/version.gradle index 40e934278..55982039b 100644 --- a/version.gradle +++ b/version.gradle @@ -76,7 +76,11 @@ task configureVersionInfo { jmeFullVersion = jmeMainVersion jmePomVersion = jmeVersion - if (jmeBranchName != "master") { + if (jmeGitTag.startsWith(jmeMainVersion)) { + jmeVersionTag = "" + jmePomVersion = jmeGitTag + } + if (jmeBranchName != "master" && jmeVersionTag == "SNAPSHOT") { jmeFullVersion += "-${jmeBranchName}" jmePomVersion += "-${jmeBranchName}" From 2aca942b1bc34f9d3b78e30bafd4d81d4668e727 Mon Sep 17 00:00:00 2001 From: David Bernard Date: Sat, 6 Feb 2016 10:47:57 +0100 Subject: [PATCH 04/24] build: use the same custom pom file for 'install', 'uploadArchives' and 'uploadBintray'. A custom pom with minimal information required to maven central --- bintray.gradle | 5 ++- common.gradle | 90 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/bintray.gradle b/bintray.gradle index 74671c6a5..0deafc8cf 100644 --- a/bintray.gradle +++ b/bintray.gradle @@ -8,6 +8,7 @@ bintray { user = bintray_user key = bintray_api_key configurations = ['archives'] + dryRun = false pkg { repo = 'org.jmonkeyengine' userOrg = 'jmonkeyengine' @@ -15,11 +16,13 @@ bintray { desc = POM_DESCRIPTION websiteUrl = POM_URL licenses = ['BSD New'] - vcsUrl = POM_SCM_CONNECTION + vcsUrl = POM_SCM_URL labels = ['jmonkeyengine'] } } +bintrayUpload.dependsOn(writeFullPom) + bintrayUpload.onlyIf { (bintray_api_key.length() > 0) && !(version ==~ /.*SNAPSHOT/) diff --git a/common.gradle b/common.gradle index 5da5ce9d1..a85abe742 100644 --- a/common.gradle +++ b/common.gradle @@ -61,12 +61,84 @@ task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from from javadoc.destinationDir } +def pomConfig = { + name POM_NAME + description POM_DESCRIPTION + url POM_URL + inceptionYear '2016' + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEVELOPER_CONNECTION + } + licenses { + license { + name POM_LICENSE_NAME + url POM_LICENSE_URL + distribution POM_LICENSE_DISTRIBUTION + } + } + // from http://hub.jmonkeyengine.org/introduction/team/ + developers { + developer { + name 'Kirill Vainer' + id 'Momoko_Fan' + } + developer { + name 'Erlend Sogge Heggen' + id 'erlend_sh' + } + developer { + name 'Skye Book' + id 'sbook' + } + developer { + name 'Normen Hansen' + id 'normen' + } + developer { + name 'Ruth Kusterer' + id 'zathras' + } + developer { + name 'Rémy Bouquet' + id 'nehon' + } + developer { + name 'Paul Speed' + id 'pspeed' + } + developer { + name 'Brent Owens' + id 'Sploreg' + } + developer { + name 'Eric Potter' + id 'iwgeric' + } + } +} + +// workaround to be able to use same custom pom with 'maven' and 'bintray' plugin +task writeFullPom { + ext.pomFile = "$mavenPomDir/${project.name}-${project.version}.pom" + outputs.file pomFile + doLast { + pom { + project pomConfig + }.writeTo(pomFile) + } +} +install.dependsOn(writeFullPom) +uploadArchives.dependsOn(writeFullPom) + artifacts { archives jar archives sourcesJar if(buildJavaDoc == "true"){ archives javadocJar } + archives writeFullPom.outputs.files[0] } uploadArchives { @@ -80,23 +152,7 @@ uploadArchives { authentication(userName: "www-updater", privateKey: "private/www-updater.key") } - pom.project { - name POM_NAME - description POM_DESCRIPTION - url POM_URL - scm { - url POM_SCM_URL - connection POM_SCM_CONNECTION - developerConnection POM_SCM_DEVELOPER_CONNECTION - } - licenses { - license { - name POM_LICENSE_NAME - url POM_LICENSE_URL - distribution POM_LICENSE_DISTRIBUTION - } - } - } + pom.project pomConfig } } From 80c11a5f0257bd23651ab34b6ead47710564289c Mon Sep 17 00:00:00 2001 From: David Bernard Date: Sat, 6 Feb 2016 11:04:22 +0100 Subject: [PATCH 05/24] build: fix sdk build failure due to writeFullPom --- common.gradle | 1 + sdk/build.gradle | 1 + 2 files changed, 2 insertions(+) diff --git a/common.gradle b/common.gradle index a85abe742..c8fb251ba 100644 --- a/common.gradle +++ b/common.gradle @@ -129,6 +129,7 @@ task writeFullPom { }.writeTo(pomFile) } } +assemble.dependsOn(writeFullPom) install.dependsOn(writeFullPom) uploadArchives.dependsOn(writeFullPom) diff --git a/sdk/build.gradle b/sdk/build.gradle index 99c850987..1a841ef0e 100644 --- a/sdk/build.gradle +++ b/sdk/build.gradle @@ -109,6 +109,7 @@ task createBaseXml(dependsOn: configurations.corelibs) <<{ dep.dependencyProject.configurations.archives.allArtifacts.each{ artifact-> if(artifact.classifier == "sources"){ } else if(artifact.classifier == "javadoc"){ + } else if(artifact.file.name.endsWith('.pom')) { } else{ if(!jmeJarFiles.contains(artifact.file)){ jmeJarFiles.add(artifact.file) From 6f6e93e01be225327a233fd02e3fd614ae0ee048 Mon Sep 17 00:00:00 2001 From: MeFisto94 Date: Fri, 19 Feb 2016 00:59:15 +0100 Subject: [PATCH 06/24] Simplified the AudioNode constructors (#342) and allow looping/seeking on every AudioNode now by default. --- .../main/java/com/jme3/audio/AudioNode.java | 72 +++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 8664af6c7..2ff4134ab 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2012, 2016 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -86,6 +86,7 @@ public class AudioNode extends Node implements AudioSource { protected float innerAngle = 360; protected float outerAngle = 360; protected boolean positional = true; + protected Type type = null; /** * Status indicates the current status of the audio node. @@ -112,6 +113,26 @@ public class AudioNode extends Node implements AudioSource { Stopped, } + /** + * Type indicates how to retrieve the audio data. + * It replaced the old "stream" and "streamCache" parameters. + * It defines whether the whole file is read and buffered or + * if it is read gradually from disk. + */ + public enum Type { + /** + * The audio data will be loaded as whole and be buffered in memory. + * Use this for short sounds. + */ + Buffered, + + /** + * The audio data will be streamed gradually from disk. + * Use this for longer sounds. + * Note: looping and seeking is supported. + */ + Streaming, + } /** * Creates a new AudioNode without any audio data set. */ @@ -126,6 +147,22 @@ public class AudioNode extends Node implements AudioSource { */ public AudioNode(AudioData audioData, AudioKey audioKey) { setAudioData(audioData, audioKey); + if (audioKey.isStream()) { + type = Type.Streaming; + } else { + type = Type.Buffered; + } + } + + /** + * Creates a new AudioNode with the given audio file. + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + * @param type The type. If Type.Streaming, the audio will be streamed gradually from disk, + * otherwise it will be buffered (Type.Buffered) + */ + public AudioNode(AssetManager assetManager, String name, Type type) { + this(assetManager, name, type == Type.Streaming, true); } /** @@ -139,10 +176,17 @@ public class AudioNode extends Node implements AudioSource { * the stream cache is used. When enabled, the audio stream will * be read entirely but not decoded, allowing features such as * seeking, looping and determining duration. + * + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioNode.Type)} instead */ public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) { this.audioKey = new AudioKey(name, stream, streamCache); this.data = (AudioData) assetManager.loadAsset(audioKey); + if (stream) { + type = Type.Streaming; + } else { + type = Type.Buffered; + } } /** @@ -152,9 +196,11 @@ public class AudioNode extends Node implements AudioSource { * @param name The filename of the audio file * @param stream If true, the audio will be streamed gradually from disk, * otherwise, it will be buffered. + * + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioNode.Type)} instead */ public AudioNode(AssetManager assetManager, String name, boolean stream) { - this(assetManager, name, stream, false); + this(assetManager, name, stream, true); // Always streamCached } /** @@ -167,7 +213,7 @@ public class AudioNode extends Node implements AudioSource { * @deprecated AudioRenderer parameter is ignored. */ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) { - this(assetManager, name, false); + this(assetManager, name, Type.Buffered); } /** @@ -175,9 +221,10 @@ public class AudioNode extends Node implements AudioSource { * * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioNode.Type)} instead */ public AudioNode(AssetManager assetManager, String name) { - this(assetManager, name, false); + this(assetManager, name, Type.Buffered); } protected AudioRenderer getRenderer() { @@ -283,6 +330,12 @@ public class AudioNode extends Node implements AudioSource { data = audioData; this.audioKey = audioKey; + + if (audioKey.isStream()) { + type = Type.Streaming; + } else { + type = Type.Buffered; + } } /** @@ -310,6 +363,17 @@ public class AudioNode extends Node implements AudioSource { this.status = status; } + /** + * This is set only once in the constructor. + * It defines, whether the underlying Data is Buffered or + * Streamed continuously. + * Warning: Can return null! + * @return The {@link Type} of the audio node. + */ + public Type getType() { + return type; + } + /** * @return True if the audio will keep looping after it is done playing, * otherwise, false. From ecef8ff521f85fae5d91577202e833ed59a3f05f Mon Sep 17 00:00:00 2001 From: MeFisto94 Date: Tue, 23 Feb 2016 01:42:32 +0100 Subject: [PATCH 07/24] Two small ShaderNodes fixes --- .../editor/MatDefEditorToolBar.java | 16 ++++++++++- .../fileStructure/TechniqueBlock.java | 27 ++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java index 5ad859fcf..d33ee26d5 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/editor/MatDefEditorToolBar.java @@ -8,6 +8,8 @@ package com.jme3.gde.materialdefinition.editor; import com.jme3.gde.materialdefinition.fileStructure.TechniqueBlock; import java.awt.Component; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.JLabel; @@ -23,7 +25,8 @@ public class MatDefEditorToolBar extends javax.swing.JPanel { private MatDefEditorlElement parent; private final DefaultComboBoxModel comboModel = new DefaultComboBoxModel(); - + private final static Logger logger = Logger.getLogger(MatDefEditorToolBar.class.getName()); + /** * Creates new form MatDefEditorToolBar */ @@ -130,6 +133,17 @@ public class MatDefEditorToolBar extends javax.swing.JPanel { }// //GEN-END:initComponents private void techniqueComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_techniqueComboBoxActionPerformed + if (techniqueComboBox.getSelectedItem() == null) { + if (techniqueComboBox.getItemCount() > 0) { + if (techniqueComboBox.getItemCount() > 1) { + logger.log(Level.WARNING, "No Technique selected, taking the first one!"); /* Don't be over verbose: When there's only one Element, you can't select itself again, thus null */ + } + techniqueComboBox.setSelectedIndex(0); /* Take the first one available */ + } else { + logger.log(Level.WARNING, "No Techniques known for this MaterialDef. Please add one using the button to the right!"); + return; + } + } parent.switchTechnique((TechniqueBlock) techniqueComboBox.getSelectedItem()); }//GEN-LAST:event_techniqueComboBoxActionPerformed diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java index 26bba097c..665a1f628 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/fileStructure/TechniqueBlock.java @@ -15,6 +15,8 @@ import com.jme3.util.blockparser.Statement; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.openide.util.WeakListeners; /** @@ -28,6 +30,8 @@ public class TechniqueBlock extends UberStatement { public static final String ADD_WORLD_PARAM = "addWorldParam"; public static final String REMOVE_WORLD_PARAM = "removeWorldParam"; protected String name; + + private static final Logger logger = Logger.getLogger(TechniqueBlock.class.getName()); protected TechniqueBlock(int lineNumber, String line) { super(lineNumber, line); @@ -102,7 +106,13 @@ public class TechniqueBlock extends UberStatement { } public List getWorldParams() { - return getWorldParameters().getWorldParams(); + WorldParametersBlock block = getWorldParameters(); + if (block != null) + return getWorldParameters().getWorldParams(); + else { + logger.log(Level.WARNING, "Unable to build ShaderNodes: Could not find any WorldParameters. Most likely the technique {0} is broken.", line); + return new ArrayList(); + } } public void addWorldParam(WorldParamBlock block) { @@ -180,8 +190,19 @@ public class TechniqueBlock extends UberStatement { public List getShaderNodes() { List list = new ArrayList(); - list.addAll(getBlock(VertexShaderNodesBlock.class).getShaderNodes()); - list.addAll(getBlock(FragmentShaderNodesBlock.class).getShaderNodes()); + + VertexShaderNodesBlock vert_block = getBlock(VertexShaderNodesBlock.class); + if (vert_block == null) + logger.log(Level.WARNING, "Unable to build ShaderNodes: Could not find any VertexShaderNode. Most likely the technique {0} is broken.", line); + else + list.addAll(vert_block.getShaderNodes()); + + FragmentShaderNodesBlock frag_block = getBlock(FragmentShaderNodesBlock.class); + if (frag_block == null) + logger.log(Level.WARNING, "Unable to build ShaderNodes: Could not find any FragmentShaderNode. Most likely the technique {0} is broken.", line); + else + list.addAll(frag_block.getShaderNodes()); + return list; } From 9416387111b88fae4e8ccc7aa9a5cca7789d4097 Mon Sep 17 00:00:00 2001 From: David Bernard Date: Tue, 23 Feb 2016 14:32:24 +0100 Subject: [PATCH 08/24] =?UTF-8?q?build:=20release=20version=20defined=20fr?= =?UTF-8?q?om=20tag=20in=20format=20=E2=80=9CvX.Y.Z=E2=80=9D=20and=20?= =?UTF-8?q?=E2=80=9CvX.Y.Z-M=E2=80=9D.=20Refactor=20to=20clearly=20split?= =?UTF-8?q?=20behaviour=20when=20release=20(based=20on=20tag)=20or=20not?= =?UTF-8?q?=20(SNAPSHOT).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.gradle | 139 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 47 deletions(-) diff --git a/version.gradle b/version.gradle index 55982039b..501132ac5 100644 --- a/version.gradle +++ b/version.gradle @@ -3,27 +3,31 @@ ===================== Nightly Build Snapshot + * git tag: * Full Version: 3.1-5124 * POM Version: 3.1.0-SNAPSHOT * NBM Revision: 5124 * NBM UC Suffix: nightly/3.1/plugins Nightly Build Snapshot (PBRIsComing branch) + * git tag: * Full Version: 3.1-PBRIsComing-5124 * POM Version: 3.1.0-PBRIsComing-SNAPSHOT * NBM Revision: 5124 * NBM UC Suffix: PBRIsComing-nightly/3.1/plugins Alpha1 Release + * git tag: v3.1.0-alpha1 * Full Version: 3.1-alpha1 * POM Version: 3.1.0-alpha1 - * NBM Revision: 1 + * NBM Revision: 0 * NBM UC Suffix: stable/3.1/plugins Final Release + * git tag: v3.1.0 * Full Version: 3.1 * POM Version: 3.1.0 - * NBM Revision: 5 + * NBM Revision: 0 * NBM UC Suffix: stable/3.1/plugins */ @@ -52,6 +56,63 @@ ext { jmeNbmUcSuffix = "unknown" } +def getReleaseInfo(String tag) { + if (tag == null) { + // not a tagged commit + return null; + } + if (!tag.startsWith("v")) { + // syntax error + return null; + } + tag = tag.substring(1) + + String[] parts = tag.split("-"); + String mainVersion; + boolean prerelease; + String releaseName = null; + + if (parts.length == 2) { + // prerelease + prerelease = true; + mainVersion = parts[0]; + releaseName = parts[1]; + if (releaseName.size() == 0) { + // syntax error + return null; + } + } else if (parts.length == 1) { + // final release + prerelease = false; + mainVersion = parts[0]; + } else { + // error + return null; + } + + if (mainVersion.size() == 0) { + // syntax error + return null; + } + + parts = mainVersion.split("\\."); + if (parts.size() != 3) { + // syntax error + return null; + } + + String baseVersion = parts[0] + "." + parts[1]; + + return [ + "tag" : tag, + "baseVersion" : baseVersion, + "mainVersion" : mainVersion, + "prerelease" : prerelease, + "releaseName" : releaseName, + "releaseSuffix": (prerelease ? "-${releaseName}": "") + ] +} + task configureVersionInfo { try { def grgit = Grgit.open(project.file('.')) @@ -59,56 +120,40 @@ task configureVersionInfo { jmeGitHash = grgit.head().id jmeShortGitHash = grgit.head().abbreviatedId jmeBranchName = grgit.branch.current.name - jmeGitTag = grgit.describe() - if (jmeGitTag == null) jmeGitTag = "" - - if (System.env.TRAVIS_BRANCH != null) { - jmeBranchName = System.env.TRAVIS_BRANCH - } - if (System.env.TRAVIS_TAG != null) { - jmeGitTag = System.env.TRAVIS_TAG - } - if (System.env.TRAVIS_PULL_REQUEST != null && - System.env.TRAVIS_PULL_REQUEST != "false") { - jmeBranchName += "-pr-" + System.env.TRAVIS_PULL_REQUEST - } - - jmeFullVersion = jmeMainVersion - jmePomVersion = jmeVersion - - if (jmeGitTag.startsWith(jmeMainVersion)) { - jmeVersionTag = "" - jmePomVersion = jmeGitTag - } - if (jmeBranchName != "master" && jmeVersionTag == "SNAPSHOT") { - jmeFullVersion += "-${jmeBranchName}" - jmePomVersion += "-${jmeBranchName}" - - jmeNbmUcSuffix = "${jmeBranchName}-" - } else { - jmeNbmUcSuffix = "" - } - - if (jmeVersionTag == "SNAPSHOT") { - jmeNbmUcSuffix += "nightly" + //gtgit.describe doesn't behave like git describe and it doens't support any option + //jmeGitTag = grgit.describe() ?: System.env.TRAVIS_TAG + jmeGitTag = "git describe --tags --exact-match --dirty".execute().text.trim() ?: System.env.TRAVIS_TAG + + def releaseInfo = getReleaseInfo(jmeGitTag) + if (releaseInfo != null) { + jmeFullVersion = "${releaseInfo.baseVersion}${releaseInfo.releaseSuffix}" + jmePomVersion = "${releaseInfo.mainVersion}${releaseInfo.releaseSuffix}" + jmeNbmRevision = "0" + jmeNbmUcSuffix = "stable/${releaseInfo.baseVersion}/plugins" } else { - jmeNbmUcSuffix += "stable" - } - - jmeNbmUcSuffix += "/" + jmeMainVersion + "/plugins" - - if (jmeVersionTag == "SNAPSHOT") { + // SNAPSHOT + jmeFullVersion = jmeMainVersion + jmePomVersion = jmeVersion + if (System.env.TRAVIS_BRANCH != null) { + jmeBranchName = System.env.TRAVIS_BRANCH + } + if (System.env.TRAVIS_PULL_REQUEST != null && + System.env.TRAVIS_PULL_REQUEST != "false") { + jmeBranchName += "-pr-" + System.env.TRAVIS_PULL_REQUEST + } + if (jmeBranchName != "master") { + jmeFullVersion += "-${jmeBranchName}" + jmePomVersion += "-${jmeBranchName}" + jmeNbmUcSuffix = "${jmeBranchName}-" + } else { + jmeNbmUcSuffix = "" + } + jmeNbmUcSuffix += "nightly/" + jmeMainVersion + "/plugins" jmeFullVersion += "-${jmeRevision}" jmePomVersion += "-SNAPSHOT" jmeNbmRevision = jmeRevision - } else if (jmeVersionTag == "") { - jmeNbmRevision = jmeVersionTagID - } else { - jmeFullVersion += "-${jmeVersionTag}" - jmePomVersion += "-${jmeVersionTag}" - jmeNbmRevision = jmeVersionTagID } - + logger.warn("Full Version: ${jmeFullVersion}") logger.warn("POM Version: ${jmePomVersion}") logger.warn("NBM Revision: ${jmeNbmRevision}") From f39ff628643c23fed59d4ff5ebb326db07d51efd Mon Sep 17 00:00:00 2001 From: David Bernard Date: Tue, 23 Feb 2016 14:37:46 +0100 Subject: [PATCH 09/24] =?UTF-8?q?build:=20replace=20developers=20list=20by?= =?UTF-8?q?=20generic=20=E2=80=98jMonkeyEngine=20Team=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common.gradle | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/common.gradle b/common.gradle index c8fb251ba..d275c19f5 100644 --- a/common.gradle +++ b/common.gradle @@ -81,40 +81,8 @@ def pomConfig = { // from http://hub.jmonkeyengine.org/introduction/team/ developers { developer { - name 'Kirill Vainer' - id 'Momoko_Fan' - } - developer { - name 'Erlend Sogge Heggen' - id 'erlend_sh' - } - developer { - name 'Skye Book' - id 'sbook' - } - developer { - name 'Normen Hansen' - id 'normen' - } - developer { - name 'Ruth Kusterer' - id 'zathras' - } - developer { - name 'Rémy Bouquet' - id 'nehon' - } - developer { - name 'Paul Speed' - id 'pspeed' - } - developer { - name 'Brent Owens' - id 'Sploreg' - } - developer { - name 'Eric Potter' - id 'iwgeric' + name 'jMonkeyEngine Team' + id 'jMonkeyEngine' } } } From 4815016906529fb05686d52fb8ff210dbd03acc7 Mon Sep 17 00:00:00 2001 From: David Bernard Date: Tue, 23 Feb 2016 17:02:30 +0100 Subject: [PATCH 10/24] build: travis move uploadArchives to after_success section --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8874544b0..519094ed7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,6 @@ install: script: - ./gradlew check - ./gradlew createZipDistribution - - "[ $TRAVIS_BRANCH == 'master' ] && [ $TRAVIS_PULL_REQUEST == 'false' ] && ./gradlew uploadArchives || :" before_deploy: - export RELEASE_DIST=$(ls build/distributions/*.zip) @@ -56,4 +55,5 @@ before_install: # export ANDROID_NDK=`pwd`/android-ndk-r10c after_success: + - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives || :' - '[ -n "$TRAVIS_TAG"" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew bintrayUpload || :' From 8e866561b80fc076960439d432f6dfe8019bb1d0 Mon Sep 17 00:00:00 2001 From: David Bernard Date: Tue, 23 Feb 2016 17:06:34 +0100 Subject: [PATCH 11/24] build: travis fix typo double double quote --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 519094ed7..085c39e53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,4 +56,4 @@ before_install: after_success: - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives || :' - - '[ -n "$TRAVIS_TAG"" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew bintrayUpload || :' + - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew bintrayUpload || :' From c90048595f4b310cce3f2164a299d2c13f79adb7 Mon Sep 17 00:00:00 2001 From: MeFisto94 Date: Wed, 24 Feb 2016 02:46:16 +0100 Subject: [PATCH 12/24] Removed AudioNode.Type in favor of AudioData.DataType, which is what we are using now. --- .../main/java/com/jme3/audio/AudioNode.java | 70 +++++-------------- 1 file changed, 18 insertions(+), 52 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 2ff4134ab..2c5cc5b98 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -33,6 +33,7 @@ package com.jme3.audio; import com.jme3.asset.AssetManager; import com.jme3.asset.AssetNotFoundException; +import com.jme3.audio.AudioData.DataType; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; @@ -86,7 +87,6 @@ public class AudioNode extends Node implements AudioSource { protected float innerAngle = 360; protected float outerAngle = 360; protected boolean positional = true; - protected Type type = null; /** * Status indicates the current status of the audio node. @@ -113,26 +113,6 @@ public class AudioNode extends Node implements AudioSource { Stopped, } - /** - * Type indicates how to retrieve the audio data. - * It replaced the old "stream" and "streamCache" parameters. - * It defines whether the whole file is read and buffered or - * if it is read gradually from disk. - */ - public enum Type { - /** - * The audio data will be loaded as whole and be buffered in memory. - * Use this for short sounds. - */ - Buffered, - - /** - * The audio data will be streamed gradually from disk. - * Use this for longer sounds. - * Note: looping and seeking is supported. - */ - Streaming, - } /** * Creates a new AudioNode without any audio data set. */ @@ -147,22 +127,17 @@ public class AudioNode extends Node implements AudioSource { */ public AudioNode(AudioData audioData, AudioKey audioKey) { setAudioData(audioData, audioKey); - if (audioKey.isStream()) { - type = Type.Streaming; - } else { - type = Type.Buffered; - } } /** * Creates a new AudioNode with the given audio file. * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file - * @param type The type. If Type.Streaming, the audio will be streamed gradually from disk, - * otherwise it will be buffered (Type.Buffered) + * @param type The type. If {@link com.jme3.audio.AudioData.DataType}.Stream, the audio will be streamed gradually from disk, + * otherwise it will be buffered ({@link com.jme3.audio.AudioData.DataType}.Buffer) */ - public AudioNode(AssetManager assetManager, String name, Type type) { - this(assetManager, name, type == Type.Streaming, true); + public AudioNode(AssetManager assetManager, String name, DataType type) { + this(assetManager, name, type == DataType.Stream, true); } /** @@ -177,16 +152,11 @@ public class AudioNode extends Node implements AudioSource { * be read entirely but not decoded, allowing features such as * seeking, looping and determining duration. * - * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioNode.Type)} instead + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead */ public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) { this.audioKey = new AudioKey(name, stream, streamCache); this.data = (AudioData) assetManager.loadAsset(audioKey); - if (stream) { - type = Type.Streaming; - } else { - type = Type.Buffered; - } } /** @@ -197,7 +167,7 @@ public class AudioNode extends Node implements AudioSource { * @param stream If true, the audio will be streamed gradually from disk, * otherwise, it will be buffered. * - * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioNode.Type)} instead + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead */ public AudioNode(AssetManager assetManager, String name, boolean stream) { this(assetManager, name, stream, true); // Always streamCached @@ -213,7 +183,7 @@ public class AudioNode extends Node implements AudioSource { * @deprecated AudioRenderer parameter is ignored. */ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) { - this(assetManager, name, Type.Buffered); + this(assetManager, name, DataType.Buffer); } /** @@ -221,10 +191,10 @@ public class AudioNode extends Node implements AudioSource { * * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file - * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioNode.Type)} instead + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType) } instead */ public AudioNode(AssetManager assetManager, String name) { - this(assetManager, name, Type.Buffered); + this(assetManager, name, DataType.Buffer); } protected AudioRenderer getRenderer() { @@ -330,12 +300,6 @@ public class AudioNode extends Node implements AudioSource { data = audioData; this.audioKey = audioKey; - - if (audioKey.isStream()) { - type = Type.Streaming; - } else { - type = Type.Buffered; - } } /** @@ -364,14 +328,16 @@ public class AudioNode extends Node implements AudioSource { } /** - * This is set only once in the constructor. - * It defines, whether the underlying Data is Buffered or - * Streamed continuously. + * Get the Type of the underlying AudioData to see if it's streamed or buffered. + * This is a shortcut to getAudioData().getType() * Warning: Can return null! - * @return The {@link Type} of the audio node. + * @return The {@link com.jme3.audio.AudioData.DataType} of the audio node. */ - public Type getType() { - return type; + public DataType getType() { + if (data == null) + return null; + else + return data.getDataType(); } /** From a6a066149e2ef27ae7a9cc3f4d785696c55c18bc Mon Sep 17 00:00:00 2001 From: grizeldi Date: Wed, 24 Feb 2016 21:16:56 +0100 Subject: [PATCH 13/24] Update Bundle.propreties to jME3.1 --- .../src/com/jme3/gde/welcome/Bundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties b/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties index aa85f4a47..3fdd7bf88 100644 --- a/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties +++ b/sdk/jme3-welcome-screen/src/com/jme3/gde/welcome/Bundle.properties @@ -3,6 +3,6 @@ OpenIDE-Module-Long-Description=\ The jMonkeyEngine GDE Welcome Screen OpenIDE-Module-Name=Welcome Screen OpenIDE-Module-Short-Description=The jMonkeyEngine GDE Welcome Screen -WelcomeScreenTopComponent.http.link=http://hub.jmonkeyengine.org/wiki/doku.php/sdk:welcome:3_0?do=export_xhtmlbody +WelcomeScreenTopComponent.http.link=http://hub.jmonkeyengine.org/wiki/doku.php/sdk:welcome:3_1?do=export_xhtmlbody WelcomeScreenTopComponent.rss.link=http://hub.jmonkeyengine.org/feed/rdf/ WelcomeScreenTopComponent.local.link=nbres:/com/jme3/gde/docs/sdk/welcome/local.html From c0af575487273296ef935b2d7cf670f503bdaa8a Mon Sep 17 00:00:00 2001 From: David Bernard Date: Wed, 24 Feb 2016 22:46:47 +0100 Subject: [PATCH 14/24] =?UTF-8?q?build:=20remove=20=E2=80=9Cgit=20?= =?UTF-8?q?=E2=80=A6=E2=80=9D.execute()=20to=20retrieve=20git=20tag.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- version.gradle | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/version.gradle b/version.gradle index 501132ac5..8b2b09074 100644 --- a/version.gradle +++ b/version.gradle @@ -116,13 +116,12 @@ def getReleaseInfo(String tag) { task configureVersionInfo { try { def grgit = Grgit.open(project.file('.')) - jmeRevision = grgit.log(includes:['HEAD']).size() - jmeGitHash = grgit.head().id - jmeShortGitHash = grgit.head().abbreviatedId + def head = grgit.head() + jmeRevision = grgit.log(includes: [head]).size() + jmeGitHash = head.id + jmeShortGitHash = head.abbreviatedId jmeBranchName = grgit.branch.current.name - //gtgit.describe doesn't behave like git describe and it doens't support any option - //jmeGitTag = grgit.describe() ?: System.env.TRAVIS_TAG - jmeGitTag = "git describe --tags --exact-match --dirty".execute().text.trim() ?: System.env.TRAVIS_TAG + jmeGitTag = grgit.tag.list().find { it.commit == head } ?: System.env.TRAVIS_TAG def releaseInfo = getReleaseInfo(jmeGitTag) if (releaseInfo != null) { From 3d8cbda2400d5fdc1df5b7284526641f0e0250a7 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Wed, 24 Feb 2016 18:48:06 -0500 Subject: [PATCH 15/24] version: fix tag parsing issues --- version.gradle | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 8b2b09074..f07c9d281 100644 --- a/version.gradle +++ b/version.gradle @@ -67,7 +67,7 @@ def getReleaseInfo(String tag) { } tag = tag.substring(1) - String[] parts = tag.split("-"); + String[] parts = tag.split("-", 2); String mainVersion; boolean prerelease; String releaseName = null; @@ -121,7 +121,13 @@ task configureVersionInfo { jmeGitHash = head.id jmeShortGitHash = head.abbreviatedId jmeBranchName = grgit.branch.current.name - jmeGitTag = grgit.tag.list().find { it.commit == head } ?: System.env.TRAVIS_TAG + jmeGitTag = grgit.tag.list().find { it.commit == head } + + if (jmeGitTag != null) { + jmeGitTag = jmeGitTag.name + } else { + jmeGitTag = System.env.TRAVIS_TAG + } def releaseInfo = getReleaseInfo(jmeGitTag) if (releaseInfo != null) { From bc8ecc3bc309c8df117550a9a9cba593d0991303 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 25 Feb 2016 10:24:46 -0500 Subject: [PATCH 16/24] Update Particle.vert --- jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert index 878c8e6da..705936943 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.vert @@ -32,7 +32,7 @@ void main(){ #ifdef POINT_SPRITE vec4 worldPos = g_WorldMatrix * pos; float d = distance(g_CameraPosition.xyz, worldPos.xyz); - float size = (inSize * SIZE_MULTIPLIER * m_Quadratic) / d); + float size = (inSize * SIZE_MULTIPLIER * m_Quadratic) / d; gl_PointSize = max(1.0, size); //vec4 worldViewPos = g_WorldViewMatrix * pos; From 67fef7e767962b9fc27be70d2cdf4267198f6032 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 25 Feb 2016 10:43:14 -0500 Subject: [PATCH 17/24] travis: test to find out why upload doesn't work --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 085c39e53..5f684cf51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,5 +55,9 @@ before_install: # export ANDROID_NDK=`pwd`/android-ndk-r10c after_success: + - '[ "$TRAVIS_BRANCH" == "master" ] && echo running on master' + - '[ "$TRAVIS_PULL_REQUEST" == "false" ] && echo not a PR' + - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && echo master and not PR' + - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && echo upload archives! || :' - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives || :' - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew bintrayUpload || :' From 78a921e3008cd16f54339dc0260f3c2d3136a646 Mon Sep 17 00:00:00 2001 From: InShadow Date: Thu, 25 Feb 2016 08:26:56 -0800 Subject: [PATCH 18/24] Forcing the project to always build JavaDoc with UTF-8 encoding. --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 2d4fe1e3d..a4a4259a6 100644 --- a/build.gradle +++ b/build.gradle @@ -90,6 +90,8 @@ task dist(dependsOn: [':jme3-examples:dist', 'mergedJavadoc']){ task mergedJavadoc(type: Javadoc, description: 'Creates Javadoc from all the projects.') { title = 'jMonkeyEngine3' destinationDir = mkdir("dist/javadoc") + + options.encoding = 'UTF-8' // Allows Javadoc to be generated on Java 8 despite doclint errors. if (JavaVersion.current().isJava8Compatible()) { From 1f4672264e942b036598b0ef88a7cf0d2aabf592 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 25 Feb 2016 11:09:26 -0500 Subject: [PATCH 19/24] travis: upload archives for tagged builds --- .travis.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5f684cf51..3d8e67840 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,9 +55,6 @@ before_install: # export ANDROID_NDK=`pwd`/android-ndk-r10c after_success: - - '[ "$TRAVIS_BRANCH" == "master" ] && echo running on master' - - '[ "$TRAVIS_PULL_REQUEST" == "false" ] && echo not a PR' - - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && echo master and not PR' - - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && echo upload archives! || :' + - '[ "$TRAVIS_BRANCH" == "master" ] && echo the branch is $TRAVIS_BRANCH' - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives || :' - - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew bintrayUpload || :' + - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives bintrayUpload || :' From d0c7bd988e9bae1e241e6359b5dc434201f2e2b4 Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Thu, 25 Feb 2016 12:00:39 -0500 Subject: [PATCH 20/24] travis: cleanup --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3d8e67840..fffa1ec68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,5 @@ before_install: # export ANDROID_NDK=`pwd`/android-ndk-r10c after_success: - - '[ "$TRAVIS_BRANCH" == "master" ] && echo the branch is $TRAVIS_BRANCH' - '[ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives || :' - '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew uploadArchives bintrayUpload || :' From 1073eba6bee240a0ebc41e0fa6106b85b2e6616f Mon Sep 17 00:00:00 2001 From: InShadow Date: Thu, 25 Feb 2016 20:41:33 -0800 Subject: [PATCH 21/24] Cleaned up BatchNode a bit and implemented collideWith to work only with non-batched children. --- .../main/java/com/jme3/scene/BatchNode.java | 127 +++++------------- 1 file changed, 33 insertions(+), 94 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index c3a0ab4bb..e1d4cce7f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -31,14 +31,6 @@ */ package com.jme3.scene; -import com.jme3.export.*; -import com.jme3.material.Material; -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector3f; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.SafeArrayList; -import com.jme3.util.TempVars; -import java.io.IOException; import java.nio.Buffer; import java.nio.FloatBuffer; import java.util.ArrayList; @@ -48,13 +40,22 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; + /** * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph. * There is one geometry per different material in the sub tree. * The geometries are directly attached to the node in the scene graph. * Usage is like any other node except you have to call the {@link #batch()} method once all the geometries have been attached to the sub scene graph and their material set * (see todo more automagic for further enhancements) - * All the geometries that have been batched are set to {@link CullHint#Always} to not render them. + * All the geometries that have been batched are set to not be rendered - {@link CullHint} is left intact. * The sub geometries can be transformed as usual, their transforms are used to update the mesh of the geometryBatch. * Sub geoms can be removed but it may be slower than the normal spatial removing * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries. @@ -72,7 +73,7 @@ public class BatchNode extends GeometryGroupNode { */ protected SafeArrayList batches = new SafeArrayList(Batch.class); /** - * a map storing he batches by geometry to quickly acces the batch when updating + * a map for storing the batches by geometry to quickly access the batch when updating */ protected Map batchesByGeom = new HashMap(); /** @@ -118,7 +119,6 @@ public class BatchNode extends GeometryGroupNode { public void onGeoemtryUnassociated(Geometry geom) { setNeedsFullRebatch(true); } - protected Matrix4f getTransformMatrix(Geometry g){ return g.cachedWorldMat; @@ -166,7 +166,7 @@ public class BatchNode extends GeometryGroupNode { */ public void batch() { doBatch(); - //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice + //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice for (Batch batch : batches.getArray()) { batch.geometry.setIgnoreTransform(true); batch.geometry.setUserData(UserData.JME_PHYSICSIGNORE, true); @@ -174,10 +174,10 @@ public class BatchNode extends GeometryGroupNode { } protected void doBatch() { - Map> matMap = new HashMap>(); + Map> matMap = new HashMap>(); int nbGeoms = 0; - gatherGeomerties(matMap, this, needsFullRebatch); + gatherGeometries(matMap, this, needsFullRebatch); if (needsFullRebatch) { for (Batch batch : batches.getArray()) { batch.geometry.removeFromParent(); @@ -221,7 +221,7 @@ public class BatchNode extends GeometryGroupNode { batch.geometry.setMesh(m); batch.geometry.getMesh().updateCounts(); - batch.geometry.updateModelBound(); + batch.geometry.updateModelBound(); batches.add(batch); } if (batches.size() > 0) { @@ -271,7 +271,7 @@ public class BatchNode extends GeometryGroupNode { } - private void gatherGeomerties(Map> map, Spatial n, boolean rebatch) { + private void gatherGeometries(Map> map, Spatial n, boolean rebatch) { if (n instanceof Geometry) { @@ -304,7 +304,7 @@ public class BatchNode extends GeometryGroupNode { if (child instanceof BatchNode) { continue; } - gatherGeomerties(map, child, rebatch); + gatherGeometries(map, child, rebatch); } } @@ -319,7 +319,7 @@ public class BatchNode extends GeometryGroupNode { return null; } - private boolean isBatch(Spatial s) { + public final boolean isBatch(Spatial s) { for (Batch batch : batches.getArray()) { if (batch.geometry == s) { return true; @@ -336,9 +336,6 @@ public class BatchNode extends GeometryGroupNode { */ @Override public void setMaterial(Material material) { -// for (Batch batch : batches.values()) { -// batch.geometry.setMaterial(material); -// } throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching"); } @@ -356,74 +353,7 @@ public class BatchNode extends GeometryGroupNode { Batch b = batches.iterator().next(); return b.geometry.getMaterial(); } - return null;//material; - } - -// /** -// * Sets the material to the a specific batch of this BatchNode -// * -// * -// * @param material the material to use for this geometry -// */ -// public void setMaterial(Material material,int batchIndex) { -// if (!batches.isEmpty()) { -// -// } -// -// } -// -// /** -// * Returns the material that is used for the first batch of this BatchNode -// * -// * use getMaterial(Material material,int batchIndex) to get a material from a specific batch -// * -// * @return the material that is used for the first batch of this BatchNode -// * -// * @see #setMaterial(com.jme3.material.Material) -// */ -// public Material getMaterial(int batchIndex) { -// if (!batches.isEmpty()) { -// Batch b = batches.get(batches.keySet().iterator().next()); -// return b.geometry.getMaterial(); -// } -// return null;//material; -// } - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule oc = ex.getCapsule(this); -// -// if (material != null) { -// oc.write(material.getAssetName(), "materialName", null); -// } -// oc.write(material, "material", null); - - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule ic = im.getCapsule(this); - - -// material = null; -// String matName = ic.readString("materialName", null); -// if (matName != null) { -// // Material name is set, -// // Attempt to load material via J3M -// try { -// material = im.getAssetManager().loadMaterial(matName); -// } catch (AssetNotFoundException ex) { -// // Cannot find J3M file. -// logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.", -// matName); -// } -// } -// // If material is NULL, try to load it from the geometry -// if (material == null) { -// material = (Material) ic.readSavable("material", null); -// } - + return null; } /** @@ -494,7 +424,7 @@ public class BatchNode extends GeometryGroupNode { if (mode != null && mode != listMode) { throw new UnsupportedOperationException("Cannot combine different" + " primitive types: " + mode + " != " + listMode); - } + } mode = listMode; if (mode == Mesh.Mode.Lines) { if (lineWidth != 1f && listLineWidth != lineWidth) { @@ -510,8 +440,7 @@ public class BatchNode extends GeometryGroupNode { outMesh.setMode(mode); outMesh.setLineWidth(lineWidth); if (totalVerts >= 65536) { - // make sure we create an UnsignedInt buffer so - // we can fit all of the meshes + // make sure we create an UnsignedInt buffer so we can fit all of the meshes formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt; } else { formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort; @@ -733,7 +662,6 @@ public class BatchNode extends GeometryGroupNode { } protected class Batch { - /** * update the batchesByGeom map for this batch with the given List of geometries * @param list @@ -745,7 +673,7 @@ public class BatchNode extends GeometryGroupNode { } } } - Geometry geometry; + Geometry geometry; } protected void setNeedsFullRebatch(boolean needsFullRebatch) { @@ -771,4 +699,15 @@ public class BatchNode extends GeometryGroupNode { } return clone; } + + @Override + public int collideWith(Collidable other, CollisionResults results) { + int total = 0; + for (Spatial child : children.getArray()){ + if (!isBatch(child)) { + total += child.collideWith(other, results); + } + } + return total; + } } From a920d9c6114e0e95ec1ec14d3a3f1042517a6676 Mon Sep 17 00:00:00 2001 From: MeFisto94 Date: Sun, 28 Feb 2016 00:30:03 +0100 Subject: [PATCH 22/24] Improved ShaderNodes: * Fixed a NPE when trying to select a Node. Now you can properly remove/edit nodes * "smartLog" now compares the hash across different instances (Two Material Previews led to duplicate log messages) * Using smartLog for CompilationErrors now to not spam the log with 60 FPS --- .../com/jme3/gde/materialdefinition/EditableMatDefFile.java | 2 +- .../com/jme3/gde/materialdefinition/MatDefDataObject.java | 1 + .../src/com/jme3/gde/materials/MaterialPreviewRenderer.java | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java index 73797cadc..fc1d66d70 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/EditableMatDefFile.java @@ -186,7 +186,7 @@ public class EditableMatDefFile { return ""; } catch (Exception e) { Exceptions.printStackTrace(e); - return "error generating shader " + e.getMessage(); + return "Error generating shader: " + e.getMessage(); } } diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java index 634792505..cae44f72a 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materialdefinition/MatDefDataObject.java @@ -142,6 +142,7 @@ public class MatDefDataObject extends MultiDataObject { findAssetManager(); final MatDefMetaData metaData = new MatDefMetaData(this); lookupContents.add(metaData); + lookupContents.add(new MatDefNavigatorPanel()); pf.addFileChangeListener(new FileChangeAdapter() { @Override public void fileChanged(FileEvent fe) { diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java index e400da0f4..fc92aab9c 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/MaterialPreviewRenderer.java @@ -148,7 +148,7 @@ public class MaterialPreviewRenderer implements SceneListener { }); } - private int lastErrorHash = 0; + private static int lastErrorHash = 0; private void smartLog(String expText, String message) { int hash = message.hashCode(); @@ -183,7 +183,8 @@ public class MaterialPreviewRenderer implements SceneListener { //compilation error, the shader code will be output to the console //the following code will output the error //System.err.println(e.getMessage()); - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage()); + //Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage()); + smartLog("{0}", e.getMessage()); java.awt.EventQueue.invokeLater(new Runnable() { public void run() { From 44601cf5ffbb0fc2a4d64910a7aa183d991004f9 Mon Sep 17 00:00:00 2001 From: Nehon Date: Sun, 28 Feb 2016 18:38:52 +0100 Subject: [PATCH 23/24] Added an experimental MikkTspace generator --- .../util/mikktspace/MikkTSpaceContext.java | 97 + .../jme3/util/mikktspace/MikkTSpaceImpl.java | 100 + .../MikktspaceTangentGenerator.java | 1717 +++++++++++++++++ 3 files changed, 1914 insertions(+) create mode 100644 jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java create mode 100644 jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java create mode 100644 jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java new file mode 100644 index 000000000..dc56ac240 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java @@ -0,0 +1,97 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.util.mikktspace; + +/** + * + * @author Nehon + */ +public interface MikkTSpaceContext { + + /** + * Returns the number of faces (triangles/quads) on the mesh to be + * processed. + * + * @return + */ + public int getNumFaces(); + + /** + * Returns the number of vertices on face number iFace iFace is a number in + * the range {0, 1, ..., getNumFaces()-1} + * + * @param face + * @return + */ + public int getNumVerticesOfFace(int face); + + /** + * returns the position/normal/texcoord of the referenced face of vertex + * number iVert. iVert is in the range {0,1,2} for triangles and {0,1,2,3} + * for quads. + * + * @param posOut + * @param face + * @param vert + */ + public void getPosition(float posOut[], int face, int vert); + + public void getNormal(float normOut[], int face, int vert); + + public void getTexCoord(float texOut[], int face, int vert); + + /** + * The call-backsetTSpaceBasic() is sufficient for basic normal mapping. + * This function is used to return the tangent and sign to the application. + * tangent is a unit length vector. For normal maps it is sufficient to use + * the following simplified version of the bitangent which is generated at + * pixel/vertex level. + * + * bitangent = fSign * cross(vN, tangent); + * + * Note that the results are returned unindexed. It is possible to generate + * a new index list But averaging/overwriting tangent spaces by using an + * already existing index list WILL produce INCRORRECT results. DO NOT! use + * an already existing index list. + * + * @param tangent + * @param sign + * @param face + * @param vert + */ + public void setTSpaceBasic(float tangent[], float sign, int face, int vert); + + /** + * This function is used to return tangent space results to the application. + * tangent and biTangent are unit length vectors and fMagS and fMagT are + * their true magnitudes which can be used for relief mapping effects. + * + * biTangent is the "real" bitangent and thus may not be perpendicular to + * tangent. However, both are perpendicular to the vertex normal. For normal + * maps it is sufficient to use the following simplified version of the + * bitangent which is generated at pixel/vertex level. + * + *
+     * fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
+     * bitangent = fSign * cross(vN, tangent);
+     * 
+ * + * Note that the results are returned unindexed. It is possible to generate + * a new index list. But averaging/overwriting tangent spaces by using an + * already existing index list WILL produce INCRORRECT results. DO NOT! use + * an already existing index list. + * + * @param tangent + * @param biTangent + * @param magS + * @param magT + * @param isOrientationPreserving + * @param face + * @param vert + */ + void setTSpace(float tangent[], float biTangent[], float magS, float magT, + boolean isOrientationPreserving, int face, int vert); +} diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java new file mode 100644 index 000000000..190a2e2c4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java @@ -0,0 +1,100 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.util.mikktspace; + +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; + +/** + * + * @author Nehon + */ +public class MikkTSpaceImpl implements MikkTSpaceContext { + + Mesh mesh; + + public MikkTSpaceImpl(Mesh mesh) { + this.mesh = mesh; + VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent); + if(tangentBuffer == null){ + FloatBuffer fb = BufferUtils.createFloatBuffer(mesh.getVertexCount() * 4); + mesh.setBuffer(VertexBuffer.Type.Tangent, 4, fb); + } + + //TODO ensure the Tangent buffer exists, else create one. + } + + @Override + public int getNumFaces() { + return mesh.getTriangleCount(); + } + + @Override + public int getNumVerticesOfFace(int face) { + return 3; + } + + @Override + public void getPosition(float[] posOut, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.Position); + FloatBuffer pos = (FloatBuffer) position.getData(); + pos.position(vertIndex * 3); + posOut[0] = pos.get(); + posOut[1] = pos.get(); + posOut[2] = pos.get(); + } + + @Override + public void getNormal(float[] normOut, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer normal = mesh.getBuffer(VertexBuffer.Type.Normal); + FloatBuffer norm = (FloatBuffer) normal.getData(); + norm.position(vertIndex * 3); + normOut[0] = norm.get(); + normOut[1] = norm.get(); + normOut[2] = norm.get(); + } + + @Override + public void getTexCoord(float[] texOut, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer texCoord = mesh.getBuffer(VertexBuffer.Type.TexCoord); + FloatBuffer tex = (FloatBuffer) texCoord.getData(); + tex.position(vertIndex * 2); + texOut[0] = tex.get(); + texOut[1] = tex.get(); + } + + @Override + public void setTSpaceBasic(float[] tangent, float sign, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent); + FloatBuffer tan = (FloatBuffer) tangentBuffer.getData(); + + tan.position(vertIndex * 4); + tan.put(tangent); + tan.put(sign); + + tan.rewind(); + tangentBuffer.setUpdateNeeded(); + } + + @Override + public void setTSpace(float[] tangent, float[] biTangent, float magS, float magT, boolean isOrientationPreserving, int face, int vert) { + //Do nothing + } + + private int getIndex(int face, int vert) { + IndexBuffer index = mesh.getIndexBuffer(); + int vertIndex = index.get(face * 3 + vert); + return vertIndex; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java new file mode 100644 index 000000000..af20b842b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java @@ -0,0 +1,1717 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.util.mikktspace; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This tangent generator is Highly experimental. + * This is the Java translation of The mikktspace generator made by Morten S. Mikkelsen + * C Source code can be found here + * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.c + * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.h + * + * MikkTspace looks like the new standard of tengent generation in 3D softwares. + * Xnormal, Blender, Substance painter, and many more use it. + * + * Usage is : + * MikkTSpaceTangentGenerator.generate(spatial); + * + * + * + * @author Nehon + */ +public class MikktspaceTangentGenerator { + + private final static int MARK_DEGENERATE = 1; + private final static int QUAD_ONE_DEGEN_TRI = 2; + private final static int GROUP_WITH_ANY = 4; + private final static int ORIENT_PRESERVING = 8; + private final static long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL; + static final int CELLS = 2048; + + static int makeIndex(final int face, final int vert) { + assert (vert >= 0 && vert < 4 && face >= 0); + return (face << 2) | (vert & 0x3); + } + + private static void indexToData(int[] face, int[] vert, final int indexIn) { + vert[0] = indexIn & 0x3; + face[0] = indexIn >> 2; + } + + static TSpace avgTSpace(final TSpace tS0, final TSpace tS1) { + TSpace tsRes = new TSpace(); + + // this if is important. Due to floating point precision + // averaging when s0 == s1 will cause a slight difference + // which results in tangent space splits later on + if (tS0.magS == tS1.magS && tS0.magT == tS1.magT && tS0.os.equals(tS1.os) && tS0.ot.equals(tS1.ot)) { + tsRes.magS = tS0.magS; + tsRes.magT = tS0.magT; + tsRes.os.set(tS0.os); + tsRes.ot.set(tS0.ot); + } else { + tsRes.magS = 0.5f * (tS0.magS + tS1.magS); + tsRes.magT = 0.5f * (tS0.magT + tS1.magT); + tsRes.os.set(tS0.os).addLocal(tS1.os).normalizeLocal(); + tsRes.ot.set(tS0.ot).addLocal(tS1.ot).normalizeLocal(); + } + return tsRes; + } + + public static void generate(Spatial s){ + if(s instanceof Node){ + Node n = (Node)s; + for (Spatial child : n.getChildren()) { + generate(child); + } + } else if (s instanceof Geometry){ + Geometry g = (Geometry)s; + MikkTSpaceImpl context = new MikkTSpaceImpl(g.getMesh()); + if(!genTangSpaceDefault(context)){ + Logger.getLogger(MikktspaceTangentGenerator.class.getName()).log(Level.SEVERE, "Failed to generate tangents for geometry " + g.getName()); + } + } + } + + public static boolean genTangSpaceDefault(MikkTSpaceContext mikkTSpace) { + return genTangSpace(mikkTSpace, 180.0f); + } + + public static boolean genTangSpace(MikkTSpaceContext mikkTSpace, final float angularThreshold) { + + // count nr_triangles + int[] piTriListIn; + int[] piGroupTrianglesBuffer; + TriInfo[] pTriInfos; + Group[] pGroups; + TSpace[] psTspace; + int iNrTrianglesIn = 0; + int iNrTSPaces, iTotTris, iDegenTriangles, iNrMaxGroups; + int iNrActiveGroups, index; + final int iNrFaces = mikkTSpace.getNumFaces(); + //boolean bRes = false; + final float fThresCos = (float) FastMath.cos((angularThreshold * (float) FastMath.PI) / 180.0f); + + // count triangles on supported faces + for (int f = 0; f < iNrFaces; f++) { + final int verts = mikkTSpace.getNumVerticesOfFace(f); + if (verts == 3) { + ++iNrTrianglesIn; + } else if (verts == 4) { + iNrTrianglesIn += 2; + } + } + if (iNrTrianglesIn <= 0) { + return false; + } + + piTriListIn = new int[3 * iNrTrianglesIn]; + pTriInfos = new TriInfo[iNrTrianglesIn]; + + // make an initial triangle -. face index list + iNrTSPaces = generateInitialVerticesIndexList(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn); + + // make a welded index list of identical positions and attributes (pos, norm, texc) + generateSharedVerticesIndexList(piTriListIn, mikkTSpace, iNrTrianglesIn); + + // Mark all degenerate triangles + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + for (int t = 0; t < iTotTris; t++) { + final int i0 = piTriListIn[t * 3 + 0]; + final int i1 = piTriListIn[t * 3 + 1]; + final int i2 = piTriListIn[t * 3 + 2]; + final Vector3f p0 = getPosition(mikkTSpace, i0); + final Vector3f p1 = getPosition(mikkTSpace, i1); + final Vector3f p2 = getPosition(mikkTSpace, i2); + if (p0.equals(p1) || p0.equals(p2) || p1.equals(p2)) {// degenerate + pTriInfos[t].flag |= MARK_DEGENERATE; + ++iDegenTriangles; + } + } + iNrTrianglesIn = iTotTris - iDegenTriangles; + + // mark all triangle pairs that belong to a quad with only one + // good triangle. These need special treatment in DegenEpilogue(). + // Additionally, move all good triangles to the start of + // pTriInfos[] and piTriListIn[] without changing order and + // put the degenerate triangles last. + degenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris); + + // evaluate triangle level attributes and neighbor list + initTriInfo(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn); + + // based on the 4 rules, identify groups based on connectivity + iNrMaxGroups = iNrTrianglesIn * 3; + pGroups = new Group[iNrMaxGroups]; + piGroupTrianglesBuffer = new int[iNrTrianglesIn * 3]; + + iNrActiveGroups + = build4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn); + + psTspace = new TSpace[iNrTSPaces]; + + for (int t = 0; t < iNrTSPaces; t++) { + TSpace tSpace = new TSpace(); + tSpace.os.set(1.0f, 0.0f, 0.0f); + tSpace.magS = 1.0f; + tSpace.ot.set(0.0f, 1.0f, 0.0f); + tSpace.magT = 1.0f; + psTspace[t] = tSpace; + } + + // make tspaces, each group is split up into subgroups if necessary + // based on fAngularThreshold. Finally a tangent space is made for + // every resulting subgroup + generateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, mikkTSpace); + + // degenerate quads with one good triangle will be fixed by copying a space from + // the good triangle to the coinciding vertex. + // all other degenerate triangles will just copy a space from any good triangle + // with the same welded index in piTriListIn[]. + DegenEpilogue(psTspace, pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn, iTotTris); + + index = 0; + for (int f = 0; f < iNrFaces; f++) { + final int verts = mikkTSpace.getNumVerticesOfFace(f); + if (verts != 3 && verts != 4) { + continue; + } + + // I've decided to let degenerate triangles and group-with-anythings + // vary between left/right hand coordinate systems at the vertices. + // All healthy triangles on the other hand are built to always be either or. + + /*// force the coordinate system orientation to be uniform for every face. + // (this is already the case for good triangles but not for + // degenerate ones and those with bGroupWithAnything==true) + bool bOrient = psTspace[index].bOrient; + if (psTspace[index].iCounter == 0) // tspace was not derived from a group + { + // look for a space created in GenerateTSpaces() by iCounter>0 + bool bNotFound = true; + int i=1; + while (i 0) bNotFound=false; + else ++i; + } + if (!bNotFound) bOrient = psTspace[index+i].bOrient; + }*/ + // set data + for (int i = 0; i < verts; i++) { + final TSpace pTSpace = psTspace[index]; + float tang[] = {pTSpace.os.x, pTSpace.os.y, pTSpace.os.z}; + float bitang[] = {pTSpace.ot.x, pTSpace.ot.y, pTSpace.ot.z}; + mikkTSpace.setTSpace(tang, bitang, pTSpace.magS, pTSpace.magT, pTSpace.orient, f, i); + mikkTSpace.setTSpaceBasic(tang, pTSpace.orient == true ? 1.0f : (-1.0f), f, i); + ++index; + } + } + + return true; + } + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // it is IMPORTANT that this function is called to evaluate the hash since + // inlining could potentially reorder instructions and generate different + // results for the same effective input value fVal. + //TODO nehon: Wuuttt? something is fishy about this. How the fuck inlining can reorder instructions? Is that a C thing? + static int findGridCell(final float min, final float max, final float val) { + final float fIndex = CELLS * ((val - min) / (max - min)); + final int iIndex = (int) fIndex; + return iIndex < CELLS ? (iIndex >= 0 ? iIndex : 0) : (CELLS - 1); + } + + static void generateSharedVerticesIndexList(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + + // Generate bounding box + TmpVert[] pTmpVert; + Vector3f vMin = getPosition(mikkTSpace, 0); + Vector3f vMax = vMin.clone(); + Vector3f vDim; + float fMin, fMax; + for (int i = 1; i < (iNrTrianglesIn * 3); i++) { + final int index = piTriList_in_and_out[i]; + + final Vector3f vP = getPosition(mikkTSpace, index); + if (vMin.x > vP.x) { + vMin.x = vP.x; + } else if (vMax.x < vP.x) { + vMax.x = vP.x; + } + if (vMin.y > vP.y) { + vMin.y = vP.y; + } else if (vMax.y < vP.y) { + vMax.y = vP.y; + } + if (vMin.z > vP.z) { + vMin.z = vP.z; + } else if (vMax.z < vP.z) { + vMax.z = vP.z; + } + } + + vDim = vMax.subtract(vMin); + int iChannel = 0; + fMin = vMin.x; + fMax = vMax.x; + if (vDim.y > vDim.x && vDim.y > vDim.z) { + iChannel = 1; + fMin = vMin.y; + fMax = vMax.y; + } else if (vDim.z > vDim.x) { + iChannel = 2; + fMin = vMin.z; + fMax = vMax.z; + } + + //TODO Nehon: this is really fishy... seems like a hashtable implementation with nasty array manipulation... + int[] piHashTable = new int[iNrTrianglesIn * 3]; + int[] piHashCount = new int[CELLS]; + int[] piHashOffsets = new int[CELLS]; + int[] piHashCount2 = new int[CELLS]; + + // count amount of elements in each cell unit + for (int i = 0; i < (iNrTrianglesIn * 3); i++) { + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z); + final int iCell = findGridCell(fMin, fMax, fVal); + ++piHashCount[iCell]; + } + + // evaluate start index of each cell. + piHashOffsets[0] = 0; + for (int k = 1; k < CELLS; k++) { + piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1]; + } + + // insert vertices + for (int i = 0; i < (iNrTrianglesIn * 3); i++) { + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z); + final int iCell = findGridCell(fMin, fMax, fVal); + + assert (piHashCount2[iCell] < piHashCount[iCell]); + + // int * pTable = &piHashTable[piHashOffsets[iCell]]; + // pTable[piHashCount2[iCell]] = i; // vertex i has been inserted. + piHashTable[piHashOffsets[iCell] + piHashCount2[iCell]] = i;// vertex i has been inserted. + ++piHashCount2[iCell]; + } + for (int k = 0; k < CELLS; k++) { + assert (piHashCount2[k] == piHashCount[k]); // verify the count + } + + // find maximum amount of entries in any hash entry + int iMaxCount = piHashCount[0]; + for (int k = 1; k < CELLS; k++) { + if (iMaxCount < piHashCount[k]) { + iMaxCount = piHashCount[k]; + } + } + + pTmpVert = new TmpVert[iMaxCount]; + + // complete the merge + for (int k = 0; k < CELLS; k++) { + // extract table of cell k and amount of entries in it + // int * pTable = &piHashTable[piHashOffsets[k]]; + final int iEntries = piHashCount[k]; + if (iEntries < 2) { + continue; + } + + if (pTmpVert != null) { + for (int e = 0; e < iEntries; e++) { + int j = piHashTable[piHashOffsets[k] + e]; + final Vector3f vP = getPosition(mikkTSpace, piTriList_in_and_out[j]); + pTmpVert[e] = new TmpVert(); + pTmpVert[e].vert[0] = vP.x; + pTmpVert[e].vert[1] = vP.y; + pTmpVert[e].vert[2] = vP.z; + pTmpVert[e].index = j; + } + MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, 0, iEntries - 1); + } else { + //TODO Nehon: pTempVert is very unlikely to be null...maybe remove this... + int[] pTable = Arrays.copyOfRange(piHashTable, piHashOffsets[k], piHashOffsets[k] + iEntries); + MergeVertsSlow(piTriList_in_and_out, mikkTSpace, pTable, iEntries); + } + } + } + + static void MergeVertsFast(int piTriList_in_and_out[], TmpVert pTmpVert[], final MikkTSpaceContext mikkTSpace, final int iL_in, final int iR_in) { + // make bbox + float[] fvMin = new float[3]; + float[] fvMax = new float[3]; + for (int c = 0; c < 3; c++) { + fvMin[c] = pTmpVert[iL_in].vert[c]; + fvMax[c] = fvMin[c]; + } + for (int l = (iL_in + 1); l <= iR_in; l++) { + for (int c = 0; c < 3; c++) { + if (fvMin[c] > pTmpVert[l].vert[c]) { + fvMin[c] = pTmpVert[l].vert[c]; + } else if (fvMax[c] < pTmpVert[l].vert[c]) { + fvMax[c] = pTmpVert[l].vert[c]; + } + } + } + + float dx = fvMax[0] - fvMin[0]; + float dy = fvMax[1] - fvMin[1]; + float dz = fvMax[2] - fvMin[2]; + + int channel = 0; + if (dy > dx && dy > dz) { + channel = 1; + } else if (dz > dx) { + channel = 2; + } + + float fSep = 0.5f * (fvMax[channel] + fvMin[channel]); + + // terminate recursion when the separation/average value + // is no longer strictly between fMin and fMax values. + if (fSep >= fvMax[channel] || fSep <= fvMin[channel]) { + // complete the weld + for (int l = iL_in; l <= iR_in; l++) { + int i = pTmpVert[l].index; + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final Vector3f vN = getNormal(mikkTSpace, index); + final Vector3f vT = getTexCoord(mikkTSpace, index); + + boolean bNotFound = true; + int l2 = iL_in, i2rec = -1; + while (l2 < l && bNotFound) { + final int i2 = pTmpVert[l2].index; + final int index2 = piTriList_in_and_out[i2]; + final Vector3f vP2 = getPosition(mikkTSpace, index2); + final Vector3f vN2 = getNormal(mikkTSpace, index2); + final Vector3f vT2 = getTexCoord(mikkTSpace, index2); + i2rec = i2; + + //if (vP==vP2 && vN==vN2 && vT==vT2) + if (vP.x == vP2.x && vP.y == vP2.y && vP.z == vP2.z + && vN.x == vN2.x && vN.y == vN2.y && vN.z == vN2.z + && vT.x == vT2.x && vT.y == vT2.y && vT.z == vT2.z) { + bNotFound = false; + } else { + ++l2; + } + } + + // merge if previously found + if (!bNotFound) { + piTriList_in_and_out[i] = piTriList_in_and_out[i2rec]; + } + } + } else { + int iL = iL_in, iR = iR_in; + assert ((iR_in - iL_in) > 0); // at least 2 entries + + // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] + while (iL < iR) { + boolean bReadyLeftSwap = false, bReadyRightSwap = false; + while ((!bReadyLeftSwap) && iL < iR) { + assert (iL >= iL_in && iL <= iR_in); + bReadyLeftSwap = !(pTmpVert[iL].vert[channel] < fSep); + if (!bReadyLeftSwap) { + ++iL; + } + } + while ((!bReadyRightSwap) && iL < iR) { + assert (iR >= iL_in && iR <= iR_in); + bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep; + if (!bReadyRightSwap) { + --iR; + } + } + assert ((iL < iR) || !(bReadyLeftSwap && bReadyRightSwap)); + + if (bReadyLeftSwap && bReadyRightSwap) { + final TmpVert sTmp = pTmpVert[iL]; + assert (iL < iR); + pTmpVert[iL] = pTmpVert[iR]; + pTmpVert[iR] = sTmp; + ++iL; + --iR; + } + } + + assert (iL == (iR + 1) || (iL == iR)); + if (iL == iR) { + final boolean bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep; + if (bReadyRightSwap) { + ++iL; + } else { + --iR; + } + } + + // only need to weld when there is more than 1 instance of the (x,y,z) + if (iL_in < iR) { + MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL_in, iR); // weld all left of fSep + } + if (iL < iR_in) { + MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL, iR_in); // weld all right of (or equal to) fSep + } + } + } + + //TODO Nehon: Used only if an array failed to be allocated... Can't happen in Java... + static void MergeVertsSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int pTable[], final int iEntries) { + // this can be optimized further using a tree structure or more hashing. + for (int e = 0; e < iEntries; e++) { + int i = pTable[e]; + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final Vector3f vN = getNormal(mikkTSpace, index); + final Vector3f vT = getTexCoord(mikkTSpace, index); + + boolean bNotFound = true; + int e2 = 0, i2rec = -1; + while (e2 < e && bNotFound) { + final int i2 = pTable[e2]; + final int index2 = piTriList_in_and_out[i2]; + final Vector3f vP2 = getPosition(mikkTSpace, index2); + final Vector3f vN2 = getNormal(mikkTSpace, index2); + final Vector3f vT2 = getTexCoord(mikkTSpace, index2); + i2rec = i2; + + if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) { + bNotFound = false; + } else { + ++e2; + } + } + + // merge if previously found + if (!bNotFound) { + piTriList_in_and_out[i] = piTriList_in_and_out[i2rec]; + } + } + } + + //TODO Nehon : Not used...seemsit's used in the original version if the structure to store the data in the regular method failed... + static void generateSharedVerticesIndexListSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + int iNumUniqueVerts = 0; + for (int t = 0; t < iNrTrianglesIn; t++) { + for (int i = 0; i < 3; i++) { + final int offs = t * 3 + i; + final int index = piTriList_in_and_out[offs]; + + final Vector3f vP = getPosition(mikkTSpace, index); + final Vector3f vN = getNormal(mikkTSpace, index); + final Vector3f vT = getTexCoord(mikkTSpace, index); + + boolean bFound = false; + int t2 = 0, index2rec = -1; + while (!bFound && t2 <= t) { + int j = 0; + while (!bFound && j < 3) { + final int index2 = piTriList_in_and_out[t2 * 3 + j]; + final Vector3f vP2 = getPosition(mikkTSpace, index2); + final Vector3f vN2 = getNormal(mikkTSpace, index2); + final Vector3f vT2 = getTexCoord(mikkTSpace, index2); + + if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) { + bFound = true; + } else { + ++j; + } + } + if (!bFound) { + ++t2; + } + } + + assert (bFound); + // if we found our own + if (index2rec == index) { + ++iNumUniqueVerts; + } + + piTriList_in_and_out[offs] = index2rec; + } + } + } + + static int generateInitialVerticesIndexList(TriInfo pTriInfos[], int piTriList_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + int iTSpacesOffs = 0; + int iDstTriIndex = 0; + for (int f = 0; f < mikkTSpace.getNumFaces(); f++) { + final int verts = mikkTSpace.getNumVerticesOfFace(f); + if (verts != 3 && verts != 4) { + continue; + } + + //TODO nehon : clean this, have a local TrinInfo and assign it to pTriInfo[iDstTriIndex] at the end... and change those variables names... + pTriInfos[iDstTriIndex] = new TriInfo(); + pTriInfos[iDstTriIndex].orgFaceNumber = f; + pTriInfos[iDstTriIndex].tSpacesOffs = iTSpacesOffs; + + if (verts == 3) { + //TODO same here it should be easy once the local TriInfo is created. + byte[] pVerts = pTriInfos[iDstTriIndex].vertNum; + pVerts[0] = 0; + pVerts[1] = 1; + pVerts[2] = 2; + piTriList_out[iDstTriIndex * 3 + 0] = makeIndex(f, 0); + piTriList_out[iDstTriIndex * 3 + 1] = makeIndex(f, 1); + piTriList_out[iDstTriIndex * 3 + 2] = makeIndex(f, 2); + ++iDstTriIndex; // next + } else { + //Note, Nehon: we should never get there with JME, because we don't support quads... + //but I'm going to let it there incase someone needs it... Just know this code is not tested. + {//TODO remove those useless brackets... + pTriInfos[iDstTriIndex + 1].orgFaceNumber = f; + pTriInfos[iDstTriIndex + 1].tSpacesOffs = iTSpacesOffs; + } + + { + // need an order independent way to evaluate + // tspace on quads. This is done by splitting + // along the shortest diagonal. + final int i0 = makeIndex(f, 0); + final int i1 = makeIndex(f, 1); + final int i2 = makeIndex(f, 2); + final int i3 = makeIndex(f, 3); + final Vector3f T0 = getTexCoord(mikkTSpace, i0); + final Vector3f T1 = getTexCoord(mikkTSpace, i1); + final Vector3f T2 = getTexCoord(mikkTSpace, i2); + final Vector3f T3 = getTexCoord(mikkTSpace, i3); + final float distSQ_02 = T2.subtract(T0).lengthSquared(); + final float distSQ_13 = T3.subtract(T1).lengthSquared(); + boolean bQuadDiagIs_02; + if (distSQ_02 < distSQ_13) { + bQuadDiagIs_02 = true; + } else if (distSQ_13 < distSQ_02) { + bQuadDiagIs_02 = false; + } else { + final Vector3f P0 = getPosition(mikkTSpace, i0); + final Vector3f P1 = getPosition(mikkTSpace, i1); + final Vector3f P2 = getPosition(mikkTSpace, i2); + final Vector3f P3 = getPosition(mikkTSpace, i3); + final float distSQ_022 = P2.subtract(P0).lengthSquared(); + final float distSQ_132 = P3.subtract(P1).lengthSquared(); + + bQuadDiagIs_02 = distSQ_132 >= distSQ_022; + } + + if (bQuadDiagIs_02) { + { + byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum; + pVerts_A[0] = 0; + pVerts_A[1] = 1; + pVerts_A[2] = 2; + } + piTriList_out[iDstTriIndex * 3 + 0] = i0; + piTriList_out[iDstTriIndex * 3 + 1] = i1; + piTriList_out[iDstTriIndex * 3 + 2] = i2; + ++iDstTriIndex; // next + { + byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum; + pVerts_B[0] = 0; + pVerts_B[1] = 2; + pVerts_B[2] = 3; + } + piTriList_out[iDstTriIndex * 3 + 0] = i0; + piTriList_out[iDstTriIndex * 3 + 1] = i2; + piTriList_out[iDstTriIndex * 3 + 2] = i3; + ++iDstTriIndex; // next + } else { + { + byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum; + pVerts_A[0] = 0; + pVerts_A[1] = 1; + pVerts_A[2] = 3; + } + piTriList_out[iDstTriIndex * 3 + 0] = i0; + piTriList_out[iDstTriIndex * 3 + 1] = i1; + piTriList_out[iDstTriIndex * 3 + 2] = i3; + ++iDstTriIndex; // next + { + byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum; + pVerts_B[0] = 1; + pVerts_B[1] = 2; + pVerts_B[2] = 3; + } + piTriList_out[iDstTriIndex * 3 + 0] = i1; + piTriList_out[iDstTriIndex * 3 + 1] = i2; + piTriList_out[iDstTriIndex * 3 + 2] = i3; + ++iDstTriIndex; // next + } + } + } + + iTSpacesOffs += verts; + assert (iDstTriIndex <= iNrTrianglesIn); + } + + for (int t = 0; t < iNrTrianglesIn; t++) { + pTriInfos[t].flag = 0; + } + + // return total amount of tspaces + return iTSpacesOffs; + } + + static Vector3f getPosition(final MikkTSpaceContext mikkTSpace, final int index) { + //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData + int[] iF = new int[1]; + int[] iI = new int[1]; + float[] pos = new float[3]; + indexToData(iF, iI, index); + mikkTSpace.getPosition(pos, iF[0], iI[0]); + return new Vector3f(pos[0], pos[1], pos[2]); + } + + static Vector3f getNormal(final MikkTSpaceContext mikkTSpace, final int index) { + //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData + int[] iF = new int[1]; + int[] iI = new int[1]; + float[] norm = new float[3]; + indexToData(iF, iI, index); + mikkTSpace.getNormal(norm, iF[0], iI[0]); + return new Vector3f(norm[0], norm[1], norm[2]); + } + + static Vector3f getTexCoord(final MikkTSpaceContext mikkTSpace, final int index) { + //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData + int[] iF = new int[1]; + int[] iI = new int[1]; + float[] texc = new float[2]; + indexToData(iF, iI, index); + mikkTSpace.getTexCoord(texc, iF[0], iI[0]); + return new Vector3f(texc[0], texc[1], 1.0f); + } + + // returns the texture area times 2 + static float calcTexArea(final MikkTSpaceContext mikkTSpace, final int indices[]) { + final Vector3f t1 = getTexCoord(mikkTSpace, indices[0]); + final Vector3f t2 = getTexCoord(mikkTSpace, indices[1]); + final Vector3f t3 = getTexCoord(mikkTSpace, indices[2]); + + final float t21x = t2.x - t1.x; + final float t21y = t2.y - t1.y; + final float t31x = t3.x - t1.x; + final float t31y = t3.y - t1.y; + + final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x; + + return fSignedAreaSTx2 < 0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; + } + + private static boolean isNotZero(float v) { + return Math.abs(v) > 0; + } + + static void initTriInfo(TriInfo pTriInfos[], final int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + + // pTriInfos[f].flag is cleared in GenerateInitialVerticesIndexList() which is called before this function. + // generate neighbor info list + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + pTriInfos[f].faceNeighbors[i] = -1; + pTriInfos[f].assignedGroup[i] = null; + + pTriInfos[f].os.x = 0.0f; + pTriInfos[f].os.y = 0.0f; + pTriInfos[f].os.z = 0.0f; + pTriInfos[f].ot.x = 0.0f; + pTriInfos[f].ot.y = 0.0f; + pTriInfos[f].ot.z = 0.0f; + pTriInfos[f].magS = 0; + pTriInfos[f].magT = 0; + + // assumed bad + pTriInfos[f].flag |= GROUP_WITH_ANY; + } + } + + // evaluate first order derivatives + for (int f = 0; f < iNrTrianglesIn; f++) { + // initial values + final Vector3f v1 = getPosition(mikkTSpace, piTriListIn[f * 3 + 0]); + final Vector3f v2 = getPosition(mikkTSpace, piTriListIn[f * 3 + 1]); + final Vector3f v3 = getPosition(mikkTSpace, piTriListIn[f * 3 + 2]); + final Vector3f t1 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 0]); + final Vector3f t2 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 1]); + final Vector3f t3 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 2]); + + final float t21x = t2.x - t1.x; + final float t21y = t2.y - t1.y; + final float t31x = t3.x - t1.x; + final float t31y = t3.y - t1.y; + final Vector3f d1 = v2.subtract(v1); + final Vector3f d2 = v3.subtract(v1); + + final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x; + //assert(fSignedAreaSTx2!=0); + Vector3f vOs = d1.mult(t31y).subtract(d2.mult(t21y)); // eq 18 + Vector3f vOt = d1.mult(-t31x).add(d2.mult(t21x)); // eq 19 + + pTriInfos[f].flag |= (fSignedAreaSTx2 > 0 ? ORIENT_PRESERVING : 0); + + if (isNotZero(fSignedAreaSTx2)) { + final float fAbsArea = Math.abs(fSignedAreaSTx2); + final float fLenOs = vOs.length(); + final float fLenOt = vOt.length(); + final float fS = (pTriInfos[f].flag & ORIENT_PRESERVING) == 0 ? (-1.0f) : 1.0f; + if (isNotZero(fLenOs)) { + pTriInfos[f].os = vOs.multLocal(fS / fLenOs); + } + if (isNotZero(fLenOt)) { + pTriInfos[f].ot = vOt.multLocal(fS / fLenOt); + } + + // evaluate magnitudes prior to normalization of vOs and vOt + pTriInfos[f].magS = fLenOs / fAbsArea; + pTriInfos[f].magT = fLenOt / fAbsArea; + + // if this is a good triangle + if (isNotZero(pTriInfos[f].magS) && isNotZero(pTriInfos[f].magT)) { + pTriInfos[f].flag &= (~GROUP_WITH_ANY); + } + } + } + + // force otherwise healthy quads to a fixed orientation + int t = 0; + while (t < (iNrTrianglesIn - 1)) { + final int iFO_a = pTriInfos[t].orgFaceNumber; + final int iFO_b = pTriInfos[t + 1].orgFaceNumber; + if (iFO_a == iFO_b) { + // this is a quad + final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0; + final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0; + + // bad triangles should already have been removed by + // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false + if ((bIsDeg_a || bIsDeg_b) == false) { + final boolean bOrientA = (pTriInfos[t].flag & ORIENT_PRESERVING) != 0; + final boolean bOrientB = (pTriInfos[t + 1].flag & ORIENT_PRESERVING) != 0; + // if this happens the quad has extremely bad mapping!! + if (bOrientA != bOrientB) { + //printf("found quad with bad mapping\n"); + boolean bChooseOrientFirstTri = false; + if ((pTriInfos[t + 1].flag & GROUP_WITH_ANY) != 0) { + bChooseOrientFirstTri = true; + } else if (calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, t * 3 + 0, t * 3 + 3)) >= calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, (t + 1) * 3 + 0, (t + 1) * 3 + 3))) { + bChooseOrientFirstTri = true; + } + + // force match + { + final int t0 = bChooseOrientFirstTri ? t : (t + 1); + final int t1 = bChooseOrientFirstTri ? (t + 1) : t; + pTriInfos[t1].flag &= (~ORIENT_PRESERVING); // clear first + pTriInfos[t1].flag |= (pTriInfos[t0].flag & ORIENT_PRESERVING); // copy bit + } + } + } + t += 2; + } else { + ++t; + } + } + + // match up edge pairs + { + //Edge * pEdges = (Edge *) malloc(sizeof(Edge)*iNrTrianglesIn*3); + Edge[] pEdges = new Edge[iNrTrianglesIn * 3]; + + //TODO nehon weird... original algorithm check if pEdges is null but it's just been allocated... weirder, it does soemthing different if the edges are null... + // if (pEdges==null) + // BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); + // else + // { + buildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); + + // } + } + } + + static int build4RuleGroups(TriInfo pTriInfos[], Group pGroups[], int piGroupTrianglesBuffer[], final int piTriListIn[], final int iNrTrianglesIn) { + final int iNrMaxGroups = iNrTrianglesIn * 3; + int iNrActiveGroups = 0; + int iOffset = 0; + // (void)iNrMaxGroups; /* quiet warnings in non debug mode */ + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + // if not assigned to a group + if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0 && pTriInfos[f].assignedGroup[i] == null) { + boolean bOrPre; + final int vert_index = piTriListIn[f * 3 + i]; + assert (iNrActiveGroups < iNrMaxGroups); + pTriInfos[f].assignedGroup[i] = new Group(); + pGroups[iNrActiveGroups] = pTriInfos[f].assignedGroup[i]; + pTriInfos[f].assignedGroup[i].vertexRepresentitive = vert_index; + pTriInfos[f].assignedGroup[i].orientPreservering = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0; + pTriInfos[f].assignedGroup[i].nrFaces = 0; + + ++iNrActiveGroups; + + addTriToGroup(pTriInfos[f].assignedGroup[i], f); + bOrPre = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0; + int neigh_indexL = pTriInfos[f].faceNeighbors[i]; + int neigh_indexR = pTriInfos[f].faceNeighbors[i > 0 ? (i - 1) : 2]; + if (neigh_indexL >= 0) { + // neighbor + final boolean bAnswer + = assignRecur(piTriListIn, pTriInfos, neigh_indexL, + pTriInfos[f].assignedGroup[i]); + + final boolean bOrPre2 = (pTriInfos[neigh_indexL].flag & ORIENT_PRESERVING) != 0; + final boolean bDiff = bOrPre != bOrPre2; + assert (bAnswer || bDiff); + //(void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + if (neigh_indexR >= 0) { + // neighbor + final boolean bAnswer + = assignRecur(piTriListIn, pTriInfos, neigh_indexR, + pTriInfos[f].assignedGroup[i]); + + final boolean bOrPre2 = (pTriInfos[neigh_indexR].flag & ORIENT_PRESERVING) != 0; + final boolean bDiff = bOrPre != bOrPre2; + assert (bAnswer || bDiff); + //(void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + + int[] faceIndices = new int[pTriInfos[f].assignedGroup[i].nrFaces]; + //pTriInfos[f].assignedGroup[i].faceIndices.toArray(faceIndices); + for (int j = 0; j < faceIndices.length; j++) { + faceIndices[j] = pTriInfos[f].assignedGroup[i].faceIndices.get(j); + } + + //Nehon: copy back the faceIndices data into the groupTriangleBuffer. + System.arraycopy( faceIndices, 0, piGroupTrianglesBuffer, iOffset, pTriInfos[f].assignedGroup[i].nrFaces); + // update offset + iOffset += pTriInfos[f].assignedGroup[i].nrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + assert (iOffset <= iNrMaxGroups); + } + } + } + + return iNrActiveGroups; + } + + static void addTriToGroup(Group group, final int triIndex) { + //group.faceIndices[group.nrFaces] = triIndex; + group.faceIndices.add(triIndex); + ++group.nrFaces; + } + + static boolean assignRecur(final int piTriListIn[], TriInfo psTriInfos[], final int iMyTriIndex, Group pGroup) { + TriInfo pMyTriInfo = psTriInfos[iMyTriIndex]; + + // track down vertex + final int iVertRep = pGroup.vertexRepresentitive; + int index = 3 * iMyTriIndex; + int i = -1; + if (piTriListIn[index] == iVertRep) { + i = 0; + } else if (piTriListIn[index + 1] == iVertRep) { + i = 1; + } else if (piTriListIn[index + 2] == iVertRep) { + i = 2; + } + assert (i >= 0 && i < 3); + + // early out + if (pMyTriInfo.assignedGroup[i] == pGroup) { + return true; + } else if (pMyTriInfo.assignedGroup[i] != null) { + return false; + } + if ((pMyTriInfo.flag & GROUP_WITH_ANY) != 0) { + // first to group with a group-with-anything triangle + // determines it's orientation. + // This is the only existing order dependency in the code!! + if (pMyTriInfo.assignedGroup[0] == null + && pMyTriInfo.assignedGroup[1] == null + && pMyTriInfo.assignedGroup[2] == null) { + pMyTriInfo.flag &= (~ORIENT_PRESERVING); + pMyTriInfo.flag |= (pGroup.orientPreservering ? ORIENT_PRESERVING : 0); + } + } + { + final boolean bOrient = (pMyTriInfo.flag & ORIENT_PRESERVING) != 0; + if (bOrient != pGroup.orientPreservering) { + return false; + } + } + + addTriToGroup(pGroup, iMyTriIndex); + pMyTriInfo.assignedGroup[i] = pGroup; + + { + final int neigh_indexL = pMyTriInfo.faceNeighbors[i]; + final int neigh_indexR = pMyTriInfo.faceNeighbors[i > 0 ? (i - 1) : 2]; + if (neigh_indexL >= 0) { + assignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + } + if (neigh_indexR >= 0) { + assignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + } + + return true; + } + + static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], final Group pGroups[], + final int iNrActiveGroups, final int piTriListIn[], final float fThresCos, + final MikkTSpaceContext mikkTSpace) { + TSpace[] pSubGroupTspace; + SubGroup[] pUniSubGroups; + int[] pTmpMembers; + int iMaxNrFaces = 0, iUniqueTspaces = 0, g = 0, i = 0; + for (g = 0; g < iNrActiveGroups; g++) { + if (iMaxNrFaces < pGroups[g].nrFaces) { + iMaxNrFaces = pGroups[g].nrFaces; + } + } + + if (iMaxNrFaces == 0) { + return true; + } + + // make initial allocations + pSubGroupTspace = new TSpace[iMaxNrFaces]; + pUniSubGroups = new SubGroup[iMaxNrFaces]; + pTmpMembers = new int[iMaxNrFaces]; + + + iUniqueTspaces = 0; + for (g = 0; g < iNrActiveGroups; g++) { + final Group pGroup = pGroups[g]; + int iUniqueSubGroups = 0, s = 0; + + for (i = 0; i < pGroup.nrFaces; i++) // triangles + { + final int f = pGroup.faceIndices.get(i); // triangle number + int index = -1, iVertIndex = -1, iOF_1 = -1, iMembers = 0, j = 0, l = 0; + SubGroup tmp_group = new SubGroup(); + boolean bFound; + Vector3f n, vOs, vOt; + if (pTriInfos[f].assignedGroup[0] == pGroup) { + index = 0; + } else if (pTriInfos[f].assignedGroup[1] == pGroup) { + index = 1; + } else if (pTriInfos[f].assignedGroup[2] == pGroup) { + index = 2; + } + assert (index >= 0 && index < 3); + + iVertIndex = piTriListIn[f * 3 + index]; + assert (iVertIndex == pGroup.vertexRepresentitive); + + // is normalized already + n = getNormal(mikkTSpace, iVertIndex); + + // project + vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os))); + vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot))); + vOs.normalizeLocal(); + vOt.normalizeLocal(); + + // original face number + iOF_1 = pTriInfos[f].orgFaceNumber; + + iMembers = 0; + for (j = 0; j < pGroup.nrFaces; j++) { + final int t = pGroup.faceIndices.get(j); // triangle number + final int iOF_2 = pTriInfos[t].orgFaceNumber; + + // project + Vector3f vOs2 = pTriInfos[t].os.subtract(n.mult(n.dot(pTriInfos[t].os))); + Vector3f vOt2 = pTriInfos[t].ot.subtract(n.mult(n.dot(pTriInfos[t].ot))); + vOs2.normalizeLocal(); + vOt2.normalizeLocal(); + + { + final boolean bAny = ((pTriInfos[f].flag | pTriInfos[t].flag) & GROUP_WITH_ANY) != 0; + // make sure triangles which belong to the same quad are joined. + final boolean bSameOrgFace = iOF_1 == iOF_2; + + final float fCosS = vOs.dot(vOs2); + final float fCosT = vOt.dot(vOt2); + + assert (f != t || bSameOrgFace); // sanity check + if (bAny || bSameOrgFace || (fCosS > fThresCos && fCosT > fThresCos)) { + pTmpMembers[iMembers++] = t; + } + } + } + + // sort pTmpMembers + tmp_group.nrFaces = iMembers; + tmp_group.triMembers = pTmpMembers; + if (iMembers > 1) { + quickSort(pTmpMembers, 0, iMembers - 1, INTERNAL_RND_SORT_SEED); + } + + // look for an existing match + bFound = false; + l = 0; + while (l < iUniqueSubGroups && !bFound) { + bFound = compareSubGroups(tmp_group, pUniSubGroups[l]); + if (!bFound) { + ++l; + } + } + + // assign tangent space index + assert (bFound || l == iUniqueSubGroups); + //piTempTangIndices[f*3+index] = iUniqueTspaces+l; + + // if no match was found we allocate a new subgroup + if (!bFound) { + // insert new subgroup + int[] pIndices = new int[iMembers]; + pUniSubGroups[iUniqueSubGroups] = new SubGroup(); + pUniSubGroups[iUniqueSubGroups].nrFaces = iMembers; + pUniSubGroups[iUniqueSubGroups].triMembers = pIndices; + System.arraycopy(tmp_group.triMembers, 0, pIndices, 0, iMembers); + //memcpy(pIndices, tmp_group.pTriMembers, iMembers*sizeof(int)); + pSubGroupTspace[iUniqueSubGroups] + = evalTspace(tmp_group.triMembers, iMembers, piTriListIn, pTriInfos, mikkTSpace, pGroup.vertexRepresentitive); + ++iUniqueSubGroups; + } + + // output tspace + { + final int iOffs = pTriInfos[f].tSpacesOffs; + final int iVert = pTriInfos[f].vertNum[index]; + TSpace pTS_out = psTspace[iOffs + iVert]; + assert (pTS_out.counter < 2); + assert (((pTriInfos[f].flag & ORIENT_PRESERVING) != 0) == pGroup.orientPreservering); + if (pTS_out.counter == 1) { + pTS_out.set(avgTSpace(pTS_out, pSubGroupTspace[l])); + pTS_out.counter = 2; // update counter + pTS_out.orient = pGroup.orientPreservering; + } else { + assert (pTS_out.counter == 0); + pTS_out.set(pSubGroupTspace[l]); + pTS_out.counter = 1; // update counter + pTS_out.orient = pGroup.orientPreservering; + } + } + } + + iUniqueTspaces += iUniqueSubGroups; + } + + return true; + } + + static TSpace evalTspace(int face_indices[], final int iFaces, final int piTriListIn[], final TriInfo pTriInfos[], + final MikkTSpaceContext mikkTSpace, final int iVertexRepresentitive) { + TSpace res = new TSpace(); + float fAngleSum = 0; + + for (int face = 0; face < iFaces; face++) { + final int f = face_indices[face]; + + // only valid triangles get to add their contribution + if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0) { + + int i = -1; + if (piTriListIn[3 * f + 0] == iVertexRepresentitive) { + i = 0; + } else if (piTriListIn[3 * f + 1] == iVertexRepresentitive) { + i = 1; + } else if (piTriListIn[3 * f + 2] == iVertexRepresentitive) { + i = 2; + } + assert (i >= 0 && i < 3); + + // project + int index = piTriListIn[3 * f + i]; + Vector3f n = getNormal(mikkTSpace, index); + Vector3f vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os))); + Vector3f vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot))); + vOs.normalizeLocal(); + vOt.normalizeLocal(); + + int i2 = piTriListIn[3 * f + (i < 2 ? (i + 1) : 0)]; + int i1 = piTriListIn[3 * f + i]; + int i0 = piTriListIn[3 * f + (i > 0 ? (i - 1) : 2)]; + + Vector3f p0 = getPosition(mikkTSpace, i0); + Vector3f p1 = getPosition(mikkTSpace, i1); + Vector3f p2 = getPosition(mikkTSpace, i2); + Vector3f v1 = p0.subtract(p1); + Vector3f v2 = p2.subtract(p1); + + // project + v1.subtractLocal(n.mult(n.dot(v1))).normalizeLocal(); + v2.subtractLocal(n.mult(n.dot(v2))).normalizeLocal(); + + // weight contribution by the angle + // between the two edge vectors + float fCos = v1.dot(v2); + fCos = fCos > 1 ? 1 : (fCos < (-1) ? (-1) : fCos); + float fAngle = (float) Math.acos(fCos); + float fMagS = pTriInfos[f].magS; + float fMagT = pTriInfos[f].magT; + + res.os.addLocal(vOs.multLocal(fAngle)); + res.ot.addLocal(vOt.multLocal(fAngle)); + res.magS += (fAngle * fMagS); + res.magT += (fAngle * fMagT); + fAngleSum += fAngle; + } + } + + // normalize + res.os.normalizeLocal(); + res.ot.normalizeLocal(); + + if (fAngleSum > 0) { + res.magS /= fAngleSum; + res.magT /= fAngleSum; + } + + return res; + } + + static boolean compareSubGroups(final SubGroup pg1, final SubGroup pg2) { + if(pg2 == null || (pg1.nrFaces != pg2.nrFaces)){ + return false; + } + boolean stillSame = true; + int i = 0; + while (i < pg1.nrFaces && stillSame) { + stillSame = pg1.triMembers[i] == pg2.triMembers[i]; + if (stillSame) { + ++i; + } + } + return stillSame; + } + + static void quickSort(int[] pSortBuffer, int iLeft, int iRight, long uSeed) { + int iL, iR, n, index, iMid, iTmp; + + // Random + long t = uSeed & 31; + t = (uSeed << t) | (uSeed >> (32 - t)); + uSeed = uSeed + t + 3; + // Random end + uSeed = uSeed & 0xffffffffL; + + iL = iLeft; + iR = iRight; + n = (iR - iL) + 1; + assert (n >= 0); + index = (int) ((uSeed & 0xffffffffL) % n); + + iMid = pSortBuffer[index + iL]; + + do { + while (pSortBuffer[iL] < iMid) { + ++iL; + } + while (pSortBuffer[iR] > iMid) { + --iR; + } + + if (iL <= iR) { + iTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = iTmp; + ++iL; + --iR; + } + } while (iL <= iR); + + if (iLeft < iR) { + quickSort(pSortBuffer, iLeft, iR, uSeed); + } + if (iL < iRight) { + quickSort(pSortBuffer, iL, iRight, uSeed); + } + } + + static void buildNeighborsFast(TriInfo pTriInfos[], Edge[] pEdges, final int piTriListIn[], final int iNrTrianglesIn) { + // build array of edges + long uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + final int i0 = piTriListIn[f * 3 + i]; + final int i1 = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)]; + pEdges[f * 3 + i] = new Edge(); + pEdges[f * 3 + i].setI0(i0 < i1 ? i0 : i1); // put minimum index in i0 + pEdges[f * 3 + i].setI1(!(i0 < i1) ? i0 : i1); // put maximum index in i1 + pEdges[f * 3 + i].setF(f); // record face number + } + } + + // sort over all edges by i0, this is the pricy one. + quickSortEdges(pEdges, 0, iNrTrianglesIn * 3 - 1, 0, uSeed); // sort channel 0 which is i0 + + // sub sort over i1, should be fast. + // could replace this with a 64 bit int sort over (i0,i1) + // with i0 as msb in the quicksort call above. + int iEntries = iNrTrianglesIn * 3; + int iCurStartIndex = 0; + for (int i = 1; i < iEntries; i++) { + if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0()) { + final int iL = iCurStartIndex; + final int iR = i - 1; + //final int iElems = i-iL; + iCurStartIndex = i; + quickSortEdges(pEdges, iL, iR, 1, uSeed); // sort channel 1 which is i1 + } + } + + // sub sort over f, which should be fast. + // this step is to remain compliant with BuildNeighborsSlow() when + // more than 2 triangles use the same edge (such as a butterfly topology). + iCurStartIndex = 0; + for (int i = 1; i < iEntries; i++) { + if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0() || pEdges[iCurStartIndex].getI1() != pEdges[i].getI1()) { + final int iL = iCurStartIndex; + final int iR = i - 1; + //final int iElems = i-iL; + iCurStartIndex = i; + quickSortEdges(pEdges, iL, iR, 2, uSeed); // sort channel 2 which is f + } + } + + // pair up, adjacent triangles + for (int i = 0; i < iEntries; i++) { + final int i0 = pEdges[i].getI0(); + final int i1 = pEdges[i].getI1(); + final int g = pEdges[i].getF(); + boolean bUnassigned_A; + + int[] i0_A = new int[1]; + int[] i1_A = new int[1]; + int[] edgenum_A = new int[1]; + int[] edgenum_B = new int[1]; + //int edgenum_B=0; // 0,1 or 2 + int[] triList = new int[3]; + System.arraycopy(piTriListIn, g * 3, triList, 0, 3); + getEdge(i0_A, i1_A, edgenum_A, triList, i0, i1); // resolve index ordering and edge_num + bUnassigned_A = pTriInfos[g].faceNeighbors[edgenum_A[0]] == -1; + + if (bUnassigned_A) { + // get true index ordering + int j = i + 1, t; + boolean bNotFound = true; + while (j < iEntries && i0 == pEdges[j].getI0() && i1 == pEdges[j].getI1() && bNotFound) { + boolean bUnassigned_B; + int[] i0_B = new int[1]; + int[] i1_B = new int[1]; + t = pEdges[j].getF(); + // flip i0_B and i1_B + System.arraycopy(piTriListIn, t * 3, triList, 0, 3); + getEdge(i1_B, i0_B, edgenum_B, triList, pEdges[j].getI0(), pEdges[j].getI1()); // resolve index ordering and edge_num + //assert(!(i0_A==i1_B && i1_A==i0_B)); + bUnassigned_B = pTriInfos[t].faceNeighbors[edgenum_B[0]] == -1; + if (i0_A[0] == i0_B[0] && i1_A[0] == i1_B[0] && bUnassigned_B) { + bNotFound = false; + } else { + ++j; + } + } + + if (!bNotFound) { + int t2 = pEdges[j].getF(); + pTriInfos[g].faceNeighbors[edgenum_A[0]] = t2; + //assert(pTriInfos[t].FaceNeighbors[edgenum_B]==-1); + pTriInfos[t2].faceNeighbors[edgenum_B[0]] = g; + } + } + } + } + + static void buildNeighborsSlow(TriInfo pTriInfos[], final int piTriListIn[], final int iNrTrianglesIn) { + + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + // if unassigned + if (pTriInfos[f].faceNeighbors[i] == -1) { + final int i0_A = piTriListIn[f * 3 + i]; + final int i1_A = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)]; + + // search for a neighbor + boolean bFound = false; + int t = 0, j = 0; + while (!bFound && t < iNrTrianglesIn) { + if (t != f) { + j = 0; + while (!bFound && j < 3) { + // in rev order + final int i1_B = piTriListIn[t * 3 + j]; + final int i0_B = piTriListIn[t * 3 + (j < 2 ? (j + 1) : 0)]; + //assert(!(i0_A==i1_B && i1_A==i0_B)); + if (i0_A == i0_B && i1_A == i1_B) { + bFound = true; + } else { + ++j; + } + } + } + + if (!bFound) { + ++t; + } + } + + // assign neighbors + if (bFound) { + pTriInfos[f].faceNeighbors[i] = t; + //assert(pTriInfos[t].FaceNeighbors[j]==-1); + pTriInfos[t].faceNeighbors[j] = f; + } + } + } + } + } + + static void quickSortEdges(Edge[] pSortBuffer, int iLeft, int iRight, final int channel, long uSeed) { + // early out + Edge sTmp; + final int iElems = iRight - iLeft + 1; + if (iElems < 2) { + return; + } else if (iElems == 2) { + if (pSortBuffer[iLeft].array[channel] > pSortBuffer[iRight].array[channel]) { + sTmp = pSortBuffer[iLeft]; + pSortBuffer[iLeft] = pSortBuffer[iRight]; + pSortBuffer[iRight] = sTmp; + } + return; + } + + // Random + long t = uSeed & 31; + t = (uSeed << t) | (uSeed >> (32 - t)); + uSeed = uSeed + t + 3; + // Random end + + uSeed = uSeed & 0xffffffffL; + + int iL = iLeft; + int iR = iRight; + int n = (iR - iL) + 1; + assert (n >= 0); + int index = (int) (uSeed % n); + + int iMid = pSortBuffer[index + iL].array[channel]; + + do { + while (pSortBuffer[iL].array[channel] < iMid) { + ++iL; + } + while (pSortBuffer[iR].array[channel] > iMid) { + --iR; + } + + if (iL <= iR) { + sTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = sTmp; + ++iL; + --iR; + } + } while (iL <= iR); + + if (iLeft < iR) { + quickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + } + if (iL < iRight) { + quickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); + } + } + +// resolve ordering and edge number + static void getEdge(int[] i0_out, int[] i1_out, int[] edgenum_out, final int[] indices, final int i0_in, final int i1_in) { + edgenum_out[0] = -1; + + // test if first index is on the edge + if (indices[0] == i0_in || indices[0] == i1_in) { + // test if second index is on the edge + if (indices[1] == i0_in || indices[1] == i1_in) { + edgenum_out[0] = 0; // first edge + i0_out[0] = indices[0]; + i1_out[0] = indices[1]; + } else { + edgenum_out[0] = 2; // third edge + i0_out[0] = indices[2]; + i1_out[0] = indices[0]; + } + } else { + // only second and third index is on the edge + edgenum_out[0] = 1; // second edge + i0_out[0] = indices[1]; + i1_out[0] = indices[2]; + } + } + + static void degenPrologue(TriInfo pTriInfos[], int piTriList_out[], final int iNrTrianglesIn, final int iTotTris) { + + // locate quads with only one good triangle + int t = 0; + while (t < (iTotTris - 1)) { + final int iFO_a = pTriInfos[t].orgFaceNumber; + final int iFO_b = pTriInfos[t + 1].orgFaceNumber; + if (iFO_a == iFO_b) { + // this is a quad + final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0; + final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0; + //TODO nehon : Check this in detail as this operation is utterly strange + if ((bIsDeg_a ^ bIsDeg_b) != false) { + pTriInfos[t].flag |= QUAD_ONE_DEGEN_TRI; + pTriInfos[t + 1].flag |= QUAD_ONE_DEGEN_TRI; + } + t += 2; + } else { + ++t; + } + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + int iNextGoodTriangleSearchIndex = 1; + t = 0; + boolean bStillFindingGoodOnes = true; + while (t < iNrTrianglesIn && bStillFindingGoodOnes) { + final boolean bIsGood = (pTriInfos[t].flag & MARK_DEGENERATE) == 0; + if (bIsGood) { + if (iNextGoodTriangleSearchIndex < (t + 2)) { + iNextGoodTriangleSearchIndex = t + 2; + } + } else { + // search for the first good triangle. + boolean bJustADegenerate = true; + while (bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris) { + final boolean bIsGood2 = (pTriInfos[iNextGoodTriangleSearchIndex].flag & MARK_DEGENERATE) == 0; + if (bIsGood2) { + bJustADegenerate = false; + } else { + ++iNextGoodTriangleSearchIndex; + } + } + + int t0 = t; + int t1 = iNextGoodTriangleSearchIndex; + ++iNextGoodTriangleSearchIndex; + assert (iNextGoodTriangleSearchIndex > (t + 1)); + + // swap triangle t0 and t1 + if (!bJustADegenerate) { + for (int i = 0; i < 3; i++) { + final int index = piTriList_out[t0 * 3 + i]; + piTriList_out[t0 * 3 + i] = piTriList_out[t1 * 3 + i]; + piTriList_out[t1 * 3 + i] = index; + } + { + final TriInfo tri_info = pTriInfos[t0]; + pTriInfos[t0] = pTriInfos[t1]; + pTriInfos[t1] = tri_info; + } + } else { + bStillFindingGoodOnes = false; // this is not supposed to happen + } + } + + if (bStillFindingGoodOnes) { + ++t; + } + } + + assert (bStillFindingGoodOnes); // code will still work. + assert (iNrTrianglesIn == t); + } + + static void DegenEpilogue(TSpace psTspace[], TriInfo pTriInfos[], int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn, final int iTotTris) { + + // deal with degenerate triangles + // punishment for degenerate triangles is O(N^2) + for (int t = iNrTrianglesIn; t < iTotTris; t++) { + // degenerate triangles on a quad with one good triangle are skipped + // here but processed in the next loop + final boolean bSkip = (pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0; + + if (!bSkip) { + for (int i = 0; i < 3; i++) { + final int index1 = piTriListIn[t * 3 + i]; + // search through the good triangles + boolean bNotFound = true; + int j = 0; + while (bNotFound && j < (3 * iNrTrianglesIn)) { + final int index2 = piTriListIn[j]; + if (index1 == index2) { + bNotFound = false; + } else { + ++j; + } + } + + if (!bNotFound) { + final int iTri = j / 3; + final int iVert = j % 3; + final int iSrcVert = pTriInfos[iTri].vertNum[iVert]; + final int iSrcOffs = pTriInfos[iTri].tSpacesOffs; + final int iDstVert = pTriInfos[t].vertNum[i]; + final int iDstOffs = pTriInfos[t].tSpacesOffs; + + // copy tspace + psTspace[iDstOffs + iDstVert] = psTspace[iSrcOffs + iSrcVert]; + } + } + } + } + + // deal with degenerate quads with one good triangle + for (int t = 0; t < iNrTrianglesIn; t++) { + // this triangle belongs to a quad where the + // other triangle is degenerate + if ((pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0) { + + byte[] pV = pTriInfos[t].vertNum; + int iFlag = (1 << pV[0]) | (1 << pV[1]) | (1 << pV[2]); + int iMissingIndex = 0; + if ((iFlag & 2) == 0) { + iMissingIndex = 1; + } else if ((iFlag & 4) == 0) { + iMissingIndex = 2; + } else if ((iFlag & 8) == 0) { + iMissingIndex = 3; + } + + int iOrgF = pTriInfos[t].orgFaceNumber; + Vector3f vDstP = getPosition(mikkTSpace, makeIndex(iOrgF, iMissingIndex)); + boolean bNotFound = true; + int i = 0; + while (bNotFound && i < 3) { + final int iVert = pV[i]; + final Vector3f vSrcP = getPosition(mikkTSpace, makeIndex(iOrgF, iVert)); + if (vSrcP.equals(vDstP)) { + final int iOffs = pTriInfos[t].tSpacesOffs; + psTspace[iOffs + iMissingIndex] = psTspace[iOffs + iVert]; + bNotFound = false; + } else { + ++i; + } + } + assert (!bNotFound); + } + } + + } + + /** + * SubGroup inner class + */ + private static class SubGroup { + int nrFaces; + int[] triMembers; + } + + private static class Group { + int nrFaces; + List faceIndices = new ArrayList(); + int vertexRepresentitive; + boolean orientPreservering; + } + + private static class TriInfo { + + int[] faceNeighbors = new int[3]; + Group[] assignedGroup = new Group[3]; + + // normalized first order face derivatives + Vector3f os = new Vector3f(); + Vector3f ot = new Vector3f(); + float magS, magT; // original magnitudes + + // determines if the current and the next triangle are a quad. + int orgFaceNumber; + int flag, tSpacesOffs; + byte[] vertNum = new byte[4]; + } + + private static class TSpace { + + Vector3f os = new Vector3f(); + float magS; + Vector3f ot = new Vector3f(); + float magT; + int counter; // this is to average back into quads. + boolean orient; + + void set(TSpace ts){ + os.set(ts.os); + magS = ts.magS; + ot.set(ts.ot); + magT = ts.magT; + counter = ts.counter; + orient = ts.orient; + } + } + + private static class TmpVert { + + float vert[] = new float[3]; + int index; + } + + private static class Edge { + + void setI0(int i){ + array[0] = i; + } + + void setI1(int i){ + array[1] = i; + } + + void setF(int i){ + array[2] = i; + } + + int getI0(){ + return array[0]; + } + + int getI1(){ + return array[1]; + } + + int getF(){ + return array[2]; + } + + int[] array = new int[3]; + } + +} From a3372284e9a44134f1a4c6a77e214f1a4c5f91c6 Mon Sep 17 00:00:00 2001 From: MeFisto94 Date: Tue, 1 Mar 2016 09:48:23 +0100 Subject: [PATCH 24/24] Be more verbose when the Asset Path is outside of the root. --- jme3-core/src/main/java/com/jme3/asset/AssetKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetKey.java b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java index a266bd74c..4066d7e14 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java @@ -156,7 +156,7 @@ public class AssetKey implements Savable, Cloneable { list.removeLast(); } else { list.add(".."); - Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path is outside assetmanager root"); + Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path \"{0}\" is outside assetmanager root", path); } } else { list.add(string);