├── .gitignore ├── README.md ├── build.gradle ├── buildSrc ├── build.gradle ├── settings.gradle └── src │ └── main │ └── groovy │ ├── jmec.java-conventions.gradle │ └── jmec.published-library.gradle ├── examples └── node-demo │ ├── assets │ └── Probes │ │ ├── Parking_Lot.j3o │ │ ├── River_Road.j3o │ │ ├── Stonewall.j3o │ │ └── studio.j3o │ ├── build.gradle │ ├── sampleScripts │ └── test-script.groovy │ ├── samples │ ├── test-model.blend │ └── test-model.gltf │ ├── settings.gradle │ └── src │ └── main │ └── java │ └── demo │ └── Main.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── license.md ├── release-notes.md ├── settings.gradle └── src └── main ├── java └── com │ └── simsilica │ └── jmec │ ├── AssetReader.java │ ├── AssetWriter.java │ ├── BuildInfo.java │ ├── Convert.java │ ├── JulLogSetup.java │ ├── ModelAssets.java │ ├── ModelInfo.java │ ├── ModelProcessor.java │ ├── ModelScript.java │ ├── Probe.java │ ├── gltf │ └── GltfExtrasLoader.java │ └── view │ └── JmecNode.java └── resources ├── LICENSE └── log4j2.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # Edit backup directories 26 | .backups/ 27 | 28 | # gradle related directories 29 | .gradle/ 30 | build/ 31 | 32 | # We shouldn't be checking in DLLs but we will find them in our JME source 33 | # try all the time... best to skip them 34 | *.dll 35 | *.so 36 | 37 | # In our project, logs directories are generated by the app and we never 38 | # want to check them in. 39 | logs/ 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JmeConvert 2 | A command line utility for converting models to J3O and copying their dependencies to a new target structure. 3 | ``` 4 | Current command line help: 5 | _ __ __ ___ ___ 6 | _ | | | \/ | | __| / __| 7 | | || | | |\/| | | _| | (__ 8 | \__/ |_| |_| |___| \___| 9 | 10 | JME3 Asset Converter v1.0.0 build:2019-05-17T03:46:59-0400 11 | 12 | 03:47:00,290 INFO [Convert] Max memory:3641.00 mb 13 | Usage: jmec [options] [models] 14 | 15 | Where [models] are a list of JME-compatible model files. 16 | 17 | Where [options] are: 18 | -sourceRoot : specifies the asset root for the models. 19 | Model dependency paths will be evaluated relative to this root. 20 | 21 | -targetRoot : specifies the asset target root for writing 22 | converted assets and their dependencies. 23 | 24 | -targetPath : a path string specifying where to place 25 | the copied/converted assets within the targetRoot. Any 26 | internal asset keys will be 'rehomed' to this path. 27 | 28 | -script : a script file that will be run against the model 29 | before writing out. Any number of script files can be specified 30 | and they will be run in the order specified. 31 | Groovy and Javascript are supported 'out of the box' but any 32 | JSR 223 compatible scripting engine should work if on the classpath. 33 | 34 | -probe [probe options string] : configures the information that the probe 35 | will output. 36 | [probe options]: 37 | A : all options turned on, same as: btrscpdu 38 | b : show bounding volumes 39 | t : show translations 40 | r : show rotation 41 | s : show scale 42 | c : show the list of controls 43 | p : show material parameters 44 | u : show user-added data 45 | d : list asset dependencies 46 | 47 | Examples: 48 | 49 | >jmec -sourceRoot C:\Downloads\CoolModel -targetRoot assets -targetPath Models/CoolModel C:\Downloads\CoolModel\AwesomeThing.gltf 50 | 51 | Converts C:\Downloads\CoolModel\AwesomeThing.gltf to a j3o and writes it 52 | to ./assets/Models/CoolModel/AwesomeThing.gltf.j3o 53 | 54 | Any dependent textures, etc. relative to C:\Downloads\CoolModel will 55 | be copied until the appropriate ./assets/Models/CoolModel/* subdirectory. 56 | 57 | For example: 58 | C:\Downloads\CoolModel\textures\awesome-sauce.jpg 59 | Gets copied to: 60 | ./assets/Models/CoolModel/textures/awesome-sauce.jpg 61 | ``` 62 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Root-level JmeConvert build script. 3 | */ 4 | 5 | plugins { 6 | id 'jmec.published-library' 7 | id 'application' 8 | id "com.gorylenko.gradle-git-properties" version "2.4.0" 9 | } 10 | 11 | version='1.3.1-SNAPSHOT' 12 | 13 | mainClassName='com.simsilica.jmec.Convert' 14 | 15 | 16 | applicationDefaultJvmArgs = ["-Xmx4g", "-Xms512m", "-XX:MaxDirectMemorySize=1024m"] 17 | 18 | ext.jmeVersion = "3.3.2-stable" 19 | 20 | // Set this module's maven pom description 21 | publishing.publications.library(MavenPublication).pom { 22 | description = 'A command line utility and embeddable Java library for converting models to jMonkeyEngine format.' 23 | } 24 | 25 | dependencies { 26 | api "org.jmonkeyengine:jme3-core:$jmeVersion" 27 | 28 | // Make sure we have all of the plugins, loaders, etc. 29 | implementation "org.jmonkeyengine:jme3-desktop:$jmeVersion" 30 | implementation "org.jmonkeyengine:jme3-plugins:$jmeVersion" 31 | //runtimeOnly "org.jmonkeyengine:jme3-blender:$jmeVersion" 32 | // Blender is a separate project now and not well supported. 33 | 34 | api 'com.google.guava:guava:19.0' 35 | 36 | // For runtime scripting support 37 | runtimeOnly 'org.codehaus.groovy:groovy-all:2.4.11' 38 | 39 | implementation 'org.slf4j:slf4j-api:1.7.13' 40 | implementation 'org.slf4j:jul-to-slf4j:1.7.13' 41 | runtimeOnly 'org.apache.logging.log4j:log4j-api:2.5' 42 | runtimeOnly 'org.apache.logging.log4j:log4j-core:2.5' 43 | runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.5' 44 | 45 | // Trying to get log coloring working properly on windows 46 | runtimeOnly 'org.fusesource.jansi:jansi:1.11' 47 | 48 | } 49 | 50 | compileJava.doLast { 51 | def buildDate = new Date().format('yyyyMMdd') 52 | println "Writing jmec.build.date:" + buildDate 53 | new File(destinationDir, 'jmec.build.date').text = "build.date=$buildDate" 54 | } 55 | 56 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Build file for the buildSrc convention plugins. 3 | * buildSrc is a special gradle sub-project that builds local 4 | * convention plugins for use in the regular project build 5 | * files. 6 | * See: 7 | * https://docs.gradle.org/current/userguide/sharing_build_logic_between_subprojects.html 8 | * https://docs.gradle.org/current/samples/sample_convention_plugins.html 9 | */ 10 | 11 | plugins { 12 | id 'groovy-gradle-plugin' 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='jmec-conventions' 2 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/jmec.java-conventions.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Conventions for all sio2 java-based modules. 3 | */ 4 | 5 | plugins { 6 | id 'java' 7 | //id 'checkstyle' 8 | } 9 | 10 | // Projects should use Maven Central for external dependencies 11 | repositories { 12 | mavenLocal() 13 | mavenCentral() 14 | } 15 | 16 | //checkstyle { 17 | // config = ... 18 | // maxWarnings = 0 19 | //} 20 | 21 | compileJava { // compile-time options: 22 | options.encoding = 'UTF-8' 23 | options.compilerArgs << '-Xlint:unchecked' 24 | options.deprecation = true 25 | if( JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_1_10) ) { 26 | options.release = 7 27 | } 28 | } 29 | 30 | java { 31 | sourceCompatibility = 1.7 32 | targetCompatibility = 1.7 33 | withJavadocJar() 34 | withSourcesJar() 35 | } 36 | 37 | javadoc { 38 | // Disable doclint for JDK8+. 39 | if( JavaVersion.current().isJava8Compatible() ) { 40 | options.addStringOption('Xdoclint:none', '-quiet') 41 | } 42 | } 43 | 44 | test { 45 | testLogging { 46 | // I want to see the tests that are run and pass, etc. 47 | events "passed", "skipped", "failed", "standardOut", "standardError" 48 | } 49 | } 50 | 51 | sourceSets { 52 | main { 53 | resources { 54 | exclude "**/.backups/**" 55 | } 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/jmec.published-library.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Conventions for all JMEC maven-published modules. 3 | */ 4 | 5 | plugins { 6 | id 'java-library' 7 | id 'maven-publish' 8 | id 'jmec.java-conventions' 9 | id 'signing' 10 | } 11 | 12 | group = 'com.simsilica' 13 | 14 | publishing { 15 | publications { 16 | library(MavenPublication) { 17 | from components.java 18 | pom { 19 | developers { 20 | developer { 21 | name = 'Paul Speed' 22 | } 23 | } 24 | inceptionYear = '2019' 25 | licenses { 26 | license { 27 | distribution = 'repo' 28 | name = 'New BSD (3-clause) License' 29 | url = 'https://github.com/Simsilica/JmeConvert/blob/master/license.md' 30 | } 31 | } 32 | name = project.group + ':' + project.name 33 | scm { 34 | connection = 'scm:git:git://github.com/Simsilica/JmeConvert.git' 35 | developerConnection = 'scm:git:ssh://github.com:Simsilica/JmeConvert.git' 36 | url = 'https://github.com/Simsilica/JmeConvert/tree/master' 37 | } 38 | url = 'https://github.com/Simsilica/JmeConvert' 39 | } 40 | } 41 | } 42 | 43 | // Staging to OSSRH relies on the existence of 2 properties 44 | // (ossrhUsername and ossrhPassword) 45 | // which should be stored in ~/.gradle/gradle.properties 46 | repositories { 47 | maven { 48 | credentials { 49 | username = project.hasProperty('ossrhUsername') ? ossrhUsername : 'Unknown user' 50 | password = project.hasProperty('ossrhPassword') ? ossrhPassword : 'Unknown password' 51 | } 52 | name = 'OSSRH' 53 | 54 | def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2' 55 | def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots' 56 | 57 | // Have to evaluate the project version late because when the conventions 58 | // are configured the project build file hasn't set the version yet. 59 | afterEvaluate { 60 | url = project.version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 61 | } 62 | } 63 | } 64 | } 65 | 66 | 67 | tasks.register('install') { 68 | dependsOn 'publishToMavenLocal' 69 | description 'Installs Maven artifacts to the local repository.' 70 | } 71 | 72 | // signing tasks 73 | // Signing relies on the existence of 3 properties 74 | // (signing.keyId, signing.password, and signing.secretKeyRingFile) 75 | // which should be stored in ~/.gradle/gradle.properties 76 | 77 | signing { 78 | sign publishing.publications 79 | } 80 | tasks.withType(Sign) { 81 | onlyIf { project.hasProperty('signing.keyId') } 82 | } 83 | 84 | // Customize some tasks 85 | tasks.sourcesJar { 86 | exclude "**/.backups/**" 87 | } 88 | 89 | -------------------------------------------------------------------------------- /examples/node-demo/assets/Probes/Parking_Lot.j3o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simsilica/JmeConvert/42dfd8b311a0c9453cb8221aebd775fb6076f184/examples/node-demo/assets/Probes/Parking_Lot.j3o -------------------------------------------------------------------------------- /examples/node-demo/assets/Probes/River_Road.j3o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simsilica/JmeConvert/42dfd8b311a0c9453cb8221aebd775fb6076f184/examples/node-demo/assets/Probes/River_Road.j3o -------------------------------------------------------------------------------- /examples/node-demo/assets/Probes/Stonewall.j3o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simsilica/JmeConvert/42dfd8b311a0c9453cb8221aebd775fb6076f184/examples/node-demo/assets/Probes/Stonewall.j3o -------------------------------------------------------------------------------- /examples/node-demo/assets/Probes/studio.j3o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simsilica/JmeConvert/42dfd8b311a0c9453cb8221aebd775fb6076f184/examples/node-demo/assets/Probes/studio.j3o -------------------------------------------------------------------------------- /examples/node-demo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'application' 4 | id 'idea' 5 | id 'eclipse' 6 | } 7 | 8 | mainClassName='demo.Main' 9 | 10 | repositories { 11 | mavenLocal() 12 | mavenCentral() 13 | } 14 | 15 | ext.jmeVersion = "3.3.2-stable" 16 | ext.jmecVersion = "1.3.0" 17 | 18 | 19 | dependencies { 20 | implementation "org.jmonkeyengine:jme3-core:$jmeVersion" 21 | implementation "org.jmonkeyengine:jme3-desktop:$jmeVersion" 22 | implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVersion" 23 | 24 | implementation "com.simsilica:jmec:$jmecVersion" 25 | 26 | implementation 'com.simsilica:lemur:1.16.0' 27 | } 28 | 29 | sourceSets { 30 | main { 31 | resources { 32 | srcDirs += "assets" 33 | //excludes += [ 34 | // "**/*.psd", 35 | // "**/*.glb", 36 | // "**/*.gltf" 37 | //] 38 | } 39 | } 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/node-demo/sampleScripts/test-script.groovy: -------------------------------------------------------------------------------- 1 | 2 | // The following bindings are setup by default: 3 | // convert - the JME Convert instance 4 | // model - the loaded ModelInfo. model.modelRoot is the loaded spatial. 5 | println "Convert:" + convert 6 | println "Model:" + model 7 | 8 | // Example of finding all nodes in a scene 9 | println "All nodes in the scene..." 10 | model.findAll(com.jme3.scene.Node.class).each { node -> 11 | println "found:" + node 12 | } 13 | 14 | println "All nodes named 'Scene' in the scene..." 15 | model.findAll("Scene", com.jme3.scene.Node.class).each { node -> 16 | println "found:" + node 17 | } 18 | 19 | // Move the model so that it's base is at y=0 20 | def bound = model.modelRoot.getWorldBound() 21 | model.modelRoot.setLocalTranslation(0, (float)(bound.yExtent - bound.center.y), 0); 22 | 23 | // Make sure all included materials with a name get a generated .j3m 24 | import com.jme3.asset.MaterialKey; 25 | import com.jme3.scene.Geometry; 26 | model.findAll(Geometry.class).each { geom -> 27 | 28 | if( geom.material.name != null ) { 29 | model.generateMaterial(geom.material, "materials/" + geom.material.name); 30 | } else { 31 | // We'll try to make up a unique name 32 | def name = geom.name; 33 | for( def s = geom.parent; s != null; s = s.parent ) { 34 | name = s.name + "_" + name; 35 | } 36 | model.generateMaterial(geom.material, "materials/" + name); 37 | } 38 | } 39 | 40 | float x = 0; 41 | println "All nodes..."; 42 | model.findAll(com.jme3.scene.Spatial.class).each { node -> 43 | println node 44 | 45 | println "User data keys:" + node.userDataKeys 46 | 47 | String submodel = node.getUserData("submodel"); 48 | if( submodel != null ) { 49 | println "Submode:" + submodel; 50 | model.extractSubmodel(node, submodel); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/node-demo/samples/test-model.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simsilica/JmeConvert/42dfd8b311a0c9453cb8221aebd775fb6076f184/examples/node-demo/samples/test-model.blend -------------------------------------------------------------------------------- /examples/node-demo/samples/test-model.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset" : { 3 | "generator" : "Khronos glTF Blender I/O v1.0.5", 4 | "version" : "2.0" 5 | }, 6 | "scene" : 0, 7 | "scenes" : [ 8 | { 9 | "extras" : { 10 | "glTF2ExportSettings" : { 11 | "export_apply" : 1, 12 | "export_extras" : 1, 13 | "export_format" : "GLTF_SEPARATE", 14 | "export_tangents" : 1 15 | } 16 | }, 17 | "name" : "Scene", 18 | "nodes" : [ 19 | 2, 20 | 3, 21 | 4, 22 | 7, 23 | 8, 24 | 11, 25 | 12, 26 | 13 27 | ] 28 | } 29 | ], 30 | "nodes" : [ 31 | { 32 | "mesh" : 0, 33 | "name" : "Cylinder", 34 | "translation" : [ 35 | 0, 36 | 3.5248501300811768, 37 | 0 38 | ] 39 | }, 40 | { 41 | "mesh" : 1, 42 | "name" : "Sphere", 43 | "translation" : [ 44 | 0, 45 | 1.6277294158935547, 46 | 0 47 | ] 48 | }, 49 | { 50 | "children" : [ 51 | 0, 52 | 1 53 | ], 54 | "extras" : { 55 | "submodel" : "test-submodel" 56 | }, 57 | "mesh" : 2, 58 | "name" : "Cube", 59 | "translation" : [ 60 | 0.6716637015342712, 61 | 1.0070183277130127, 62 | 3.314561128616333 63 | ] 64 | }, 65 | { 66 | "name" : "Light", 67 | "rotation" : [ 68 | 0.16907575726509094, 69 | 0.7558803558349609, 70 | -0.27217137813568115, 71 | 0.570947527885437 72 | ], 73 | "translation" : [ 74 | 4.076245307922363, 75 | 5.903861999511719, 76 | -1.0054539442062378 77 | ] 78 | }, 79 | { 80 | "name" : "Camera", 81 | "rotation" : [ 82 | 0.30479565262794495, 83 | 0.6932504177093506, 84 | -0.4294925928115845, 85 | 0.49197518825531006 86 | ], 87 | "translation" : [ 88 | 21.189729690551758, 89 | 13.086507797241211, 90 | -5.02763557434082 91 | ] 92 | }, 93 | { 94 | "mesh" : 0, 95 | "name" : "Cylinder.002", 96 | "translation" : [ 97 | 0, 98 | 3.5248501300811768, 99 | 0 100 | ] 101 | }, 102 | { 103 | "mesh" : 1, 104 | "name" : "Sphere.002", 105 | "translation" : [ 106 | 0, 107 | 1.6277294158935547, 108 | 0 109 | ] 110 | }, 111 | { 112 | "children" : [ 113 | 5, 114 | 6 115 | ], 116 | "extras" : { 117 | "submodel" : "test-submodel" 118 | }, 119 | "mesh" : 2, 120 | "name" : "Cube.002", 121 | "translation" : [ 122 | 4.057814121246338, 123 | 0.8479821085929871, 124 | 6.510812759399414 125 | ] 126 | }, 127 | { 128 | "name" : "Light.001", 129 | "rotation" : [ 130 | 0.16907575726509094, 131 | 0.7558803558349609, 132 | -0.27217137813568115, 133 | 0.570947527885437 134 | ], 135 | "translation" : [ 136 | 7.462395668029785, 137 | 5.744825839996338, 138 | 2.190797805786133 139 | ] 140 | }, 141 | { 142 | "mesh" : 0, 143 | "name" : "Cylinder.001", 144 | "translation" : [ 145 | 0, 146 | 3.524850368499756, 147 | 0 148 | ] 149 | }, 150 | { 151 | "mesh" : 1, 152 | "name" : "Sphere.001", 153 | "translation" : [ 154 | 0, 155 | 1.6277295351028442, 156 | 0 157 | ] 158 | }, 159 | { 160 | "children" : [ 161 | 9, 162 | 10 163 | ], 164 | "mesh" : 2, 165 | "name" : "Cube.001", 166 | "translation" : [ 167 | -0.9786449670791626, 168 | 0.8783012628555298, 169 | -2.1278927326202393 170 | ] 171 | }, 172 | { 173 | "mesh" : 3, 174 | "name" : "Cone", 175 | "translation" : [ 176 | 0.6865530014038086, 177 | 6.559228420257568, 178 | 3.264239549636841 179 | ] 180 | }, 181 | { 182 | "mesh" : 4, 183 | "name" : "Cone.001", 184 | "translation" : [ 185 | -0.9478145241737366, 186 | 6.424351215362549, 187 | -2.1889755725860596 188 | ] 189 | } 190 | ], 191 | "materials" : [ 192 | { 193 | "doubleSided" : true, 194 | "name" : "Material.002", 195 | "pbrMetallicRoughness" : { 196 | "baseColorFactor" : [ 197 | 0.04293302819132805, 198 | 0.0653897300362587, 199 | 0.8000000715255737, 200 | 1 201 | ], 202 | "metallicFactor" : 1, 203 | "roughnessFactor" : 0 204 | } 205 | }, 206 | { 207 | "doubleSided" : true, 208 | "name" : "Material.001", 209 | "pbrMetallicRoughness" : { 210 | "baseColorFactor" : [ 211 | 0.36750009655952454, 212 | 0.8000000715255737, 213 | 0.42307808995246887, 214 | 1 215 | ], 216 | "metallicFactor" : 1, 217 | "roughnessFactor" : 0.14601770043373108 218 | } 219 | }, 220 | { 221 | "doubleSided" : true, 222 | "name" : "Material", 223 | "pbrMetallicRoughness" : { 224 | "baseColorFactor" : [ 225 | 0.056559015065431595, 226 | 0.006200659554451704, 227 | 0.20791545510292053, 228 | 1 229 | ], 230 | "metallicFactor" : 0, 231 | "roughnessFactor" : 1 232 | } 233 | } 234 | ], 235 | "meshes" : [ 236 | { 237 | "name" : "Cylinder", 238 | "primitives" : [ 239 | { 240 | "attributes" : { 241 | "POSITION" : 0, 242 | "NORMAL" : 1, 243 | "TEXCOORD_0" : 2 244 | }, 245 | "indices" : 3, 246 | "material" : 0 247 | } 248 | ] 249 | }, 250 | { 251 | "name" : "Sphere", 252 | "primitives" : [ 253 | { 254 | "attributes" : { 255 | "POSITION" : 4, 256 | "NORMAL" : 5, 257 | "TANGENT" : 6, 258 | "TEXCOORD_0" : 7 259 | }, 260 | "indices" : 8, 261 | "material" : 1 262 | } 263 | ] 264 | }, 265 | { 266 | "name" : "Cube", 267 | "primitives" : [ 268 | { 269 | "attributes" : { 270 | "POSITION" : 9, 271 | "NORMAL" : 10, 272 | "TANGENT" : 11, 273 | "TEXCOORD_0" : 12 274 | }, 275 | "indices" : 13, 276 | "material" : 2 277 | } 278 | ] 279 | }, 280 | { 281 | "name" : "Cone", 282 | "primitives" : [ 283 | { 284 | "attributes" : { 285 | "POSITION" : 14, 286 | "NORMAL" : 15, 287 | "TEXCOORD_0" : 16 288 | }, 289 | "indices" : 17 290 | } 291 | ] 292 | }, 293 | { 294 | "name" : "Cone.001", 295 | "primitives" : [ 296 | { 297 | "attributes" : { 298 | "POSITION" : 18, 299 | "NORMAL" : 19, 300 | "TEXCOORD_0" : 20 301 | }, 302 | "indices" : 17 303 | } 304 | ] 305 | } 306 | ], 307 | "accessors" : [ 308 | { 309 | "bufferView" : 0, 310 | "componentType" : 5126, 311 | "count" : 130, 312 | "max" : [ 313 | 1, 314 | 1, 315 | 1 316 | ], 317 | "min" : [ 318 | -1, 319 | -1, 320 | -1 321 | ], 322 | "type" : "VEC3" 323 | }, 324 | { 325 | "bufferView" : 1, 326 | "componentType" : 5126, 327 | "count" : 130, 328 | "type" : "VEC3" 329 | }, 330 | { 331 | "bufferView" : 2, 332 | "componentType" : 5126, 333 | "count" : 130, 334 | "type" : "VEC2" 335 | }, 336 | { 337 | "bufferView" : 3, 338 | "componentType" : 5123, 339 | "count" : 372, 340 | "type" : "SCALAR" 341 | }, 342 | { 343 | "bufferView" : 4, 344 | "componentType" : 5126, 345 | "count" : 559, 346 | "max" : [ 347 | 1.000000238418579, 348 | 1, 349 | 1.0000003576278687 350 | ], 351 | "min" : [ 352 | -0.9999998211860657, 353 | -1, 354 | -1 355 | ], 356 | "type" : "VEC3" 357 | }, 358 | { 359 | "bufferView" : 5, 360 | "componentType" : 5126, 361 | "count" : 559, 362 | "type" : "VEC3" 363 | }, 364 | { 365 | "bufferView" : 6, 366 | "componentType" : 5126, 367 | "count" : 559, 368 | "type" : "VEC4" 369 | }, 370 | { 371 | "bufferView" : 7, 372 | "componentType" : 5126, 373 | "count" : 559, 374 | "type" : "VEC2" 375 | }, 376 | { 377 | "bufferView" : 8, 378 | "componentType" : 5123, 379 | "count" : 2880, 380 | "type" : "SCALAR" 381 | }, 382 | { 383 | "bufferView" : 9, 384 | "componentType" : 5126, 385 | "count" : 24, 386 | "max" : [ 387 | 1, 388 | 1, 389 | 1 390 | ], 391 | "min" : [ 392 | -1, 393 | -1, 394 | -1 395 | ], 396 | "type" : "VEC3" 397 | }, 398 | { 399 | "bufferView" : 10, 400 | "componentType" : 5126, 401 | "count" : 24, 402 | "type" : "VEC3" 403 | }, 404 | { 405 | "bufferView" : 11, 406 | "componentType" : 5126, 407 | "count" : 24, 408 | "type" : "VEC4" 409 | }, 410 | { 411 | "bufferView" : 12, 412 | "componentType" : 5126, 413 | "count" : 24, 414 | "type" : "VEC2" 415 | }, 416 | { 417 | "bufferView" : 13, 418 | "componentType" : 5123, 419 | "count" : 36, 420 | "type" : "SCALAR" 421 | }, 422 | { 423 | "bufferView" : 14, 424 | "componentType" : 5126, 425 | "count" : 65, 426 | "max" : [ 427 | 1, 428 | 1, 429 | 1 430 | ], 431 | "min" : [ 432 | -1, 433 | -1, 434 | -1 435 | ], 436 | "type" : "VEC3" 437 | }, 438 | { 439 | "bufferView" : 15, 440 | "componentType" : 5126, 441 | "count" : 65, 442 | "type" : "VEC3" 443 | }, 444 | { 445 | "bufferView" : 16, 446 | "componentType" : 5126, 447 | "count" : 65, 448 | "type" : "VEC2" 449 | }, 450 | { 451 | "bufferView" : 17, 452 | "componentType" : 5123, 453 | "count" : 186, 454 | "type" : "SCALAR" 455 | }, 456 | { 457 | "bufferView" : 18, 458 | "componentType" : 5126, 459 | "count" : 65, 460 | "max" : [ 461 | 1, 462 | 1, 463 | 1 464 | ], 465 | "min" : [ 466 | -1, 467 | -1, 468 | -1 469 | ], 470 | "type" : "VEC3" 471 | }, 472 | { 473 | "bufferView" : 19, 474 | "componentType" : 5126, 475 | "count" : 65, 476 | "type" : "VEC3" 477 | }, 478 | { 479 | "bufferView" : 20, 480 | "componentType" : 5126, 481 | "count" : 65, 482 | "type" : "VEC2" 483 | } 484 | ], 485 | "bufferViews" : [ 486 | { 487 | "buffer" : 0, 488 | "byteLength" : 1560, 489 | "byteOffset" : 0 490 | }, 491 | { 492 | "buffer" : 0, 493 | "byteLength" : 1560, 494 | "byteOffset" : 1560 495 | }, 496 | { 497 | "buffer" : 0, 498 | "byteLength" : 1040, 499 | "byteOffset" : 3120 500 | }, 501 | { 502 | "buffer" : 0, 503 | "byteLength" : 744, 504 | "byteOffset" : 4160 505 | }, 506 | { 507 | "buffer" : 0, 508 | "byteLength" : 6708, 509 | "byteOffset" : 4904 510 | }, 511 | { 512 | "buffer" : 0, 513 | "byteLength" : 6708, 514 | "byteOffset" : 11612 515 | }, 516 | { 517 | "buffer" : 0, 518 | "byteLength" : 8944, 519 | "byteOffset" : 18320 520 | }, 521 | { 522 | "buffer" : 0, 523 | "byteLength" : 4472, 524 | "byteOffset" : 27264 525 | }, 526 | { 527 | "buffer" : 0, 528 | "byteLength" : 5760, 529 | "byteOffset" : 31736 530 | }, 531 | { 532 | "buffer" : 0, 533 | "byteLength" : 288, 534 | "byteOffset" : 37496 535 | }, 536 | { 537 | "buffer" : 0, 538 | "byteLength" : 288, 539 | "byteOffset" : 37784 540 | }, 541 | { 542 | "buffer" : 0, 543 | "byteLength" : 384, 544 | "byteOffset" : 38072 545 | }, 546 | { 547 | "buffer" : 0, 548 | "byteLength" : 192, 549 | "byteOffset" : 38456 550 | }, 551 | { 552 | "buffer" : 0, 553 | "byteLength" : 72, 554 | "byteOffset" : 38648 555 | }, 556 | { 557 | "buffer" : 0, 558 | "byteLength" : 780, 559 | "byteOffset" : 38720 560 | }, 561 | { 562 | "buffer" : 0, 563 | "byteLength" : 780, 564 | "byteOffset" : 39500 565 | }, 566 | { 567 | "buffer" : 0, 568 | "byteLength" : 520, 569 | "byteOffset" : 40280 570 | }, 571 | { 572 | "buffer" : 0, 573 | "byteLength" : 372, 574 | "byteOffset" : 40800 575 | }, 576 | { 577 | "buffer" : 0, 578 | "byteLength" : 780, 579 | "byteOffset" : 41172 580 | }, 581 | { 582 | "buffer" : 0, 583 | "byteLength" : 780, 584 | "byteOffset" : 41952 585 | }, 586 | { 587 | "buffer" : 0, 588 | "byteLength" : 520, 589 | "byteOffset" : 42732 590 | } 591 | ], 592 | "buffers" : [ 593 | { 594 | "byteLength" : 43252, 595 | "uri" : "test-model.bin" 596 | } 597 | ] 598 | } 599 | -------------------------------------------------------------------------------- /examples/node-demo/settings.gradle: -------------------------------------------------------------------------------- 1 | // Empty on purpose as we don't need to configure anything here 2 | -------------------------------------------------------------------------------- /examples/node-demo/src/main/java/demo/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2020, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package demo; 38 | 39 | import java.io.*; 40 | 41 | import com.jme3.app.SimpleApplication; 42 | import com.jme3.input.KeyInput; 43 | import com.jme3.input.controls.ActionListener; 44 | import com.jme3.input.controls.KeyTrigger; 45 | import com.jme3.light.*; 46 | import com.jme3.math.*; 47 | import com.jme3.material.Material; 48 | import com.jme3.scene.*; 49 | import com.jme3.scene.shape.Box; 50 | import com.jme3.texture.Texture; 51 | 52 | import com.simsilica.jmec.*; 53 | import com.simsilica.jmec.view.*; 54 | 55 | /** 56 | * 57 | * 58 | * @author Paul Speed 59 | */ 60 | public class Main extends SimpleApplication { 61 | 62 | public static void main( String... args ) { 63 | Main main = new Main(); 64 | main.start(); 65 | } 66 | 67 | public void simpleInitApp() { 68 | 69 | // Setup so that we can toggle the fly cam on/off with the spacebard 70 | inputManager.addMapping("FlyCamToggle", new KeyTrigger(KeyInput.KEY_SPACE)); 71 | inputManager.addListener(new FlyCamToggle(), "FlyCamToggle"); 72 | 73 | // Make sure that we can see our changes when we're switched to another 74 | // window 75 | setPauseOnLostFocus(false); 76 | 77 | // Setup the main light 78 | DirectionalLight sun = new DirectionalLight(new Vector3f(0.25f, -1, -1f).normalizeLocal()); 79 | rootNode.addLight(sun); 80 | 81 | // Setup the default light probe 82 | //Spatial probeHolder = assetManager.loadModel("Probes/studio.j3o"); 83 | //Spatial probeHolder = assetManager.loadModel("Probes/River_Road.j3o"); 84 | Spatial probeHolder = assetManager.loadModel("Probes/Stonewall.j3o"); 85 | //Spatial probeHolder = assetManager.loadModel("Probes/Parking_Lot.j3o"); 86 | LightProbe probe = (LightProbe)probeHolder.getLocalLightList().get(0); 87 | probe.setPosition(Vector3f.ZERO); 88 | probeHolder.removeLight(probe); 89 | rootNode.addLight(probe); 90 | 91 | cam.setLocation(new Vector3f(0.0f, 3.5173976f, 10.0f)); 92 | cam.setRotation(new Quaternion(0.0f, 0.9947752f, -0.102089666f, 0.0f)); 93 | 94 | // Setup a floor 95 | Box box = new Box(10, 1, 10); 96 | Geometry geom = new Geometry("box", box); 97 | geom.setLocalTranslation(0, -1, 0); 98 | Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); 99 | mat.setColor("Diffuse", ColorRGBA.Gray); 100 | mat.setColor("Ambient", ColorRGBA.Gray); 101 | mat.setBoolean("UseMaterialColors", true); 102 | //Texture texture = assetManager.loadTexture("Textures/Monkey.png"); 103 | //mat.setTexture("DiffuseMap", texture); 104 | 105 | geom.setMaterial(mat); 106 | rootNode.attachChild(geom); 107 | 108 | // Setup the JMEC node 109 | JmecNode jmecNode = new JmecNode(new File("samples/test-model.gltf")); 110 | //JmecNode jmecNode = new JmecNode(new File("samples/test-linking.gltf")); 111 | //JmecNode jmecNode = new JmecNode(new File("samples/test-level.gltf")); 112 | jmecNode.addModelScript(new File("sampleScripts/test-script.groovy")); 113 | jmecNode.getConvert().setProbeOptions("A"); 114 | 115 | // To resolve child assets we will need to supply an AssetManager 116 | // and we will need to make sure it's pointing out at our asset directory 117 | // or it won't be able to find assets that we've just written out. 118 | jmecNode.setAssetManager(assetManager); 119 | 120 | // Projects that already have ./asset in their runtime classpath do not 121 | // need this second part... but those that do, will have to make sure that 122 | // the file locator is before the classpath locator. 123 | assetManager.unregisterLocator("/", com.jme3.asset.plugins.ClasspathLocator.class); 124 | assetManager.registerLocator("./assets", com.jme3.asset.plugins.FileLocator.class); 125 | assetManager.registerLocator("/", com.jme3.asset.plugins.ClasspathLocator.class); 126 | 127 | rootNode.attachChild(jmecNode); 128 | } 129 | 130 | private class FlyCamToggle implements ActionListener { 131 | @Override 132 | public void onAction( String name, boolean value, float tpf ) { 133 | if( value ) { 134 | flyCam.setEnabled(!flyCam.isEnabled()); 135 | 136 | // Fly cam does not manage this correctly 137 | inputManager.setCursorVisible(!flyCam.isEnabled()); 138 | } 139 | } 140 | } 141 | } 142 | 143 | 144 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Simsilica/JmeConvert/42dfd8b311a0c9453cb8221aebd775fb6076f184/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Simsilica, LLC 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /release-notes.md: -------------------------------------------------------------------------------- 1 | Version 1.3.1 (unreleased) 2 | -------------- 3 | * Added "assets" to the script bindings so that model scripts can load 4 | model-relative assets. Useful for swapping/flipping textures in 5 | materials, etc.. These assets are also automatically added as 6 | model dependencies and copied to the target as relevant. 7 | * Fixed a few places where AssetKey.toString() was used where AssetKey.getName() 8 | is more appropriate. Prior to this change, texture dependencies with 9 | non-standard texture key properties would fail to copy during conversion 10 | because "myTexture.png (flipped)" is not a real file. 11 | * Modified the ModelInfo "model" script API to allow easily adding new 12 | dependencies that will be copied during conversion. 13 | * Added Convert.addModelProcessor() to allow embedding application to 14 | add custom model processors. 15 | * Added a "log" binding for scripts that logs to their script name appended 16 | to "script.". 17 | * Added ModelScript.set/getBinding for setting script binding variables 18 | from embedding applications. 19 | * Made AssetReader reusable by exposing AssetReader.setAssetRoot() method. 20 | Convert now uses the same AssetReader and AssetManager and will just reset 21 | the asset root path when required instead of creating a new one. This fixes 22 | a memory leak issue that was happening because of native memory allocated 23 | with previous asset manager instances was not being released properly for 24 | some reason and was leading to an OutOfMemoryError in embedding applications. 25 | * Added ModelInfo.markSharedAsset() to mark assets that should not be copied 26 | to the target directory as part of writing the final model. 27 | 28 | Version 1.3.0 (latest) 29 | -------------- 30 | * Upgraded gradle to 7.4.2 and published to maven central. 31 | * Modified BuildInfo to load a jmec.build.date properties file in addition 32 | to the normal git.properties. Newer versions of the git properties plugin 33 | seem to no longer provide a build.time. 34 | 35 | 36 | Version 1.2.0 37 | -------------- 38 | * Fixed AssedReader to use canonical path instead of absolute path 39 | because sometimes paths weren't fully resolved and it confused 40 | the 'localized path' detection. 41 | * Modified Convert to keep a list of ModelScripts instead of just 42 | script names. Makes it easier to manage scripts in embedded 43 | applications. 44 | * Modified ModelScript to be able to take the loaded script String 45 | on the constructor. 46 | * Modified ModelScript so that it can return its script name. 47 | * Added support for extracting subgraphs into separate j3o files 48 | linked into the main model with an AssetLinkNode. 49 | see: ModelInfo.extractSubmodel() 50 | * Fixed an issue where the custom extras loader was not being used 51 | for GLB files. 52 | * JmecNode for attaching an auto-updating JmeConverted asset to your 53 | scene that live-updates when the source model changes. 54 | 55 | 56 | Version 1.1.1 57 | -------------- 58 | * Modified to default -sourceRoot to the current directory if not 59 | specified. 60 | * Fixed an issue saving J3o files that had GLTF imported user-data 61 | that was not strings. During import, these are now specifically 62 | converted to Double or are treated as String if they are nested 63 | in such a way that JME does not support. 64 | 65 | 66 | Version 1.1.0 67 | -------------- 68 | * Added support for GLTF extensions as user-added data on the 69 | Spatial. Includes support for all JSON primitives as well 70 | as Lists and Maps. 71 | * Added a 'u' option to the -probe to show the user-added data 72 | of a converted model. 73 | 74 | 75 | Version 1.0.0 76 | -------------- 77 | * Initial release, includes: 78 | * asset copying and rehoming 79 | * model script processors for groovy, javascript, etc. 80 | * model probing options 81 | * optional material generation 82 | 83 | 84 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jmec' 2 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/AssetReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.io.File; 40 | import java.io.IOException; 41 | import java.nio.file.Path; 42 | import java.net.URL; 43 | 44 | import org.slf4j.*; 45 | 46 | import com.google.common.io.Files; 47 | 48 | import com.jme3.asset.*; 49 | import com.jme3.asset.plugins.FileLocator; 50 | import com.jme3.scene.*; 51 | 52 | import com.simsilica.jmec.gltf.GltfExtrasLoader; 53 | 54 | /** 55 | * Wraps an AssetManager with localized configuration for reading assets 56 | * from a certain directory tree. 57 | * 58 | * @author Paul Speed 59 | */ 60 | public class AssetReader { 61 | 62 | public static final String DESKTOP_ASSET_CONFIG = "/com/jme3/asset/Desktop.cfg"; 63 | 64 | static Logger log = LoggerFactory.getLogger(AssetReader.class); 65 | 66 | private Path root; 67 | private final DesktopAssetManager assets; 68 | 69 | public AssetReader() { 70 | this(new File(".")); 71 | } 72 | 73 | public AssetReader( File assetRoot ) { 74 | this(assetRoot, (URL) null); 75 | } 76 | 77 | public AssetReader( File assetRoot, URL assetConfig ) { 78 | if( assetConfig == null ) { 79 | assetConfig = getClass().getResource(DESKTOP_ASSET_CONFIG); 80 | log.info("Found assetConfig:" + assetConfig); 81 | } 82 | 83 | this.assets = new DesktopAssetManager(assetConfig); 84 | setAssetRoot(assetRoot); 85 | } 86 | 87 | public AssetReader( File assetRoot, DesktopAssetManager assets ) { 88 | this.assets = assets; 89 | setAssetRoot(assetRoot); 90 | } 91 | 92 | public void setAssetRoot( File assetRoot ) { 93 | if (root != null) { 94 | assets.unregisterLocator(root.toString(), FileLocator.class); 95 | } 96 | if (assetRoot == null) { 97 | root = null; 98 | return; 99 | } 100 | 101 | try { 102 | this.root = assetRoot.getCanonicalFile().toPath(); 103 | } catch (java.io.IOException e) { 104 | throw new RuntimeException("Error getting canonical path for:" + assetRoot, e); 105 | } 106 | log.info("Using source asset root:" + root); 107 | 108 | assets.registerLocator(root.toString(), FileLocator.class); 109 | } 110 | 111 | public File getAssetRoot() { 112 | return root != null ? root.toFile() : null; 113 | } 114 | 115 | public DesktopAssetManager getAssetManager() { 116 | return assets; 117 | } 118 | 119 | public Spatial loadModel( File f ) { 120 | if (root == null) { 121 | throw new RuntimeException("Asset root is not set."); 122 | } 123 | 124 | log.debug("loadModel(" + f + ")"); 125 | if( !f.exists() ) { 126 | throw new IllegalArgumentException("Model file does not exist:" + f); 127 | } 128 | 129 | try { 130 | // Make sure the file is completely collapsed into canonical form. 131 | // Note: apparently getAbsoluteFile() is not good enough. 132 | f = f.getCanonicalFile(); 133 | } catch( IOException e ) { 134 | throw new IllegalArgumentException("File cannot be converted to canonical path:" + f, e); 135 | } 136 | // Find the relative path 137 | String path = root.relativize(f.getAbsoluteFile().toPath()).toString(); 138 | 139 | log.info("Loading asset:" + path); 140 | 141 | // Make sure the cache is clear 142 | assets.clearCache(); 143 | 144 | // AssetManager doesn't really give us a better way to resolve types 145 | // so we'll make some assumptions... it helps that we control the 146 | // asset manager ourselves here. 147 | String extension = Files.getFileExtension(f.getName()); 148 | if( "gltf".equalsIgnoreCase(extension) || "glb".equalsIgnoreCase(extension) ) { 149 | // We do special setup for GLTF 150 | return assets.loadModel(GltfExtrasLoader.createModelKey(path)); 151 | } else { 152 | return assets.loadModel(path); 153 | } 154 | } 155 | } 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/AssetWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.io.*; 40 | import java.lang.reflect.Field; 41 | import java.util.*; 42 | 43 | import org.slf4j.*; 44 | 45 | import com.google.common.io.Files; 46 | 47 | import com.jme3.asset.*; 48 | import com.jme3.export.binary.BinaryExporter; 49 | import com.jme3.material.*; 50 | import com.jme3.scene.*; 51 | import com.jme3.material.plugin.export.material.J3MExporter; 52 | 53 | /** 54 | * Writes an asset to a target directory structure, copying or 55 | * saving its dependencies as required. 56 | * 57 | * @author Paul Speed 58 | */ 59 | public class AssetWriter implements ModelProcessor { 60 | static Logger log = LoggerFactory.getLogger(AssetWriter.class); 61 | 62 | private static J3MExporter j3mExporter = new J3MExporter(); 63 | 64 | private File target; 65 | private String assetPath; 66 | 67 | public AssetWriter() { 68 | } 69 | 70 | public void setTarget( File target ) { 71 | this.target = target; 72 | } 73 | 74 | public void setAssetPath( String path ) { 75 | this.assetPath = path; 76 | } 77 | 78 | protected String toTargetPath( AssetKey key ) { 79 | if( assetPath != null ) { 80 | return assetPath + "/" + key.getName(); 81 | } 82 | return key.getName(); 83 | } 84 | 85 | protected String toTargetPath( String path ) { 86 | if( assetPath != null ) { 87 | return assetPath + "/" + path; 88 | } 89 | return path; 90 | } 91 | 92 | @Override 93 | public void apply( ModelInfo info ) { 94 | try { 95 | write(info); 96 | } catch( IOException e ) { 97 | throw new RuntimeException("Error writing model:" + info.getModelName(), e); 98 | } 99 | } 100 | 101 | public void write( ModelInfo info ) throws IOException { 102 | 103 | // Write the real file dependencies first, rehoming their keys as necessary. 104 | for( ModelInfo.Dependency dep : info.getDependencies() ) { 105 | if( dep.getSourceFile() == null ) { 106 | // It's a generated asset 107 | continue; 108 | } 109 | AssetKey key = dep.getKey(); 110 | 111 | String path = toTargetPath(key); 112 | File f = new File(target, path); 113 | f.getParentFile().mkdirs(); 114 | 115 | if( dep.getSourceFile() != null ) { 116 | log.info("Copying:" + dep.getSourceFile() + " to:" + f); 117 | Files.copy(dep.getSourceFile(), f); 118 | } 119 | 120 | // Set the new target to the dependency's key so that when 121 | // we write out the .j3o it will know about the new location. 122 | AssetKey newKey = rehome(path, key); 123 | //log.info("...setting key to:" + newKey); 124 | dep.setKey(newKey); 125 | } 126 | 127 | // Then generate the generated assets after writing the real file 128 | // assets... because the generated assets may need the updated keys from 129 | // the first loop. 130 | for( ModelInfo.Dependency dep : info.getDependencies() ) { 131 | if( dep.getSourceFile() != null ) { 132 | // It's a real file asset 133 | continue; 134 | } 135 | AssetKey key = dep.getKey(); 136 | 137 | String path = toTargetPath(key); 138 | AssetKey newKey = rehome(path, key); 139 | 140 | File f = new File(target, path); 141 | f.getParentFile().mkdirs(); 142 | 143 | // Set the new target to the dependency's key so that when 144 | // we write out the outer .j3o it will know about the new location. 145 | //log.info("...setting key to:" + newKey); 146 | dep.setKey(newKey); 147 | 148 | generateDependency(f, dep); 149 | } 150 | 151 | // Write the j3o 152 | File outFile = new File(target, toTargetPath(info.getModelName() + ".j3o")); 153 | log.info("Writing:" + outFile); 154 | BinaryExporter.getInstance().save(info.getModelRoot(), outFile); 155 | } 156 | 157 | protected void generateDependency( File file, ModelInfo.Dependency dep ) throws IOException { 158 | CloneableSmartAsset asset = dep.getInstances().get(0); // should always be at least one 159 | if( asset instanceof Material ) { 160 | writeJ3m(file, dep, (Material)asset); 161 | } else if( asset instanceof AssetLinkNode ) { 162 | writeLinkedAsset(file, dep, (AssetLinkNode)asset); 163 | } else { 164 | throw new UnsupportedOperationException("Type not supported for generation:" + asset); 165 | } 166 | } 167 | 168 | protected void writeJ3m( File file, ModelInfo.Dependency dep, Material material ) throws IOException { 169 | log.info("Writing material:" + file); 170 | j3mExporter.save(material, file); 171 | } 172 | 173 | protected void writeLinkedAsset( File file, ModelInfo.Dependency dep, AssetLinkNode link ) throws IOException { 174 | log.info("Writing linked asset:" + file + " for key:" + dep.getKey()); 175 | 176 | // See ModelInfo.extractSubmodel() for an overview of the 177 | // shell game we play with AssetLinkNode. 178 | 179 | // We should only have our own AssetLinkNode swap-outs here because a regular 180 | // AssetLinkNode wouldn't normally show up here (as a dependency). But we'll 181 | // do our best to check conditions anyway. 182 | if( link.getChildren().size() != 1 ) { 183 | log.warn("Not writing AssetLinkNode:" + link); 184 | return; 185 | } 186 | 187 | // Our swap-out AssetLinkNodes will only have one child... the dependency. 188 | Spatial child = link.getChildren().get(0); 189 | BinaryExporter.getInstance().save(child, file); 190 | 191 | // Make sure the AssetLinkNode knows about the child's latest rehomed key 192 | link.detachLinkedChildren(); 193 | // Note: detach only removes the spatials, not the keys 194 | link.getAssetLoaderKeys().clear(); 195 | // The API of AssetLinkNode is not the best. 196 | 197 | // By adding only the key, we force runtime loaders to load the external 198 | // asset... so they will be accurate to loading from a file. 199 | link.addLinkedChild((ModelKey)dep.getKey()); 200 | // Except... I cannot seem to properly clear the cache out for runtime 201 | // loaders. So we might as well leave them in the graph. 202 | //link.attachLinkedChild(child, (ModelKey)dep.getKey()); 203 | } 204 | 205 | private static AssetKey rehome( String newPath, AssetKey key ) { 206 | try { 207 | // Let the asset key class do the path logic for us 208 | AssetKey temp = new AssetKey(newPath); 209 | 210 | // But let clone copy the real class and all of its other fields 211 | AssetKey result = key.clone(); 212 | 213 | // Then use reflection to copy the path parts from temp to the new 214 | // key. 215 | // just brute force unoptimized for now. 216 | Field field; 217 | field = AssetKey.class.getDeclaredField("folder"); 218 | field.setAccessible(true); 219 | field.set(result, field.get(temp)); 220 | 221 | field = AssetKey.class.getDeclaredField("name"); 222 | field.setAccessible(true); 223 | field.set(result, field.get(temp)); 224 | 225 | field = AssetKey.class.getDeclaredField("extension"); 226 | field.setAccessible(true); 227 | field.set(result, field.get(temp)); 228 | 229 | return result; 230 | } catch( Exception e ) { 231 | throw new RuntimeException("Error rehoming key:" + key, e); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/BuildInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.io.*; 40 | import java.net.URL; 41 | import java.util.*; 42 | 43 | import org.slf4j.*; 44 | 45 | /** 46 | * Loads build-time information and makes it available to a caller. 47 | * 48 | * @author Paul Speed 49 | */ 50 | public class BuildInfo { 51 | 52 | static Logger log = LoggerFactory.getLogger(BuildInfo.class); 53 | 54 | private static Properties props = new Properties(); 55 | 56 | static { 57 | try { 58 | load("/git.properties"); 59 | } catch( IOException e ) { 60 | log.error("Error reading git.properties", e); 61 | } 62 | try { 63 | load("/jmec.build.date"); 64 | } catch( IOException e ) { 65 | log.error("Error reading jmec.build.date", e); 66 | } 67 | } 68 | 69 | public static String getVersion() { 70 | return get("git.build.version", "1.unknown"); 71 | } 72 | 73 | public static String getBuildTime() { 74 | return get("git.build.time", get("build.date", "Unknown")); 75 | } 76 | 77 | public static String get( String name, String defaultValue ) { 78 | return props.getProperty(name, defaultValue); 79 | } 80 | 81 | public static void load( String classResource ) throws IOException { 82 | URL u = BuildInfo.class.getResource(classResource); 83 | if( u == null ) { 84 | log.warn("No build info at:" + classResource); 85 | } 86 | log.debug("Loading build info from:" + u); 87 | InputStream in = u.openStream(); 88 | try { 89 | props.load(in); 90 | } finally { 91 | in.close(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/Convert.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.io.File; 40 | import java.io.IOException; 41 | import java.net.URL; 42 | import java.util.*; 43 | 44 | import org.slf4j.*; 45 | 46 | import com.jme3.asset.*; 47 | import com.jme3.scene.*; 48 | 49 | 50 | /** 51 | * 52 | * 53 | * @author Paul Speed 54 | */ 55 | public class Convert { 56 | 57 | public static final String[] HEADER = { 58 | " _ __ __ ___ ___ ", 59 | " _ | | | \\/ | | __| / __|", 60 | " | || | | |\\/| | | _| | (__ ", 61 | " \\__/ |_| |_| |___| \\___|", 62 | "", 63 | " JME3 Asset Converter v" + BuildInfo.getVersion() + " build:" + BuildInfo.getBuildTime(), 64 | "" 65 | }; 66 | 67 | public static final String ALL_PROBE_OPTIONS = "btrscpdu"; 68 | 69 | public static final String[] HELP = { 70 | "Usage: jmec [options] [models]", 71 | "", 72 | "Where [models] are a list of JME-compatible model files.", 73 | "", 74 | "Where [options] are:", 75 | " -sourceRoot : specifies the asset root for the models.", 76 | " Model dependency paths will be evaluated relative to this root.", 77 | "", 78 | " -targetRoot : specifies the asset target root for writing", 79 | " converted assets and their dependencies.", 80 | "", 81 | " -targetPath : a path string specifying where to place", 82 | " the copied/converted assets within the targetRoot. Any", 83 | " internal asset keys will be 'rehomed' to this path.", 84 | "", 85 | " -script : a script file that will be run against the model", 86 | " before writing out. Any number of script files can be specified", 87 | " and they will be run in the order specified.", 88 | " Groovy and Javascript are supported 'out of the box' but any ", 89 | " JSR 223 compatible scripting engine should work if on the classpath.", 90 | "", 91 | " -probe [probe options string] : configures the information that the probe", 92 | " will output.", 93 | " [probe options]:", 94 | " A : all options turned on, same as: " + ALL_PROBE_OPTIONS, 95 | " b : show bounding volumes", 96 | " t : show translations", 97 | " r : show rotation", 98 | " s : show scale", 99 | " c : show the list of controls", 100 | " p : show material parameters", 101 | " u : show user-added data", 102 | " d : list asset dependencies", 103 | "", 104 | "Examples:", 105 | "", 106 | ">jmec -sourceRoot C:\\Downloads\\CoolModel -targetRoot assets -targetPath Models/CoolModel C:\\Downloads\\CoolModel\\AwesomeThing.gltf", 107 | "", 108 | " Converts C:\\Downloads\\CoolModel\\AwesomeThing.gltf to a j3o and writes it", 109 | " to ./assets/Models/CoolModel/AwesomeThing.gltf.j3o", 110 | "", 111 | " Any dependent textures, etc. relative to C:\\Downloads\\CoolModel will", 112 | " be copied until the appropriate ./assets/Models/CoolModel/* subdirectory.", 113 | "", 114 | " For example:", 115 | " C:\\Downloads\\CoolModel\\textures\\awesome-sauce.jpg", 116 | " Gets copied to:", 117 | " ./assets/Models/CoolModel/textures/awesome-sauce.jpg" 118 | }; 119 | 120 | static Logger log = LoggerFactory.getLogger(Convert.class); 121 | 122 | private File sourceRoot; 123 | private File targetRoot; 124 | private String targetAssetPath; 125 | private AssetReader assets; 126 | private AssetWriter writer; 127 | private Probe probe = null; 128 | private String probeOptions = null; 129 | private List modelScripts = new ArrayList<>(); 130 | 131 | private List processors = new ArrayList<>(); 132 | 133 | public Convert() { 134 | this(new AssetReader()); 135 | } 136 | 137 | public Convert( AssetReader assets ) { 138 | this.assets = assets; 139 | } 140 | 141 | public AssetReader getAssetReader() { 142 | if( sourceRoot == null ) { 143 | log.warn("No source root specified, using local directory."); 144 | // Set something at least. 145 | setSourceRoot(new File(".")); 146 | } 147 | return assets; 148 | } 149 | 150 | protected AssetWriter getAssetWriter() { 151 | if( writer == null ) { 152 | writer = new AssetWriter(); 153 | processors.add(writer); 154 | } 155 | return writer; 156 | } 157 | 158 | protected Probe getProbe() { 159 | if( probe == null ) { 160 | probe = new Probe(); 161 | processors.add(0, probe); 162 | } 163 | return probe; 164 | } 165 | 166 | public void setSourceRoot( File f ) { 167 | if( !f.exists() ) { 168 | log.error("Source root doesn't exist:" + f); 169 | return; 170 | } 171 | if( !f.isDirectory() ) { 172 | log.error("Source root is not a directory:" + f); 173 | return; 174 | } 175 | this.sourceRoot = f; 176 | getAssetReader().setAssetRoot(f); 177 | } 178 | 179 | public File getSourceRoot() { 180 | return sourceRoot; 181 | } 182 | 183 | public void setTargetRoot( File f ) { 184 | this.targetRoot = f; 185 | getAssetWriter().setTarget(f); 186 | } 187 | 188 | public File getTargetRoot() { 189 | return targetRoot; 190 | } 191 | 192 | public void setTargetAssetPath( String path ) { 193 | this.targetAssetPath = path; 194 | getAssetWriter().setAssetPath(path); 195 | } 196 | 197 | public String getTargetAssetPath() { 198 | return targetAssetPath; 199 | } 200 | 201 | public void setProbeOptions( String options ) { 202 | this.probeOptions = options; 203 | for( char c : options.toCharArray() ) { 204 | switch( c ) { 205 | case 'A': 206 | setProbeOptions(ALL_PROBE_OPTIONS); 207 | break; 208 | case 'b': 209 | getProbe().setShowBounds(true); 210 | break; 211 | case 't': 212 | getProbe().setShowTranslation(true); 213 | break; 214 | case 'r': 215 | getProbe().setShowRotation(true); 216 | break; 217 | case 's': 218 | getProbe().setShowScale(true); 219 | break; 220 | case 'c': 221 | getProbe().setShowControls(true); 222 | break; 223 | case 'p': 224 | getProbe().setShowAllMaterialParameters(true); 225 | break; 226 | case 'd': 227 | getProbe().setShowDependencies(true); 228 | break; 229 | case 'u': 230 | getProbe().setShowUserData(true); 231 | break; 232 | default: 233 | log.warn("Unknown probe option:" + c); 234 | break; 235 | } 236 | } 237 | } 238 | 239 | public String getProbeOptions() { 240 | return probeOptions; 241 | } 242 | 243 | public void addModelScript( String script ) { 244 | addModelScript(new ModelScript(this, script)); 245 | } 246 | 247 | public void addModelScript( ModelScript script ) { 248 | modelScripts.add(script); 249 | addModelProcessor(script); 250 | } 251 | 252 | public List getModelScripts() { 253 | return Collections.unmodifiableList(modelScripts); 254 | } 255 | 256 | public void addModelProcessor( ModelProcessor proc ) { 257 | if( writer == null ) { 258 | // It's fine just to add it directly 259 | processors.add(proc); 260 | } else { 261 | // We need to add them before the asset writer 262 | int index = processors.indexOf(writer); 263 | processors.add(index, proc); 264 | } 265 | } 266 | 267 | public void clearModelScripts() { 268 | // Remove them all from the processors list 269 | processors.removeAll(modelScripts); 270 | // Then clear 271 | modelScripts.clear(); 272 | } 273 | 274 | public ModelInfo convert( File f ) throws IOException { 275 | if( !f.exists() ) { 276 | log.error("File doesn't exist:" + f); 277 | return null; 278 | } 279 | log.info("Convert:" + f); 280 | Spatial s = getAssetReader().loadModel(f); 281 | 282 | ModelInfo info = new ModelInfo(sourceRoot, f.getName(), s); 283 | runProcessors(info); 284 | 285 | return info; 286 | } 287 | 288 | public void runProcessors( ModelInfo info ) { 289 | if( processors.isEmpty() ) { 290 | log.warn("No output configured, probing instead."); 291 | getProbe(); // just let it use defaults 292 | } 293 | log.info("Processing:" + info.getModelName()); 294 | for( ModelProcessor proc : processors ) { 295 | proc.apply(info); 296 | } 297 | } 298 | 299 | public static void printMemInfo() { 300 | long maxMem = Runtime.getRuntime().maxMemory(); 301 | double mem = maxMem / (1024.0 * 1024.0); 302 | String s = String.format("%.2f mb", mem); 303 | log.info("Max memory:" + s); 304 | } 305 | 306 | public static void print( String... lines ) { 307 | for( String l : lines ) { 308 | System.out.println(l); 309 | } 310 | } 311 | 312 | public static void main( String... args ) throws Exception { 313 | 314 | boolean test = true; 315 | 316 | // Forward JUL logging to slf4j 317 | JulLogSetup.initialize(); 318 | 319 | print(HEADER); 320 | 321 | printMemInfo(); 322 | 323 | if( args.length == 0 ) { 324 | print(HELP); 325 | if( !test ) { 326 | return; 327 | } 328 | } 329 | 330 | Convert convert = new Convert(); 331 | for( Iterator it = Arrays.asList(args).iterator(); it.hasNext(); ) { 332 | String arg = it.next(); 333 | if( "-sourceRoot".equals(arg) ) { 334 | convert.setSourceRoot(new File(it.next())); 335 | } else if( "-targetRoot".equals(arg) ) { 336 | convert.setTargetRoot(new File(it.next())); 337 | } else if( "-targetPath".equals(arg) ) { 338 | convert.setTargetAssetPath(it.next()); 339 | } else if( "-script".equals(arg) ) { 340 | convert.addModelScript(it.next()); 341 | } else if( "-probe".equals(arg) ) { 342 | convert.setProbeOptions(it.next()); 343 | } else { 344 | convert.convert(new File(arg)); 345 | } 346 | } 347 | 348 | if( args.length == 0 && test ) { 349 | boolean testConvert = false; 350 | if( testConvert ) { 351 | convert.setSourceRoot(new File("sampleSource3")); 352 | convert.setTargetRoot(new File("sampleTarget")); 353 | convert.setTargetAssetPath("foo"); 354 | convert.setProbeOptions("bd"); 355 | //convert.addModelScript("test-script.groovy"); 356 | //convert.convert(new File("sampleSource/scene.gltf")); 357 | convert.convert(new File("sampleSource3/door.gltf")); 358 | } else { 359 | // Test reloading what we converted 360 | convert.setSourceRoot(new File("test-models")); 361 | convert.setProbeOptions("A"); 362 | convert.convert(new File("test-models/test-model.gltf")); 363 | } 364 | } 365 | } 366 | } 367 | 368 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/JulLogSetup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.util.logging.*; 40 | 41 | import org.slf4j.bridge.SLF4JBridgeHandler; 42 | 43 | 44 | /** 45 | * Does the stuff necessary to reroute JUL to SLF4j because JUL is 46 | * a horrible logging framework and literally anything else is better... 47 | * 48 | * @author Paul Speed 49 | */ 50 | public class JulLogSetup { 51 | public static void initialize() { 52 | SLF4JBridgeHandler.removeHandlersForRootLogger(); 53 | SLF4JBridgeHandler.install(); 54 | Logger.getLogger("").setLevel(Level.FINEST); // Root logger, for example. 55 | // This only works after Java 1.7, I guess. 56 | //Logger.getGlobal().setLevel(Level.FINEST); // Root logger, for example. 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/ModelAssets.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2022, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import com.jme3.asset.*; 40 | import com.jme3.audio.AudioData; 41 | import com.jme3.audio.AudioKey; 42 | import com.jme3.font.BitmapFont; 43 | import com.jme3.material.Material; 44 | import com.jme3.post.FilterPostProcessor; 45 | import com.jme3.scene.Spatial; 46 | import com.jme3.texture.Texture; 47 | 48 | import org.slf4j.*; 49 | 50 | /** 51 | * A model-specific "asset manager" that can load assets from the 52 | * source model's assets and automatically add them as dependencies. 53 | * This presents just the load-related methods from the AssetManager 54 | * interface as well as some convenience methods for common custom 55 | * asset key cases (like texture flipping). 56 | * 57 | * @author Paul Speed 58 | */ 59 | public class ModelAssets { 60 | static Logger log = LoggerFactory.getLogger(ModelAssets.class); 61 | 62 | private ModelInfo model; 63 | private AssetManager assets; 64 | 65 | public ModelAssets( ModelInfo model, AssetManager assets ) { 66 | this.model = model; 67 | this.assets = assets; 68 | } 69 | 70 | public AssetManager getAssetManager() { 71 | return assets; 72 | } 73 | 74 | protected T addDependency( T asset ) { 75 | if( asset instanceof CloneableSmartAsset ) { 76 | model.addDependency((CloneableSmartAsset)asset); 77 | } 78 | return asset; 79 | } 80 | 81 | public T loadAsset( AssetKey key ) { 82 | return addDependency(assets.loadAsset(key)); 83 | } 84 | 85 | public Object loadAsset( String name ) { 86 | return addDependency(assets.loadAsset(name)); 87 | } 88 | 89 | public Texture loadTexture( TextureKey key ) { 90 | return addDependency(assets.loadTexture(key)); 91 | } 92 | 93 | public Texture loadTexture( String name ) { 94 | return addDependency(assets.loadTexture(name)); 95 | } 96 | 97 | public Texture loadTexture( String name, boolean yFlipped ) { 98 | return addDependency(assets.loadTexture(new TextureKey(name, yFlipped))); 99 | } 100 | 101 | public AudioData loadAudio( AudioKey key ) { 102 | return addDependency(assets.loadAudio(key)); 103 | } 104 | 105 | public AudioData loadAudio( String name ) { 106 | return addDependency(assets.loadAudio(name)); 107 | } 108 | 109 | public Spatial loadModel( ModelKey key ) { 110 | return addDependency(assets.loadModel(key)); 111 | } 112 | 113 | public Spatial loadModel( String name ) { 114 | return addDependency(assets.loadModel(name)); 115 | } 116 | 117 | public Material loadMaterial( String name ) { 118 | return addDependency(assets.loadMaterial(name)); 119 | } 120 | 121 | public BitmapFont loadFont( String name ) { 122 | return addDependency(assets.loadFont(name)); 123 | } 124 | 125 | public FilterPostProcessor loadFilter( FilterKey key ) { 126 | return addDependency(assets.loadFilter(key)); 127 | } 128 | 129 | public FilterPostProcessor loadFilter( String name ) { 130 | return addDependency(assets.loadFilter(name)); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/ModelInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.io.*; 40 | import java.util.*; 41 | 42 | import org.slf4j.*; 43 | 44 | import com.jme3.asset.*; 45 | import com.jme3.material.*; 46 | import com.jme3.math.Transform; 47 | import com.jme3.scene.*; 48 | 49 | /** 50 | * Inspected meta-data about a loaded model asset. 51 | * 52 | * @author Paul Speed 53 | */ 54 | public class ModelInfo { 55 | 56 | static Logger log = LoggerFactory.getLogger(ModelInfo.class); 57 | 58 | private File root; 59 | private String name; 60 | private Spatial model; 61 | private Set sharedAssets = new HashSet<>(); 62 | private Map dependencies = new HashMap<>(); 63 | 64 | public ModelInfo( File root, String name, Spatial model ) { 65 | this.root = root; 66 | this.name = name; 67 | this.model = model; 68 | findDependencies(model); 69 | } 70 | 71 | /** 72 | * Returns a collection containing all of the children (and their children) 73 | * that match the specified name. It uses a breadth first traversal 74 | * such that items earlier in the results are higher in the tree. 75 | */ 76 | public List findAll( String name ) { 77 | return findAll(name, Spatial.class); 78 | } 79 | 80 | /** 81 | * Returns a collection containing all of the children (and their children) 82 | * that match the specified name and type. It uses a breadth first traversal 83 | * such that items earlier in the results are higher in the tree. 84 | */ 85 | public List findAll( final String name, final Class type ) { 86 | // Check the type argument because from groovy scripts it's common 87 | // to pass the wrong node type 88 | if( !Spatial.class.isAssignableFrom(type) ) { 89 | throw new IllegalArgumentException("Type is not a Spatial compatible type:" + type); 90 | } 91 | final List results = new ArrayList<>(); 92 | model.breadthFirstTraversal(new SceneGraphVisitor() { 93 | public void visit( Spatial spatial ) { 94 | if( Objects.equals(name, spatial.getName()) && type.isInstance(spatial) ) { 95 | results.add(type.cast(spatial)); 96 | } 97 | } 98 | }); 99 | return results; 100 | } 101 | 102 | /** 103 | * Returns a collection containing all of the children (and their children) 104 | * that match the type. It uses a breadth first traversal 105 | * such that items earlier in the results are higher in the tree. 106 | */ 107 | public List findAll( final Class type ) { 108 | // Check the type argument because from groovy scripts it's common 109 | // to pass the wrong node type 110 | if( !Spatial.class.isAssignableFrom(type) ) { 111 | throw new IllegalArgumentException("Type is not a Spatial compatible type:" + type); 112 | } 113 | final List results = new ArrayList<>(); 114 | model.breadthFirstTraversal(new SceneGraphVisitor() { 115 | public void visit( Spatial spatial ) { 116 | if( type.isInstance(spatial) ) { 117 | results.add(type.cast(spatial)); 118 | } 119 | } 120 | }); 121 | return results; 122 | } 123 | 124 | /** 125 | * Returns the first breadth-first-search result that matches the specified name. 126 | */ 127 | public Spatial findFirst( String name ) { 128 | return findFirst(name, Spatial.class); 129 | } 130 | 131 | /** 132 | * Returns the first breadth-first-search result that matches the specified name and type. 133 | */ 134 | public T findFirst( final String name, final Class type ) { 135 | 136 | // Without a custom traverser, there is no way to early-out anyway... 137 | // so we'll just leverage the other method. 138 | List results = findAll(name, type); 139 | if( results.isEmpty() ) { 140 | return null; 141 | } 142 | return results.get(0); 143 | } 144 | 145 | public Spatial getModelRoot() { 146 | return model; 147 | } 148 | 149 | public void setModelName( String name ) { 150 | this.name = name; 151 | } 152 | 153 | public String getModelName() { 154 | return name; 155 | } 156 | 157 | public Collection getDependencies() { 158 | return dependencies.values(); 159 | } 160 | 161 | public Dependency getDependency( CloneableSmartAsset asset ) { 162 | return dependencies.get(asset); 163 | } 164 | 165 | public void generateMaterial( Material material, String assetName ) { 166 | log.debug("generateMaterial(" + material + ", " + assetName + ")"); 167 | if( !assetName.toLowerCase().endsWith(".j3m") ) { 168 | assetName = assetName + ".j3m"; 169 | } 170 | Dependency dep = addDependency(null, material); 171 | dep.setKey(new MaterialKey(assetName)); 172 | } 173 | 174 | /** 175 | * Extracts the specified model as a separate dependency and replaces 176 | * it with an AssetLinkNode. The model will be saved to the target path 177 | * with the rest of the assets using the specified assetname. The 178 | * created AssetLinkNode is returned to the caller. 179 | */ 180 | public AssetLinkNode extractSubmodel( Spatial submodel, String assetName ) { 181 | log.debug("extractSubmodel(" + submodel + ", " + assetName + ")"); 182 | if( !assetName.toLowerCase().endsWith(".j3o") ) { 183 | assetName = assetName + ".j3o"; 184 | } 185 | 186 | Node parent = submodel.getParent(); 187 | if( parent == null ) { 188 | throw new IllegalArgumentException("Submodel has no parent, only children can be extracted:" + submodel); 189 | } 190 | 191 | 192 | // Overview of the AssetLinkNode shell game. 193 | // -------------------------------------------- 194 | // Because in the case of a submodel replaced with AssetLinkNode, 195 | // we lose some of the coherence between the asset and the key. 196 | // It's not as nice as Material where the object/key are directly 197 | // linked together, ie: the material has its own key. 198 | // 199 | // That's convenient because at the time of generation/extraction, 200 | // the caller doesn't care about the target path and so on. In fact, 201 | // it's still open for runtime reconfiguration. 202 | // 203 | // For AssetLinkNode, we need to be able to replace the key of the 204 | // child later. If we store the child directly as the dependency 205 | // then we have to do some potentially dangerous gymnastics to get the 206 | // original AssetLinkNode back. 207 | // 208 | // Instead, we treat the AssetLinkNode as the dependency and unwrap 209 | // it in the AssetWriter when writing out the dependency. This also 210 | // lets us flip the key, clear the children, etc. 211 | // 212 | // It's just a little weird as compared to the convenience of materials 213 | // or even textures if we choose to do those, too, someday. Those 214 | // already have built-in key-indrection that Spatial doesn't. 215 | // 216 | // A more elegant, but more complicated, alternative would have been 217 | // to have different Dependency implementations... so we could have had 218 | // a special Dependency class that held the AssetLinkNode and its child. 219 | // It could be that is a good solution in some future iteration. 220 | // That approach seemed more invasive when I started this approach but 221 | // in retrospect, it's probably the 'right answer'... consider it if there 222 | // is ever another similar disconnected dependency issue. -pspeed:2020/01/25 223 | 224 | // The key will be rehomed later... right now we can't be 100% sure 225 | // we know where it will go. 226 | ModelKey key = new ModelKey(assetName); 227 | 228 | // In the mean time, we need to swap out the model for 229 | // an AssetLinkNode 230 | AssetLinkNode link = new AssetLinkNode(key); 231 | 232 | // Transfer the submodel's transforms to the link and then clear them. 233 | // Presumably, this submodel could be represented elsewhere in the main model's 234 | // scenegraph and each will want their own local transforms. 235 | link.setLocalTransform(submodel.getLocalTransform()); 236 | submodel.setLocalTransform(new Transform()); 237 | 238 | // Add the submodel as a child to the link so we have it later for 239 | // unwrapping and storage 240 | link.attachLinkedChild(submodel, key); 241 | 242 | // And add the link node to the scene 243 | parent.attachChild(link); 244 | 245 | // Add the dependency for later writing+key resolution 246 | // Note: we add the link itself because we need to do some key 247 | // magic when we save the child. 248 | Dependency dep = addDependency(null, link); 249 | dep.setKey(key); 250 | 251 | return link; 252 | } 253 | 254 | private void findDependencies( Spatial s ) { 255 | log.debug("findDependencies(" + s + ")"); 256 | if( s instanceof Node ) { 257 | Node n = (Node)s; 258 | for( Spatial child : n.getChildren() ) { 259 | findDependencies(child); 260 | } 261 | } else if( s instanceof Geometry ) { 262 | findDependencies(((Geometry)s).getMaterial()); 263 | } 264 | } 265 | 266 | private void findDependencies( Material m ) { 267 | log.debug("findDependencies(" + m + ")"); 268 | if( m.getKey() != null ) { 269 | dependencies.put(m, new Dependency(root, m)); 270 | } 271 | for( MatParam mp : m.getParams() ) { 272 | log.debug("Checking:" + mp); 273 | Object val = mp.getValue(); 274 | if( !(val instanceof CloneableSmartAsset) ) { 275 | continue; 276 | } 277 | CloneableSmartAsset asset = (CloneableSmartAsset)val; 278 | log.debug("material asset:" + asset); 279 | if( asset.getKey() != null ) { 280 | addDependency(root, asset); 281 | } 282 | } 283 | } 284 | 285 | /** 286 | * Marks a key as a shared asset that will not be copied during conversion. 287 | */ 288 | public T markSharedAsset( T asset ) { 289 | log.debug("markSharedAsset(" + asset + ")"); 290 | sharedAssets.add(asset.getKey()); 291 | // Remove any existing dependency tracking we might already have 292 | for( Iterator it = dependencies.keySet().iterator(); it.hasNext(); ) { 293 | CloneableSmartAsset dep = it.next(); 294 | if( Objects.equals(dep.getKey(), asset.getKey()) ) { 295 | log.debug("Removing dependency tracking for:" + dep); 296 | it.remove(); 297 | } 298 | } 299 | return asset; 300 | } 301 | 302 | /** 303 | * Adds a dependency that will be automtically copied to the target 304 | * during conversion. The asset must be resolvable by this models 305 | * asset manager to be copied correctly. 306 | */ 307 | public T addDependency( T asset ) { 308 | addDependency(root, asset); 309 | return asset; 310 | } 311 | 312 | /** 313 | * Adds a dependency that will be automtically copied to the target 314 | * during conversion. The asset must be resolvable by the specified 315 | * root directory. 316 | */ 317 | public T addExternalDependency( File root, T asset ) { 318 | // Note: we require CloneableSmartAsset because it's the only thing that 319 | // provides its own key. If we want to support cases of assets that 320 | // are not CloneableSmartAssets then we could modify Dependency 321 | // to take a raw object and pass down the AssetKey. I don't 322 | // yet have any use-cases today so it will wait. There could 323 | // be other reasons I used CloneableSmartAsset that I just don't 324 | // see at the moment. -pspeed:2022-05-07 325 | addDependency(root, asset); 326 | return asset; 327 | } 328 | 329 | private Dependency addDependency( File root, CloneableSmartAsset asset ) { 330 | log.debug("addDependency(" + root + ", " + asset + ")"); 331 | if( sharedAssets.contains(asset.getKey()) ) { 332 | log.debug("skipping shared asset:" + asset.getKey()); 333 | return null; 334 | } 335 | Dependency result = dependencies.get(asset); 336 | if( result == null ) { 337 | result = new Dependency(root, asset); 338 | dependencies.put(asset, result); 339 | return result; 340 | } 341 | 342 | // Else just add it to the existing 343 | result.instances.add(asset); 344 | return result; 345 | } 346 | 347 | public static class Dependency implements Comparable { 348 | private AssetKey originalKey; 349 | private File file; 350 | private List instances = new ArrayList<>(); 351 | 352 | public Dependency( File root, CloneableSmartAsset asset ) { 353 | instances.add(asset); 354 | this.originalKey = asset.getKey(); 355 | if( asset.getKey() != null ) { 356 | this.file = new File(root, asset.getKey().getName()); 357 | } 358 | } 359 | 360 | public int compareTo( Dependency other ) { 361 | String s1 = originalKey.getName(); 362 | String s2 = other.getKey().getName(); 363 | return s1.compareTo(s2); 364 | } 365 | 366 | public AssetKey getOriginalKey() { 367 | return originalKey; 368 | } 369 | 370 | public void setKey( AssetKey key ) { 371 | for( CloneableSmartAsset asset : instances ) { 372 | asset.setKey(key); 373 | } 374 | } 375 | 376 | public AssetKey getKey() { 377 | return instances.get(0).getKey(); 378 | } 379 | 380 | public File getSourceFile() { 381 | return file; 382 | } 383 | 384 | public List getInstances() { 385 | return instances; 386 | } 387 | 388 | @Override 389 | public String toString() { 390 | return "Dependency[file=" + file + ", originalKey=" + originalKey + "]"; 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/ModelProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | 40 | /** 41 | * Performs post-load operations on a model through its ModelInfo 42 | * wrapper. 43 | * 44 | * @author Paul Speed 45 | */ 46 | public interface ModelProcessor { 47 | 48 | public void apply( ModelInfo model ); 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/ModelScript.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.io.*; 40 | import java.net.URL; 41 | import javax.script.*; 42 | 43 | import com.google.common.base.Charsets; 44 | import com.google.common.io.Files; 45 | import com.google.common.io.Resources; 46 | 47 | import org.slf4j.*; 48 | 49 | /** 50 | * A ModelProcessor that runs a JSR 223 style script against 51 | * the supplied model. 52 | * 53 | * @author Paul Speed 54 | */ 55 | public class ModelScript implements ModelProcessor { 56 | 57 | static Logger log = LoggerFactory.getLogger(ModelScript.class); 58 | 59 | private static ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); 60 | 61 | private Convert convert; 62 | private String scriptName; 63 | private String script; 64 | private ScriptEngine engine; 65 | private CompiledScript compiledScript; 66 | private Bindings bindings; 67 | 68 | public ModelScript( Convert convert, String scriptName ) { 69 | this(convert, scriptName, loadScript(scriptName)); 70 | } 71 | 72 | public ModelScript( Convert convert, String scriptName, String script ) { 73 | this.convert = convert; 74 | this.scriptName = scriptName; 75 | this.script = script; 76 | String ext = Files.getFileExtension(scriptName); 77 | this.engine = scriptEngineManager.getEngineByExtension(ext); 78 | this.bindings = engine.createBindings(); 79 | bindings.put("convert", convert); 80 | bindings.put("assets", convert.getAssetReader().getAssetManager()); 81 | bindings.put("log", LoggerFactory.getLogger("script." + scriptName)); 82 | 83 | log.info("Script engine:" + engine); 84 | if( engine instanceof Compilable ) { 85 | try { 86 | this.compiledScript = ((Compilable)engine).compile(script); 87 | } catch( Exception e ) { 88 | throw new RuntimeException("Error compiling:" + scriptName, e); 89 | } 90 | } 91 | } 92 | 93 | public String getScriptName() { 94 | return scriptName; 95 | } 96 | 97 | /** 98 | * Sets a variable that will be available to the script. 99 | */ 100 | public void setBinding( String name, Object value ) { 101 | bindings.put( name, value ); 102 | } 103 | 104 | /** 105 | * Returns a variable that is available to the scripts. 106 | */ 107 | public Object getBinding( String name ) { 108 | return bindings.get(name); 109 | } 110 | 111 | public static final String loadScript( String scriptName ) { 112 | File f = new File(scriptName); 113 | if( f.exists() ) { 114 | // Load the file 115 | return loadScript(f); 116 | } 117 | // Else check for a class resource 118 | URL u = ModelScript.class.getResource(scriptName); 119 | if( u != null ) { 120 | return loadScript(u); 121 | } 122 | throw new IllegalArgumentException("Unable to load script as file or resource:" + scriptName); 123 | } 124 | 125 | public static final String loadScript( File f ) { 126 | try { 127 | return Files.toString(f, Charsets.UTF_8); 128 | } catch( IOException e ) { 129 | throw new RuntimeException("Error loading file:" + f, e); 130 | } 131 | } 132 | 133 | public static final String loadScript( URL u ) { 134 | try { 135 | return Resources.toString(u, Charsets.UTF_8); 136 | } catch( IOException e ) { 137 | throw new RuntimeException("Error loading resource:" + u, e); 138 | } 139 | } 140 | 141 | @Override 142 | public void apply( ModelInfo model ) { 143 | log.info("Running script:" + scriptName + " against:" + model.getModelName()); 144 | bindings.put("model", model); 145 | bindings.put("assets", new ModelAssets(model, convert.getAssetReader().getAssetManager())); 146 | try { 147 | if( compiledScript != null ) { 148 | compiledScript.eval(bindings); 149 | } else { 150 | engine.eval(script, bindings); 151 | } 152 | } catch( Exception e ) { 153 | throw new RuntimeException("Error running script:" + scriptName + " against:" + model.getModelName(), e); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/Probe.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec; 38 | 39 | import java.util.*; 40 | 41 | import org.slf4j.*; 42 | 43 | import com.jme3.asset.*; 44 | import com.jme3.material.*; 45 | import com.jme3.scene.*; 46 | import com.jme3.scene.control.*; 47 | 48 | /** 49 | * A model processor that logs information about the 50 | * loaded model. 51 | * 52 | * @author Paul Speed 53 | */ 54 | public class Probe implements ModelProcessor { 55 | 56 | static Logger log = LoggerFactory.getLogger(Probe.class); 57 | 58 | private boolean showBounds; 59 | private boolean showTranslation; 60 | private boolean showRotation; 61 | private boolean showScale; 62 | private boolean showControls; 63 | private boolean showAllMaterialParameters; 64 | private boolean showDependencies; 65 | private boolean showUserData; 66 | 67 | public Probe() { 68 | } 69 | 70 | public void setShowDependencies( boolean showDependencies ) { 71 | this.showDependencies = showDependencies; 72 | } 73 | 74 | public boolean getShowDependencies() { 75 | return showDependencies; 76 | } 77 | 78 | public void setShowUserData( boolean showUserData ) { 79 | this.showUserData = showUserData; 80 | } 81 | 82 | public boolean getShowUserData() { 83 | return showUserData; 84 | } 85 | 86 | public void setShowBounds( boolean showBounds ) { 87 | this.showBounds = showBounds; 88 | } 89 | 90 | public boolean getShowBounds() { 91 | return showBounds; 92 | } 93 | 94 | public void setShowTranslation( boolean showTranslation ) { 95 | this.showTranslation = showTranslation; 96 | } 97 | 98 | public boolean getShowTranslation() { 99 | return showTranslation; 100 | } 101 | 102 | public void setShowRotation( boolean showRotation ) { 103 | this.showRotation = showRotation; 104 | } 105 | 106 | public boolean getShowRotation() { 107 | return showRotation; 108 | } 109 | 110 | public void setShowScale( boolean showScale ) { 111 | this.showScale = showScale; 112 | } 113 | 114 | public boolean getShowScale() { 115 | return showScale; 116 | } 117 | 118 | public void setShowControls( boolean showControls ) { 119 | this.showControls = showControls; 120 | } 121 | 122 | public boolean getShowControls() { 123 | return showControls; 124 | } 125 | 126 | public void setShowAllMaterialParameters( boolean showAllMaterialParameters ) { 127 | this.showAllMaterialParameters = showAllMaterialParameters; 128 | } 129 | 130 | public boolean getShowAllMaterialParameters() { 131 | return showAllMaterialParameters; 132 | } 133 | 134 | @Override 135 | public void apply( ModelInfo info ) { 136 | probe("", info.getModelRoot(), info); 137 | if( showDependencies ) { 138 | listDependencies("", info); 139 | } 140 | } 141 | 142 | protected void listDependencies( String indent, ModelInfo info ) { 143 | if( info.getDependencies().isEmpty() ) { 144 | return; 145 | } 146 | log.info(indent + "Asset dependencies:"); 147 | Set deps = new TreeSet<>(info.getDependencies()); 148 | for( ModelInfo.Dependency dep : deps ) { 149 | probe(indent + " ", dep, info); 150 | } 151 | } 152 | 153 | protected void probe( String indent, ModelInfo.Dependency dep, ModelInfo info ) { 154 | StringBuilder sb = new StringBuilder(); 155 | if( dep.getSourceFile() == null ) { 156 | sb.append(dep.getKey()); 157 | } else { 158 | sb.append(dep.getSourceFile()); 159 | } 160 | if( !Objects.equals(dep.getKey().toString(), dep.getOriginalKey().toString()) ) { 161 | sb.append(" -> " + dep.getKey()); 162 | } 163 | if( dep.getInstances().size() > 1 ) { 164 | sb.append(" (x" + dep.getInstances().size() + ")"); 165 | } 166 | log.info(indent + sb); 167 | } 168 | 169 | protected void probe( String indent, Spatial s, ModelInfo info ) { 170 | StringBuilder sb = new StringBuilder(); 171 | if( s.getName() == null ) { 172 | sb.append(s.getClass().getSimpleName() + "()"); 173 | } else { 174 | sb.append(s.getClass().getSimpleName() + "(" + s.getName() + ")"); 175 | } 176 | if( s.getKey() != null ) { 177 | sb.append(" key:" + s.getKey()); 178 | } 179 | log.info(indent + sb); 180 | writeAttributes(indent + " -> ", s, info); 181 | 182 | if( s instanceof Node ) { 183 | Node n = (Node)s; 184 | for( Spatial child : n.getChildren() ) { 185 | probe(indent + " ", child, info); 186 | } 187 | } if( s instanceof Geometry ) { 188 | probe(indent + " ", ((Geometry)s).getMaterial(), info); 189 | } 190 | } 191 | 192 | protected void writeAttributes( String indent, Spatial s, ModelInfo info ) { 193 | if( showBounds ) { 194 | log.info(indent + "worldBounds:" + s.getWorldBound()); 195 | } 196 | if( showTranslation ) { 197 | log.info(indent + "localTranslation:" + s.getLocalTranslation()); 198 | } 199 | if( showRotation ) { 200 | log.info(indent + "localRotation:" + s.getLocalRotation()); 201 | } 202 | if( showScale ) { 203 | log.info(indent + "localScale:" + s.getLocalScale()); 204 | } 205 | if( showUserData ) { 206 | writeUserData(indent + "userData:", s); 207 | } 208 | if( showControls ) { 209 | if( s.getNumControls() > 0 ) { 210 | log.info(indent + "controls:"); 211 | for( int i = 0; i < s.getNumControls(); i++ ) { 212 | Control c = s.getControl(i); 213 | log.info(indent + " [" + i + "]:" + c); 214 | } 215 | } 216 | } 217 | } 218 | 219 | protected void writeUserData( String indent, Spatial s ) { 220 | for( String name : s.getUserDataKeys() ) { 221 | log.info(indent + name + "=" + s.getUserData(name)); 222 | } 223 | } 224 | 225 | protected void probe( String indent, Material m, ModelInfo info ) { 226 | StringBuilder sb = new StringBuilder(); 227 | sb.append(m.toString()); 228 | if( m.getKey() != null ) { 229 | sb.append(" key:" + m.getKey()); 230 | } 231 | log.info(indent + sb); 232 | if( m.getKey() != null ) { 233 | ModelInfo.Dependency dep = info.getDependency(m); 234 | if( dep != null && dep.getSourceFile() != null ) { 235 | log.info(indent + " -> source:" + dep.getSourceFile()); 236 | } 237 | } 238 | if( showAllMaterialParameters ) { 239 | for( MatParam mp : m.getParams() ) { 240 | log.info(indent + " " + mp); 241 | Object val = mp.getValue(); 242 | if( val instanceof CloneableSmartAsset ) { 243 | CloneableSmartAsset asset = (CloneableSmartAsset)val; 244 | ModelInfo.Dependency dep = info.getDependency(asset); 245 | if( dep != null && dep.getSourceFile() != null ) { 246 | log.info(indent + " -> source:" + dep.getSourceFile()); 247 | } 248 | } 249 | } 250 | } 251 | } 252 | 253 | } 254 | 255 | 256 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/gltf/GltfExtrasLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2019, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec.gltf; 38 | 39 | import java.lang.reflect.Array; 40 | import java.util.*; 41 | 42 | import org.slf4j.*; 43 | 44 | import com.google.gson.*; 45 | 46 | import com.jme3.scene.Spatial; 47 | import com.jme3.scene.plugins.gltf.*; 48 | 49 | /** 50 | * Attaches GLTF "extras" data to objects as appropriate. 51 | * 52 | * @author Paul Speed 53 | */ 54 | public class GltfExtrasLoader implements ExtrasLoader { 55 | static Logger log = LoggerFactory.getLogger(GltfExtrasLoader.class); 56 | 57 | public static final GltfExtrasLoader INSTANCE = new GltfExtrasLoader(); 58 | 59 | public GltfExtrasLoader() { 60 | } 61 | 62 | /** 63 | * Utility method to create a ModelKey that is configured to use this 64 | * ExtrasLoader. 65 | */ 66 | public static GltfModelKey createModelKey( String path ) { 67 | GltfModelKey key = new GltfModelKey(path); 68 | key.setExtrasLoader(INSTANCE); 69 | return key; 70 | } 71 | 72 | @Override 73 | public Object handleExtras( GltfLoader loader, String parentName, 74 | JsonElement parent, JsonElement extras, Object input ) { 75 | 76 | log.debug("handleExtras(" + loader + ", " + parentName + ", " + parent + ", " + extras + ", " + input + ")"); 77 | 78 | // Only interested in composite objects 79 | if( !extras.isJsonObject() ) { 80 | log.warn("Skipping extras:" + extras); 81 | return input; 82 | } 83 | JsonObject jo = extras.getAsJsonObject(); 84 | apply(input, jo); 85 | return input; 86 | } 87 | 88 | protected void apply( Object input, JsonObject extras ) { 89 | if( input == null ) { 90 | return; 91 | } 92 | if( input.getClass().isArray() ) { 93 | applyToArray(input, extras); 94 | } else if( input instanceof Spatial ) { 95 | applyToSpatial((Spatial)input, extras); 96 | } else { 97 | log.warn("Unhandled input type:" + input.getClass()); 98 | } 99 | } 100 | 101 | protected void applyToArray( Object array, JsonObject extras ) { 102 | int size = Array.getLength(array); 103 | for( int i = 0; i < size; i++ ) { 104 | Object o = Array.get(array, i); 105 | log.debug("processing array[" + i + "]:" + o); 106 | apply(o, extras); 107 | } 108 | } 109 | 110 | protected void applyToSpatial( Spatial spatial, JsonObject extras ) { 111 | for( Map.Entry el : extras.entrySet() ) { 112 | log.debug(el.toString()); 113 | Object val = toAttribute(el.getValue(), false); 114 | if( log.isDebugEnabled() ) { 115 | log.debug("setUserData(" + el.getKey() + ", " + val + ")"); 116 | } 117 | spatial.setUserData(el.getKey(), val); 118 | } 119 | } 120 | 121 | protected Object toAttribute( JsonElement el, boolean nested ) { 122 | if( el.isJsonObject() ) { 123 | return toAttribute(el.getAsJsonObject(), nested); 124 | } else if( el.isJsonArray() ) { 125 | return toAttribute(el.getAsJsonArray(), nested); 126 | } else if( el.isJsonPrimitive() ) { 127 | return toAttribute(el.getAsJsonPrimitive(), nested); 128 | } else if( el.isJsonNull() ) { 129 | return null; 130 | } 131 | log.warn("Unhandled extras element:" + el); 132 | return null; 133 | } 134 | 135 | protected Object toAttribute( JsonObject jo, boolean nested ) { 136 | Map result = new HashMap<>(); 137 | for( Map.Entry el : jo.entrySet() ) { 138 | result.put(el.getKey(), toAttribute(el.getValue(), true)); 139 | } 140 | return result; 141 | } 142 | 143 | protected Object toAttribute( JsonArray ja, boolean nested ) { 144 | List result = new ArrayList<>(); 145 | for( JsonElement el : ja ) { 146 | result.add(toAttribute(el, true)); 147 | } 148 | return result; 149 | } 150 | 151 | protected Object toAttribute( JsonPrimitive jp, boolean nested ) { 152 | if( jp.isBoolean() ) { 153 | return jp.getAsBoolean(); 154 | } else if( jp.isNumber() ) { 155 | // JME doesn't save Maps properly and treats them as two 156 | // separate Lists... and it doesn't like saving Doubles 157 | // in lists so we'll just return strings in the case where 158 | // the value would end up in a map. If users someday really 159 | // need properly typed map values and JME map storage hasn't 160 | // been fixed then perhaps we give the users the option of 161 | // flattening the nested properties into dot notation, ie: 162 | // all directly on UserData with no Map children. 163 | if( nested ) { 164 | return jp.getAsString(); 165 | } 166 | Number num = jp.getAsNumber(); 167 | // JME doesn't like to save GSON's LazilyParsedNumber so we'll 168 | // convert it into a real number. I don't think we can reliably 169 | // guess what type of number the user intended. It would take 170 | // some expirimentation to determine if things like 0.0 are preserved 171 | // during export or just get exported as 0. 172 | // Rather than randomly flip-flop between number types depending 173 | // on the inclusion (or not) of a decimal point, we'll just always 174 | // return Double. 175 | return num.doubleValue(); 176 | } else if( jp.isString() ) { 177 | return jp.getAsString(); 178 | } 179 | log.warn("Unhandled primitive:" + jp); 180 | return null; 181 | } 182 | } 183 | 184 | 185 | -------------------------------------------------------------------------------- /src/main/java/com/simsilica/jmec/view/JmecNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id$ 3 | * 4 | * Copyright (c) 2020, Simsilica, LLC 5 | * All rights reserved. 6 | * 7 | * Redistribution and use in source and binary forms, with or without 8 | * modification, are permitted provided that the following conditions 9 | * are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 14 | * 2. Redistributions in binary form must reproduce the above copyright 15 | * notice, this list of conditions and the following disclaimer in 16 | * the documentation and/or other materials provided with the 17 | * distribution. 18 | * 19 | * 3. Neither the name of the copyright holder nor the names of its 20 | * contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 28 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | package com.simsilica.jmec.view; 38 | 39 | import java.io.*; 40 | 41 | import org.slf4j.*; 42 | 43 | import com.google.common.io.Files; 44 | 45 | import com.jme3.asset.AssetManager; 46 | import com.jme3.asset.ModelKey; 47 | import com.jme3.scene.*; 48 | import com.jme3.util.SafeArrayList; 49 | 50 | import com.simsilica.jmec.*; 51 | 52 | /** 53 | * Watches a model plus optional set of scripts and reloads/reconverts 54 | * the model when changes in the files are detected. 55 | * 56 | * @author Paul Speed 57 | */ 58 | public class JmecNode extends Node { 59 | static Logger log = LoggerFactory.getLogger(JmecNode.class); 60 | 61 | private Convert convert = new Convert(); 62 | private ModelInfo model; 63 | private VersionedFile modelFile; 64 | private SafeArrayList scripts = new SafeArrayList<>(VersionedScript.class); 65 | private AssetManager assets; 66 | 67 | public JmecNode() { 68 | this((File)null); 69 | } 70 | 71 | public JmecNode( String modelFile ) { 72 | this(new File(modelFile)); 73 | } 74 | 75 | public JmecNode( File modelFile ) { 76 | setRequiresUpdates(true); 77 | if( modelFile != null ) { 78 | this.modelFile = new VersionedFile(modelFile); 79 | setName(modelFile.getName()); 80 | } 81 | 82 | // Set some defaults 83 | convert.setTargetRoot(new File("assets")); 84 | convert.setTargetAssetPath("Models/" + Files.getNameWithoutExtension(modelFile.getName())); 85 | } 86 | 87 | public void setModelFile( File modelFile ) { 88 | if( modelFile == null ) { 89 | this.modelFile = null; 90 | return; 91 | } 92 | this.modelFile = new VersionedFile(modelFile); 93 | if( getName() == null ) { 94 | setName(modelFile.getName()); 95 | } 96 | } 97 | 98 | public File getModelFile() { 99 | return modelFile != null ? modelFile.getFile() : null; 100 | } 101 | 102 | /** 103 | * Returns the JME Convert object to allow configuring 104 | * additional custom settings if required. 105 | */ 106 | public Convert getConvert() { 107 | return convert; 108 | } 109 | 110 | public void addModelScript( File f ) { 111 | scripts.add(new VersionedScript(f)); 112 | } 113 | 114 | /** 115 | * Sets an asset manager that can be used to resolve AssetLinkNodes. 116 | */ 117 | public void setAssetManager( AssetManager assets ) { 118 | this.assets = assets; 119 | } 120 | 121 | public AssetManager getAssetManager() { 122 | return assets; 123 | } 124 | 125 | @Override 126 | public void updateLogicalState( float tpf ) { 127 | 128 | if( updateDependencies() ) { 129 | // Remove the old model 130 | if( model != null && model.getModelRoot() != null ) { 131 | model.getModelRoot().removeFromParent(); 132 | } 133 | 134 | Spatial child = loadModel(); 135 | if( child != null ) { 136 | attachChild(child); 137 | } 138 | } 139 | 140 | super.updateLogicalState(tpf); 141 | } 142 | 143 | protected Spatial loadModel() { 144 | if( modelFile == null ) { 145 | throw new RuntimeException("No model file has been set"); 146 | } 147 | 148 | File f = modelFile.getFile(); 149 | if( !f.exists() ) { 150 | throw new RuntimeException("Model does not exist:" + f); 151 | } 152 | // Load the model 153 | if( convert.getSourceRoot() == null ) { 154 | convert.setSourceRoot(f.getParentFile()); 155 | } 156 | 157 | if( !scripts.isEmpty() ) { 158 | // Make sure the converter has the latest scripts 159 | convert.clearModelScripts(); 160 | for( VersionedScript script : scripts.getArray() ) { 161 | try { 162 | convert.addModelScript(script.getScript()); 163 | } catch( RuntimeException e ) { 164 | log.error("Error compiling script:" + script.getScript().getScriptName(), e.getCause()); 165 | } 166 | } 167 | } 168 | 169 | try { 170 | model = convert.convert(f); 171 | 172 | // Clear the cache for any linked dependencies 173 | // This should cover AssetLinkNodes as well as any generated materials 174 | // _they_ may try to load. 175 | for( ModelInfo.Dependency dep : model.getDependencies() ) { 176 | log.info("Clear cached dependency for:" + dep.getKey()); 177 | if( !deleteFromCache(dep) ) { 178 | // This can and will happen quite normally... either the first 179 | // time we load or if there are more than one reference to the 180 | // same dependency. No big deal. 181 | //log.warn("Cached asset was not cleared:" + dep.getKey()); 182 | } 183 | 184 | // Note: alternately, if we were not concerned about checking 185 | // loading, we could directly register the already loaded 186 | // child in the dependency object. 187 | } 188 | 189 | // Since we imported it and converted it in RAM, the AssetLinkNodes 190 | // would not have been resolved. 191 | model.getModelRoot().depthFirstTraversal(new SceneGraphVisitorAdapter() { 192 | public void visit( Node node ) { 193 | if( node instanceof AssetLinkNode ) { 194 | if( assets == null ) { 195 | log.error("No AssetManager specified, cannot load asset link:" + node); 196 | } else { 197 | log.info("Loading linked assets for:" + node); 198 | AssetLinkNode link = (AssetLinkNode)node; 199 | link.attachLinkedChildren(assets); 200 | } 201 | } 202 | } 203 | }); 204 | 205 | } catch( IOException e ) { 206 | log.error("Cannot load:" + f, e); 207 | } catch( RuntimeException e ) { 208 | // Figure out what kind of error 209 | if( e.getCause() instanceof javax.script.ScriptException ) { 210 | log.error("Script error", e.getCause()); 211 | } else { 212 | throw e; 213 | } 214 | } 215 | 216 | return model == null ? null : model.getModelRoot(); 217 | } 218 | 219 | @SuppressWarnings("unchecked") 220 | protected boolean deleteFromCache( ModelInfo.Dependency dep ) { 221 | return assets.deleteFromCache(dep.getKey()); 222 | } 223 | 224 | protected boolean updateDependencies() { 225 | boolean changed = false; 226 | if( modelFile != null && modelFile.update() ) { 227 | changed = true; 228 | } 229 | for( VersionedScript f : scripts.getArray() ) { 230 | if( f.update() ) { 231 | changed = true; 232 | } 233 | } 234 | return changed; 235 | } 236 | 237 | protected static class VersionedFile { 238 | File file; 239 | long lastVersion; 240 | 241 | public VersionedFile( File file ) { 242 | this.file = file; 243 | } 244 | 245 | public File getFile() { 246 | return file; 247 | } 248 | 249 | public boolean update() { 250 | long time = file.lastModified(); 251 | if( lastVersion == time ) { 252 | return false; 253 | } 254 | lastVersion = time; 255 | return true; 256 | } 257 | } 258 | 259 | protected class VersionedScript { 260 | File file; 261 | ModelScript script; 262 | long lastVersion; 263 | 264 | public VersionedScript( File file ) { 265 | this.file = file; 266 | } 267 | 268 | public File getFile() { 269 | return file; 270 | } 271 | 272 | public ModelScript getScript() { 273 | if( script == null ) { 274 | String text = ModelScript.loadScript(file); 275 | script = new ModelScript(convert, file.getName(), text); 276 | } 277 | return script; 278 | } 279 | 280 | public boolean update() { 281 | long time = file.lastModified(); 282 | if( lastVersion == time ) { 283 | return false; 284 | } 285 | lastVersion = time; 286 | script = null; 287 | return true; 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/main/resources/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Simsilica, LLC 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{ABSOLUTE} %t %-5p [%c] %m%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------