├── .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