├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── assembly.sbt ├── assets ├── Interface │ ├── border.png │ └── inner.png └── Textures │ ├── Cool2WarmBar.png │ └── heatmap.png ├── bin └── sbt ├── build.sbt ├── config ├── batch-render-example.conf ├── screenshots-turntable.conf ├── screenshots.conf └── viewer.conf ├── project ├── build.properties └── plugins.sbt ├── scripts ├── genViews.sh ├── vars.sh └── viewer.sh └── src └── main ├── java ├── com │ └── jme3 │ │ ├── asset │ │ └── AssetKey.java │ │ └── math │ │ └── Transform.java ├── edu │ └── stanford │ │ └── graphics │ │ └── shapenet │ │ ├── colors │ │ └── HSLColor.java │ │ ├── gui │ │ ├── MeshTreePanel.java │ │ ├── SceneTreePanel.java │ │ ├── TreeDemo.java │ │ ├── TreeNodeInfo.java │ │ └── TreePanel.java │ │ ├── jme3 │ │ ├── app │ │ │ └── ModelInfoAppState.java │ │ ├── asset │ │ │ ├── AssetInfoFile.java │ │ │ ├── ByteArrayAssetInfo.java │ │ │ ├── CachedUrlAssetInfo.java │ │ │ ├── CompressedAssetKey.java │ │ │ ├── EnhancedAssetKey.java │ │ │ ├── EnhancedModelKey.java │ │ │ ├── MemBytesLocator.java │ │ │ ├── MyFileLocator.java │ │ │ ├── MyUrlLocator.java │ │ │ ├── UncompressedAssetInfo.java │ │ │ ├── ZipAssetKey.java │ │ │ └── ZipEntryAssetInfo.java │ │ ├── loaders │ │ │ └── UTF8Decoder.java │ │ └── plugins │ │ │ ├── KMZLoader.java │ │ │ ├── MTLLoader.java │ │ │ ├── OBJLoader.java │ │ │ └── PLYLoader.java │ │ └── util │ │ ├── ImageWriter.java │ │ └── WebCacheUtils.java └── jme3dae │ ├── ColladaDocumentFactory.java │ ├── ColladaInfo.java │ ├── ColladaLoader.java │ ├── DAELoader.java │ ├── DAENode.java │ ├── ExAnimTest.java │ ├── FXEnhancerInfo.java │ ├── Main.java │ ├── ProgressListener.java │ ├── ScratchAnimationTest.java │ ├── TestNoApp.java │ ├── collada14 │ ├── ChannelTarget.java │ ├── ColladaDocumentV14.java │ ├── ColladaSpec141.java │ ├── package-info.java │ └── transformers │ │ ├── ColorRGBATransformer.java │ │ ├── GeometryTransformer.java │ │ ├── InputShared.java │ │ ├── InputSharedTransformer.java │ │ ├── InstanceControllerTransformer.java │ │ ├── LibraryImagesTransformer.java │ │ ├── LibraryMaterialsTransformer.java │ │ ├── MeshTransformer.java │ │ ├── PolygonData.java │ │ ├── PolygonsTransformer.java │ │ ├── PolylistTransformer.java │ │ ├── SceneTransformer.java │ │ ├── SemanticTransformer.java │ │ ├── TextureElementTransformer.java │ │ ├── TransformationElementTransformer.java │ │ ├── TrianglesTransformer.java │ │ ├── Vector3fTransformer.java │ │ └── package-info.java │ ├── materials │ ├── AbstractMaterialGenerator.java │ ├── BlinnMaterialGenerator.java │ ├── ColladaBlinn.j3md │ ├── ColladaConstant.j3md │ ├── ColladaLambert.j3md │ ├── ColladaPhong.j3md │ ├── ConstantMaterialGenerator.java │ ├── FXBumpMaterialGenerator.java │ ├── LambertMaterialGenerator.java │ ├── PhongMaterialGenerator.java │ ├── collada_blinn.frag │ ├── collada_blinn.vert │ ├── collada_constant.frag │ ├── collada_constant.vert │ ├── collada_lambert.frag │ ├── collada_lambert.vert │ ├── collada_phong.frag │ ├── collada_phong.vert │ └── package-info.java │ ├── package-info.java │ ├── transformers │ ├── ValueTransformer.java │ └── package-info.java │ └── utilities │ ├── ArrayTransformer.java │ ├── Bindings.java │ ├── BooleanListTransformer.java │ ├── BooleanTransformer.java │ ├── Conditions.java │ ├── ExplicitAnimationNode.java │ ├── FileType.java │ ├── FileTypeFinder.java │ ├── FloatListTransformer.java │ ├── FloatTransformer.java │ ├── HexSequenceTransformer.java │ ├── IndexExtractor.java │ ├── IntegerListTransformer.java │ ├── IntegerTransformer.java │ ├── MEMAssetLocator.java │ ├── Matrix4fTransformer.java │ ├── MatrixTransformer.java │ ├── MatrixUtils.java │ ├── MeasuringUnit.java │ ├── NameListTransformer.java │ ├── NormalGenerator.java │ ├── NormalMapFilter.java │ ├── PlainTextTransformer.java │ ├── PolygonArrayTransformer.java │ ├── SpatialTreeIterable.java │ ├── TextureBaseList.java │ ├── Todo.java │ ├── TransformerPack.java │ ├── Tuple2.java │ ├── Tuple3.java │ ├── UpAxis.java │ ├── Vertex.java │ ├── VertexSkinningData.java │ └── package-info.java ├── resources ├── edu │ └── stanford │ │ └── graphics │ │ └── shapenet │ │ └── jme3 │ │ └── viewer │ │ └── Viewer.xml └── logback.xml └── scala └── edu └── stanford └── graphics └── shapenet ├── Constants.scala ├── UserDataConstants.scala ├── apps └── GenerateViewpoints.scala ├── colors ├── ColorPalette.scala └── package.scala ├── common ├── Attributes.scala ├── CameraInfo.scala ├── CategoryTaxonomy.scala ├── CategoryUtils.scala ├── FullId.scala ├── GeometricScene.scala ├── Material.scala ├── Model.scala ├── ModelInfo.scala ├── Parts.scala ├── Scene.scala ├── SceneSelection.scala ├── SceneState.scala └── Transform.scala ├── data ├── DataManager.scala ├── ModelsDb.scala └── SolrQuerier.scala ├── jme3 ├── Jme.scala ├── JmeAssetCreator.scala ├── JmeConfig.scala ├── geom │ └── BoundingBox.scala ├── loaders │ ├── AssetLoader.scala │ ├── LoadOptions.scala │ └── UTF8Loader.scala ├── package.scala └── viewer │ ├── CameraPositionOptimizer.scala │ ├── CommandConsole.scala │ ├── DebugVisualizer.scala │ ├── FalseColoredScene.scala │ ├── GenerateImagesAppState.scala │ ├── ImageDisplaySceneProcessor.java │ ├── OffscreenAnalyzer.scala │ ├── OffscreenView.scala │ ├── ProgressBarControl.scala │ ├── RenderTask.scala │ ├── SceneImagesGenerator.scala │ ├── SceneStatsSceneProcessor.scala │ ├── ScreenshotAppState.java │ ├── SimpleRenderer.scala │ ├── Viewer.scala │ ├── ViewerConfig.scala │ └── ViewerController.scala └── util ├── BatchSampler.scala ├── CSVFile.scala ├── ConfigHelper.scala ├── ConversionUtils.scala ├── IOUtils.scala ├── LRUCache.scala ├── Logger.scala ├── SolrUtils.scala ├── StringUtils.scala ├── Threads.scala ├── UserData.scala └── WebUtils.scala /.gitignore: -------------------------------------------------------------------------------- 1 | project/target/ 2 | target/ 3 | 4 | # Compiled source # 5 | ################### 6 | *.com 7 | *.class 8 | *.dll 9 | *.exe 10 | *.o 11 | *.so 12 | *.pyc 13 | 14 | # Packages # 15 | ############ 16 | # it's better to unpack these files and commit the raw source 17 | # git has its own built in compression methods 18 | *.7z 19 | *.dmg 20 | *.iso 21 | *.jar 22 | *.rar 23 | *.tar 24 | *.zip 25 | 26 | # Logs and databases # 27 | ###################### 28 | *.log 29 | #*.sql 30 | *.sqlite 31 | 32 | # OS generated files # 33 | ###################### 34 | .DS_Store 35 | .DS_Store? 36 | ._* 37 | .Spotlight-V100 38 | .Trashes 39 | Icon? 40 | ehthumbs.db 41 | Thumbs.db 42 | 43 | # built application files 44 | *.apk 45 | *.ap_ 46 | 47 | # files for the dex VM 48 | *.dex 49 | 50 | # Java class files 51 | *.class 52 | 53 | # generated files 54 | bin/ 55 | gen/ 56 | 57 | # Local configuration file (sdk path, etc) 58 | local.properties 59 | 60 | # Eclipse project files 61 | .classpath 62 | .project 63 | 64 | # Latex 65 | *.acn 66 | *.acr 67 | *.alg 68 | *.aux 69 | *.bbl 70 | *.blg 71 | *.dvi 72 | *.fdb_latexmk 73 | *.glg 74 | *.glo 75 | *.gls 76 | *.idx 77 | *.ilg 78 | *.ind 79 | *.ist 80 | *.lof 81 | *.log 82 | *.lot 83 | *.maf 84 | *.mtc 85 | *.mtc0 86 | *.nav 87 | *.nlo 88 | *.out 89 | *.pdfsync 90 | *.ps 91 | *.snm 92 | *.synctex.gz 93 | *.toc 94 | *.vrb 95 | *.xdy 96 | 97 | *.pydevproject 98 | .project 99 | .metadata 100 | bin/** 101 | tmp/** 102 | tmp/**/* 103 | *.tmp 104 | *.bak 105 | *.swp 106 | *~.nib 107 | local.properties 108 | .classpath 109 | .settings/ 110 | .loadpath 111 | 112 | # External tool builders 113 | .externalToolBuilders/ 114 | 115 | # Locally stored "Eclipse launch configurations" 116 | *.launch 117 | 118 | # CDT-specific 119 | .cproject 120 | 121 | # PDT-specific 122 | .buildpath 123 | 124 | # Emacs specific 125 | *~ 126 | \#*\# 127 | /.emacs.desktop 128 | /.emacs.desktop.lock 129 | .elc 130 | auto-save-list 131 | tramp 132 | .\#* 133 | 134 | # Org-mode 135 | .org-id-locations 136 | *_archive 137 | 138 | # IntelliJ 139 | *.iml 140 | *.ipr 141 | *.iws 142 | .idea/ 143 | 144 | # Linux 145 | .* 146 | !.gitignore 147 | *~ 148 | 149 | .svn/ 150 | 151 | ## Ignore Visual Studio temporary files, build results, and 152 | ## files generated by popular Visual Studio add-ons. 153 | 154 | # User-specific files 155 | *.suo 156 | *.user 157 | *.sln.docstates 158 | 159 | # Build results 160 | 161 | [Dd]ebug*/ 162 | [Rr]elease/ 163 | 164 | build/ 165 | 166 | 167 | [Tt]est[Rr]esult 168 | [Bb]uild[Ll]og.* 169 | 170 | *_i.c 171 | *_p.c 172 | *.ilk 173 | *.meta 174 | *.pch 175 | *.pdb 176 | *.pgc 177 | *.pgd 178 | *.rsp 179 | *.sbr 180 | *.tlb 181 | *.tli 182 | *.tlh 183 | *.tmp 184 | *.vspscc 185 | *.vssscc 186 | .builds 187 | 188 | *.pidb 189 | 190 | *.log 191 | *.scc 192 | # Visual C++ cache files 193 | ipch/ 194 | *.aps 195 | *.ncb 196 | *.opensdf 197 | *.sdf 198 | 199 | # Visual Studio profiler 200 | *.psess 201 | *.vsp 202 | 203 | # Guidance Automation Toolkit 204 | *.gpState 205 | 206 | # ReSharper is a .NET coding add-in 207 | _ReSharper*/ 208 | 209 | *.[Rr]e[Ss]harper 210 | 211 | # NCrunch 212 | *.ncrunch* 213 | .*crunch*.local.xml 214 | 215 | # Installshield output folder 216 | [Ee]xpress 217 | 218 | # DocProject is a documentation generator add-in 219 | DocProject/buildhelp/ 220 | DocProject/Help/*.HxT 221 | DocProject/Help/*.HxC 222 | DocProject/Help/*.hhc 223 | DocProject/Help/*.hhk 224 | DocProject/Help/*.hhp 225 | DocProject/Help/Html2 226 | DocProject/Help/html 227 | 228 | # Click-Once directory 229 | publish 230 | 231 | # Publish Web Output 232 | *.Publish.xml 233 | 234 | # Others 235 | [Bb]in 236 | [Oo]bj 237 | sql 238 | TestResults 239 | [Tt]est[Rr]esult* 240 | *.Cache 241 | ClientBin 242 | [Ss]tyle[Cc]op.* 243 | ~$* 244 | *.dbmdl 245 | 246 | *.[Pp]ublish.xml 247 | 248 | Generated_Code #added for RIA/Silverlight projects 249 | 250 | # Backup & report files from converting an old project file to a newer 251 | # Visual Studio version. Backup files are not needed, because we have git ;-) 252 | _UpgradeReport_Files/ 253 | Backup*/ 254 | UpgradeLog*.XML 255 | 256 | # NuGet 257 | packages/ 258 | 259 | # vim 260 | .*.sw[a-z] 261 | *.un~ 262 | Session.vim 263 | .netrwhist 264 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | jdk: oraclejdk8 3 | sudo: false 4 | scala: 2.11.7 5 | env: BUILD="base" DATA_DIR=$HOME/test/data WORK_DIR=$HOME/work SHAPENET_VIEWER_DIR=$TRAVIS_BUILD_DIR 6 | cache: 7 | directories: 8 | - $HOME/.m2 9 | - $HOME/.ivy2 10 | - $HOME/.sbt 11 | - $HOME/work/cache 12 | before_install: umask 0022 13 | script: 14 | - make 15 | - sbt test 16 | # Tricks to avoid unnecessary cache updates 17 | - find $HOME/.sbt -name "*.lock" | xargs rm 18 | - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ShapeNet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SBT=bin/sbt 2 | 3 | # check if on jenkins and setup some env variables 4 | ifdef WORKSPACE 5 | export DATA_DIR=$(WORKSPACE)/test/data 6 | export WORK_DIR=$(WORKSPACE)/work 7 | $(info Setting DATA_DIR to $(DATA_DIR)) 8 | $(info Setting WORK_DIR to $(WORK_DIR)) 9 | endif 10 | 11 | .PHONY: default all sbt compile test clean package assembly deps 12 | 13 | default: package assembly 14 | 15 | all: package assembly deps 16 | 17 | sbt: bin/sbt-launch.jar 18 | 19 | bin/sbt-launch.jar: 20 | wget "http://repo.typesafe.com/typesafe/ivy-releases/org.scala-sbt/sbt-launch/0.13.7/sbt-launch.jar" -P bin 21 | 22 | compile: sbt 23 | $(SBT) compile 24 | 25 | test: sbt 26 | $(SBT) test 27 | 28 | clean: sbt 29 | $(SBT) clean 30 | 31 | package: sbt 32 | $(SBT) package 33 | 34 | assembly: sbt 35 | $(SBT) assembly 36 | 37 | deps: sbt 38 | $(SBT) assemblyPackageDependency -------------------------------------------------------------------------------- /assembly.sbt: -------------------------------------------------------------------------------- 1 | test in assembly := {} 2 | 3 | mainClass in assembly := Some("edu.stanford.graphics.shapenet.jme3.viewer.Viewer") 4 | 5 | // This is a nasty hack. 6 | mergeStrategy in assembly <<= (mergeStrategy in assembly) { (old) => 7 | { 8 | case x => { 9 | val oldstrat = old(x) 10 | if (oldstrat == MergeStrategy.deduplicate) MergeStrategy.first 11 | else oldstrat 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /assets/Interface/border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShapeNet/shapenet-viewer/e33d3e9770b456a6cc361328383c13b52ff8f34b/assets/Interface/border.png -------------------------------------------------------------------------------- /assets/Interface/inner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShapeNet/shapenet-viewer/e33d3e9770b456a6cc361328383c13b52ff8f34b/assets/Interface/inner.png -------------------------------------------------------------------------------- /assets/Textures/Cool2WarmBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShapeNet/shapenet-viewer/e33d3e9770b456a6cc361328383c13b52ff8f34b/assets/Textures/Cool2WarmBar.png -------------------------------------------------------------------------------- /assets/Textures/heatmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShapeNet/shapenet-viewer/e33d3e9770b456a6cc361328383c13b52ff8f34b/assets/Textures/heatmap.png -------------------------------------------------------------------------------- /bin/sbt: -------------------------------------------------------------------------------- 1 | SBT_OPTS="-Xms512M -Xmx2048M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M" 2 | java $SBT_OPTS -jar `dirname $0`/sbt-launch.jar "$@" -------------------------------------------------------------------------------- /config/batch-render-example.conf: -------------------------------------------------------------------------------- 1 | viewer.addFloor = false 2 | viewer.useRadialFloor = true 3 | viewer.useOutline = false 4 | viewer.useAmbientOcclusion = false 5 | viewer.modelDistanceScale = 1.2 6 | viewer.cacheWebFiles = false 7 | viewer.modelCacheSize = 2 8 | viewer.width = 640 9 | viewer.height = 480 10 | viewer.showSettings = false 11 | viewer.useNestedOutputDir = false # whether to save images to subdirectories with first five id characters split out 12 | viewer.nImagesPerModel = 8 # how many equally-spaced turntable positions (increments of the camera azimuth angle) to render 13 | viewer.cameraAngleFromHorizontal = 45 # angle of camera from horizontal for turntable screenshots 14 | viewer.includeCanonicalViews = false # whether to first render front/back/top/bottom/left/right views 15 | #viewer.offscreen = true # If the viewer should be display or run in a batch mode (offscreen) 16 | 17 | # load specific models by id, show meshes and then render to $WORK_DIR/screenshots/models 18 | # note that any command available in the interactive viewer should work here as well 19 | viewer.commands = [ 20 | "load model 3dw.162ed8d0d989f3acc1ccec171a275967", 21 | "show meshes", 22 | "save model screenshots", 23 | "set cameraAngleFromHorizontal 0", 24 | "load model 3dw.38aa6c6632f64ad5fdedf0d8aa5213c", 25 | "show meshes", 26 | "save model screenshots" 27 | ] 28 | -------------------------------------------------------------------------------- /config/screenshots-turntable.conf: -------------------------------------------------------------------------------- 1 | viewer.addFloor = false 2 | viewer.useRadialFloor = true 3 | viewer.useOutline = false 4 | viewer.useAmbientOcclusion = false 5 | viewer.modelDistanceScale = 3 6 | viewer.cacheWebFiles = false 7 | viewer.modelCacheSize = 2 8 | viewer.width = 640 9 | viewer.height = 480 10 | viewer.showSettings = false 11 | viewer.useNestedOutputDir = true 12 | viewer.includeCanonicalViews = false 13 | viewer.cameraPositionStrategy = distance_to_centroid 14 | -------------------------------------------------------------------------------- /config/screenshots.conf: -------------------------------------------------------------------------------- 1 | viewer.addFloor = false 2 | viewer.useRadialFloor = true 3 | viewer.useOutline = true 4 | viewer.useAmbientOcclusion = false 5 | viewer.modelDistanceScale = 1.2 6 | viewer.cacheWebFiles = false 7 | viewer.modelCacheSize = 2 8 | viewer.width = 640 9 | viewer.height = 480 10 | viewer.showSettings = false 11 | viewer.useNestedOutputDir = true 12 | -------------------------------------------------------------------------------- /config/viewer.conf: -------------------------------------------------------------------------------- 1 | # List of all viewer options 2 | # To use, run viewer with -conf viewer.conf 3 | 4 | # Viewer commands 5 | # If the viewer should be display or run in a batch mode (offscreen) 6 | # viewer.offscreen = true 7 | # What commands to run when the viewer starts up 8 | # viewer.commands = ["","",...] # array of commands 9 | 10 | # TODO: Add our other options 11 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.5 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.13.0") 2 | 3 | addSbtPlugin("com.github.gseitz" % "sbt-protobuf" % "0.3.2") -------------------------------------------------------------------------------- /scripts/genViews.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Example bash script to viewer from jar 4 | BIN=`dirname $0` 5 | source ${BIN}/vars.sh 6 | 7 | #PROPS=${1:-$CONFIG/viewer.conf} 8 | 9 | # Arguments as bash array 10 | #ARGS=("-conf" "$PROPS") 11 | 12 | # Run viewer with maximum 13 | # Use -Xmx10g to specify amount of memory to use 14 | # Use "${ARGS[@]}" so arguments are expanded to "$1" "$2" ... preserving spaces 15 | date 16 | $JAVA $@ -cp $SHAPENET_VIEWER_JAR edu.stanford.graphics.shapenet.apps.GenerateViewpoints 17 | date 18 | -------------------------------------------------------------------------------- /scripts/vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Set up environment variables common to a number of Shapenet viewer 4 | # executables. 5 | # 6 | # Normally this shouldn't be run directly, but instead source'd in 7 | # other shell scripts. 8 | 9 | JAVA=java 10 | SCALA=scala 11 | 12 | BIN=`dirname $0` 13 | BASE=$BIN/.. 14 | CONFIG=$BASE/config 15 | # Set shapnet viewer dir to checkout 16 | export SHAPENET_VIEWER_DIR=$BASE/ 17 | 18 | # Built jar with all dependencies (and main class is edu.stanford.graphics.shapenet.jme3.viewer.Viewer) 19 | SHAPENET_VIEWER_TARGET_DIR=$BASE/target/scala-2.11/ 20 | SHAPENET_VIEWER_JAR=$SHAPENET_VIEWER_TARGET_DIR/shapenet-viewer-assembly-0.1.0.jar 21 | 22 | SHAPENET_VIEWER_DEPS_JAR=$SHAPENET_VIEWER_TARGET_DIR/shapenet-viewer-assembly-0.1.0-deps.jar 23 | SHAPNET_VIEWER_CLASSES=$SHAPENET_VIEWER_TARGET_DIR/classes 24 | 25 | # Classpath 26 | CLASSPATH=$SHAPENET_VIEWER_JAR 27 | -------------------------------------------------------------------------------- /scripts/viewer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Example bash script to viewer from jar 4 | BIN=`dirname $0` 5 | source ${BIN}/vars.sh 6 | 7 | PROPS=${1:-$CONFIG/viewer.conf} 8 | 9 | # Arguments as bash array 10 | ARGS=("-conf" "$PROPS") 11 | 12 | # Run viewer with maximum 13 | # Use -Xmx10g to specify amount of memory to use 14 | # Use "${ARGS[@]}" so arguments are expanded to "$1" "$2" ... preserving spaces 15 | date 16 | $JAVA -jar $SHAPENET_VIEWER_JAR ${ARGS[@]} 17 | date 18 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/gui/MeshTreePanel.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.gui; 2 | 3 | import com.jme3.scene.Geometry; 4 | import com.jme3.scene.Spatial; 5 | import edu.stanford.graphics.shapenet.jme3.viewer.Viewer; 6 | 7 | import javax.swing.event.TreeSelectionEvent; 8 | import javax.swing.tree.DefaultMutableTreeNode; 9 | 10 | /** 11 | * Shows meshes for a node 12 | * 13 | * @author Angel Chang 14 | */ 15 | public class MeshTreePanel extends TreePanel { 16 | 17 | public MeshTreePanel(DefaultMutableTreeNode top) { 18 | super(top); 19 | } 20 | 21 | public static void create(DefaultMutableTreeNode top) { 22 | TreePanel.create("MeshTree", new MeshTreePanel(top)); 23 | } 24 | 25 | /** Required by TreeSelectionListener interface. */ 26 | public void valueChanged(TreeSelectionEvent e) { 27 | DefaultMutableTreeNode node = (DefaultMutableTreeNode) 28 | tree.getLastSelectedPathComponent(); 29 | 30 | if (node == null) return; 31 | 32 | Object nodeInfo = node.getUserObject(); 33 | TreeNodeInfo treeNodeInfo = (TreeNodeInfo)nodeInfo; 34 | Spatial spatial = treeNodeInfo.value; 35 | // TODO: Highlight spatial 36 | // TODO: Display some info in the html pane 37 | if (node.isLeaf()) { 38 | } else { 39 | } 40 | 41 | String endl = "\n"; 42 | StringBuilder sb = new StringBuilder(); 43 | String name = spatial.getName(); 44 | if (spatial instanceof Geometry) { 45 | Geometry geom = (Geometry) spatial; 46 | if (name == null) { 47 | name = "Mesh " + geom.getMesh().getId(); 48 | } 49 | sb.append(name + endl); 50 | sb.append(" mesh: " + geom.getMesh().getId() + endl); 51 | sb.append(" nTriangles: " + geom.getMesh().getTriangleCount() + endl); 52 | sb.append(" material: " + geom.getMaterial().getName() + endl); 53 | sb.append(" worldMatrix: " + geom.getWorldMatrix() + endl); 54 | } else { 55 | sb.append("Node " + name + endl); 56 | } 57 | sb.append(" transform: " + spatial.getLocalTransform() + endl); 58 | sb.append(" bb: " + spatial.getWorldBound() + endl); 59 | sb.append(endl); 60 | String extraInfo = treeNodeInfo.get("info"); 61 | if (extraInfo != null) { 62 | sb.append(extraInfo); 63 | } 64 | String content = sb.toString(); 65 | htmlPane.setText(content); 66 | 67 | // Highlight modelInstance 68 | Viewer viewer = treeNodeInfo.get("viewer"); 69 | if (viewer != null) { 70 | // TODO: Enqueue 71 | viewer.enqueue(() -> { 72 | viewer.debugVisualizer().showSpatial("TreeNodeSpatial", spatial); 73 | return 0; 74 | }); 75 | } 76 | 77 | if (DEBUG) { 78 | System.out.println(nodeInfo.toString()); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/gui/SceneTreePanel.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.gui; 2 | 3 | import edu.stanford.graphics.shapenet.common.ModelInstance; 4 | import edu.stanford.graphics.shapenet.jme3.viewer.Viewer; 5 | 6 | import javax.swing.event.TreeSelectionEvent; 7 | import javax.swing.tree.DefaultMutableTreeNode; 8 | 9 | /** 10 | * Shows meshes for a scene 11 | * 12 | * @author Angel Chang 13 | */ 14 | public class SceneTreePanel extends TreePanel { 15 | 16 | public SceneTreePanel(DefaultMutableTreeNode top) { 17 | super(top); 18 | } 19 | 20 | public static void create(DefaultMutableTreeNode top) { 21 | TreePanel.create("SceneHierarchy", new SceneTreePanel(top)); 22 | } 23 | 24 | /** Required by TreeSelectionListener interface. */ 25 | public void valueChanged(TreeSelectionEvent e) { 26 | DefaultMutableTreeNode node = (DefaultMutableTreeNode) 27 | tree.getLastSelectedPathComponent(); 28 | 29 | if (node == null) return; 30 | 31 | Object nodeInfo = node.getUserObject(); 32 | TreeNodeInfo treeNodeInfo = (TreeNodeInfo)nodeInfo; 33 | ModelInstance modelInstance = treeNodeInfo.value; 34 | if (node.isLeaf()) { 35 | } else { 36 | } 37 | if (modelInstance != null) { 38 | // Display some info in the html pane 39 | String content = modelInstance.model().modelInfo().toDetailedString(); 40 | htmlPane.setText(content); 41 | // Highlight modelInstance 42 | Viewer viewer = treeNodeInfo.get("viewer"); 43 | if (viewer != null) { 44 | // TODO: Enqueue 45 | viewer.enqueue(() -> { 46 | viewer.setSelected(modelInstance.index()); 47 | return 0; 48 | }); 49 | } 50 | } 51 | if (DEBUG) { 52 | System.out.println(nodeInfo.toString()); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/gui/TreeNodeInfo.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.gui; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Wrapper class including information about a tree node 8 | */ 9 | public class TreeNodeInfo { 10 | public String name; 11 | public T value; 12 | public Map data; 13 | 14 | public TreeNodeInfo(String name, T value) { 15 | this.name = name; 16 | this.value = value; 17 | } 18 | 19 | public String toString() { 20 | return name; 21 | } 22 | 23 | public V get(String key) { 24 | if (data != null) { 25 | return (V) data.get(key); 26 | } else return null; 27 | } 28 | 29 | public void put(String key, V v) { 30 | if (data == null) { 31 | data = new HashMap(); 32 | } 33 | data.put(key,v); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/gui/TreePanel.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.gui; 2 | 3 | import javax.swing.*; 4 | import javax.swing.event.TreeSelectionEvent; 5 | import javax.swing.event.TreeSelectionListener; 6 | import javax.swing.tree.DefaultMutableTreeNode; 7 | import javax.swing.tree.TreeSelectionModel; 8 | import java.awt.*; 9 | import java.io.IOException; 10 | import java.net.URL; 11 | 12 | /** 13 | * Shows a tree panel 14 | * 15 | * @author Angel Chang 16 | */ 17 | public class TreePanel extends JPanel implements TreeSelectionListener { 18 | protected JTextPane htmlPane; 19 | protected JTree tree; 20 | protected URL helpURL; 21 | protected static boolean DEBUG = false; 22 | 23 | //Optionally set the look and feel. 24 | private static boolean useSystemLookAndFeel = false; 25 | 26 | public TreePanel(DefaultMutableTreeNode top) { 27 | super(new GridLayout(1, 0)); 28 | 29 | //Create a tree that allows one selection at a time. 30 | tree = new JTree(top); 31 | tree.getSelectionModel().setSelectionMode 32 | (TreeSelectionModel.SINGLE_TREE_SELECTION); 33 | 34 | //Listen for when the selection changes. 35 | tree.addTreeSelectionListener(this); 36 | 37 | //Create the scroll pane and add the tree to it. 38 | JScrollPane treeView = new JScrollPane(tree); 39 | 40 | //Create the HTML viewing pane. 41 | htmlPane = new JTextPane(); 42 | htmlPane.setEditable(false); 43 | JScrollPane htmlView = new JScrollPane(htmlPane); 44 | 45 | //Add the scroll panes to a split pane. 46 | JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 47 | splitPane.setTopComponent(treeView); 48 | splitPane.setBottomComponent(htmlView); 49 | 50 | Dimension minimumSize = new Dimension(100, 50); 51 | htmlView.setMinimumSize(minimumSize); 52 | treeView.setMinimumSize(minimumSize); 53 | splitPane.setDividerLocation(100); 54 | splitPane.setPreferredSize(new Dimension(500, 300)); 55 | 56 | //Add the split pane to this panel. 57 | add(splitPane); 58 | } 59 | 60 | public void setHtmlPaneUrl(String url) { 61 | try { 62 | htmlPane.setPage(url); 63 | } catch (IOException ex) { 64 | throw new RuntimeException("Error fetching url: " + url, ex); 65 | } 66 | } 67 | 68 | public void setHtmlPaneContent(String title, String bodyString) { 69 | String htmlString = "" + title + "" + bodyString + ""; 70 | htmlPane.setContentType("text/html"); 71 | htmlPane.setText(htmlString); 72 | } 73 | 74 | public void valueChanged(TreeSelectionEvent e) { 75 | } 76 | /** 77 | * Create the GUI and show it. For thread safety, 78 | * this method should be invoked from the 79 | * event dispatch thread. 80 | */ 81 | private static void createAndShowGUI(String name, TreePanel panel, boolean exitOnClose) { 82 | if (useSystemLookAndFeel) { 83 | try { 84 | UIManager.setLookAndFeel( 85 | UIManager.getSystemLookAndFeelClassName()); 86 | } catch (Exception e) { 87 | System.err.println("Couldn't use system look and feel."); 88 | } 89 | } 90 | 91 | //Create and set up the window. 92 | JFrame frame = new JFrame(name); 93 | if (exitOnClose) { 94 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 95 | } 96 | 97 | //Add content to the window. 98 | frame.add(panel); 99 | 100 | //Display the window. 101 | frame.pack(); 102 | frame.setVisible(true); 103 | } 104 | 105 | public static void create(String name, TreePanel panel) { 106 | //Schedule a job for the event dispatch thread: 107 | //creating and showing this application's GUI. 108 | javax.swing.SwingUtilities.invokeLater(new Runnable() { 109 | public void run() { 110 | createAndShowGUI(name, panel, false); 111 | } 112 | }); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/AssetInfoFile.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLoadException; 6 | import com.jme3.asset.AssetManager; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.InputStream; 12 | 13 | public class AssetInfoFile extends AssetInfo { 14 | public final File file; 15 | 16 | public AssetInfoFile(AssetManager manager, AssetKey key, File file){ 17 | super(manager, key); 18 | this.file = file; 19 | } 20 | 21 | @Override 22 | public InputStream openStream() { 23 | try{ 24 | return new FileInputStream(file); 25 | } catch (FileNotFoundException ex){ 26 | // NOTE: Can still happen even if file.exists() is true, e.g. 27 | // permissions issue and similar 28 | throw new AssetLoadException("Failed to open file: " + file, ex); 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/ByteArrayAssetInfo.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLoadException; 6 | import com.jme3.asset.AssetManager; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | /** 13 | * Byte array asset info 14 | * 15 | * @author Angel Chang 16 | */ 17 | public class ByteArrayAssetInfo extends AssetInfo { 18 | private final byte[] bytes; 19 | 20 | public ByteArrayAssetInfo(AssetManager manager, AssetKey key, byte[] bytes) { 21 | super(manager, key); 22 | this.bytes = bytes; 23 | } 24 | 25 | public InputStream openStream() { 26 | return new ByteArrayInputStream(bytes); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/CachedUrlAssetInfo.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLoadException; 6 | import com.jme3.asset.AssetManager; 7 | import edu.stanford.graphics.shapenet.util.WebUtils; 8 | import org.apache.commons.lang3.tuple.Pair; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.net.URL; 14 | 15 | // URL asset info that is cached 16 | public class CachedUrlAssetInfo extends AssetInfo { 17 | private URL url; 18 | private File cachedFile; 19 | 20 | public static CachedUrlAssetInfo create(AssetManager assetManager, AssetKey key, URL url) throws IOException { 21 | return new CachedUrlAssetInfo(assetManager, key, url); 22 | } 23 | 24 | public File getFile() { 25 | if (cachedFile == null) { 26 | cachedFile = WebUtils.cachedFile(url); 27 | } 28 | return cachedFile; 29 | } 30 | 31 | private CachedUrlAssetInfo(AssetManager assetManager, AssetKey key, URL url) throws IOException { 32 | super(assetManager, key); 33 | this.url = url; 34 | } 35 | 36 | private static Pair getInputStream(URL url) { 37 | scala.Tuple2 tuple = WebUtils.inputStreamWithCachedFile(url).getOrElse(null); 38 | if (tuple != null) { 39 | return Pair.of(tuple._1(), tuple._2()); 40 | } else return null; 41 | } 42 | 43 | public InputStream openStream() { 44 | Pair pair = getInputStream(url); 45 | if (pair == null) { 46 | throw new AssetLoadException("Failed to read URL " + this.url); 47 | } 48 | this.cachedFile = pair.getRight(); 49 | return pair.getLeft(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/CompressedAssetKey.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetKey; 4 | import com.jme3.asset.AssetProcessor; 5 | import com.jme3.asset.cache.AssetCache; 6 | 7 | /** 8 | * Compressed asset 9 | * 10 | * @author Angel Chang 11 | */ 12 | public class CompressedAssetKey extends AssetKey { 13 | AssetKey baseAssetKey; 14 | String compressionExt; 15 | public CompressedAssetKey(AssetKey baseAssetKey, String compressionExt) { 16 | super(baseAssetKey.getName() + "." + compressionExt); 17 | this.baseAssetKey = baseAssetKey; 18 | this.compressionExt = compressionExt; 19 | } 20 | 21 | public String getCompressionExt() { 22 | return compressionExt; 23 | } 24 | 25 | public AssetKey getBaseAssetKey() { 26 | return baseAssetKey; 27 | } 28 | 29 | public String getExtension() { 30 | return baseAssetKey.getExtension(); 31 | } 32 | 33 | @Override 34 | public Class getCacheType() { 35 | return baseAssetKey.getCacheType(); 36 | } 37 | 38 | @Override 39 | public Class getProcessorType() { 40 | return baseAssetKey.getProcessorType(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/EnhancedAssetKey.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetKey; 4 | 5 | /** 6 | * Enhanced asset key that stored some more information 7 | * 8 | * @author Angel Chang 9 | */ 10 | public class EnhancedAssetKey extends AssetKey { 11 | final String geometryPath; 12 | final String materialsPath; 13 | 14 | public EnhancedAssetKey(String name, String geometryPath, String materialsPath) { 15 | super(name); 16 | this.geometryPath = geometryPath; 17 | this.materialsPath = materialsPath; 18 | } 19 | 20 | public String getGeometryPath() { 21 | return geometryPath; 22 | } 23 | 24 | public String getMaterialsPath() { 25 | return materialsPath; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/EnhancedModelKey.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.ModelKey; 4 | 5 | /** 6 | * Model key with more properties 7 | * 8 | * @author Angel Chang 9 | */ 10 | public class EnhancedModelKey extends ModelKey { 11 | final String geometryPath; 12 | final String materialsPath; 13 | 14 | public EnhancedModelKey(String name, String geometryPath, String materialsPath) { 15 | super(name); 16 | this.geometryPath = geometryPath; 17 | this.materialsPath = materialsPath; 18 | } 19 | 20 | public String getGeometryPath() { 21 | return geometryPath; 22 | } 23 | 24 | public String getMaterialsPath() { 25 | return materialsPath; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/MemBytesLocator.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLocator; 6 | import com.jme3.asset.AssetManager; 7 | 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * Create AssetInfo from to some in memory bytes 13 | */ 14 | public class MemBytesLocator implements AssetLocator { 15 | private static final Map> cache = new ConcurrentHashMap>(); 16 | 17 | private String rootPath; 18 | 19 | public static final void register(String prefix, Map map) { 20 | cache.put(prefix, map); 21 | } 22 | 23 | public static final void unregister(String prefix) { 24 | cache.remove(prefix); 25 | } 26 | 27 | public void setRootPath(String rootPath) { 28 | this.rootPath = rootPath; 29 | } 30 | 31 | public AssetInfo locate(AssetManager manager, AssetKey key) { 32 | final Map map = cache.get(rootPath); 33 | if (map != null) { 34 | final byte[] bytes = map.get(key.getName()); 35 | if (bytes != null) { 36 | return new ByteArrayAssetInfo(manager, key, bytes); 37 | } else { 38 | return null; 39 | } 40 | } else { 41 | return null; 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/MyFileLocator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009-2012 jMonkeyEngine 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | package edu.stanford.graphics.shapenet.jme3.asset; 33 | 34 | import com.jme3.asset.*; 35 | 36 | import java.io.*; 37 | 38 | /** 39 | * FileLocator allows you to specify a folder where to 40 | * look for assets. 41 | * @author Angel Chang 42 | */ 43 | public class MyFileLocator implements AssetLocator { 44 | 45 | private File root; 46 | 47 | public void setRootPath(String rootPath) { 48 | if (rootPath == null) { 49 | throw new IllegalArgumentException("rootPath is required: use '\' to register any file"); 50 | } else if (rootPath == "/") { 51 | // any file is okay 52 | root = null; 53 | return; 54 | } 55 | 56 | try { 57 | root = new File(rootPath).getCanonicalFile(); 58 | if (!root.isDirectory()){ 59 | throw new IllegalArgumentException("Given root path \"" + root + "\" is not a directory"); 60 | } 61 | } catch (IOException ex) { 62 | throw new AssetLoadException("Root path is invalid", ex); 63 | } 64 | } 65 | 66 | public AssetInfo locate(AssetManager manager, AssetKey key) { 67 | String name = key.getName(); 68 | File file = (root != null)? new File(root, name) : new File(name); 69 | if (file.exists() && file.isFile()){ 70 | try { 71 | // Now, check asset name requirements 72 | String canonical = file.getCanonicalPath(); 73 | String absolute = file.getAbsolutePath(); 74 | if (!canonical.endsWith(absolute)){ 75 | throw new AssetNotFoundException("Asset name doesn't match requirements.\n"+ 76 | "\"" + canonical + "\" doesn't match \"" + absolute + "\""); 77 | } 78 | } catch (IOException ex) { 79 | throw new AssetLoadException("Failed to get file canonical path " + file, ex); 80 | } 81 | 82 | AssetInfo fileAssetInfo = new AssetInfoFile(manager, key, file); 83 | return UncompressedAssetInfo.create(fileAssetInfo); 84 | }else{ 85 | return null; 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/MyUrlLocator.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLocator; 6 | import com.jme3.asset.AssetManager; 7 | import com.jme3.asset.plugins.UrlAssetInfo; 8 | 9 | import java.io.FileNotFoundException; 10 | import java.io.IOException; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | 16 | /** 17 | * My version of the com.jme3.asset.plugins.UrlLocator that checks 18 | * check that the url start with the rootPath and treats it as a full URL 19 | * (otherwise, incurs cost of fetch bad content) 20 | * 21 | * @author Angel Chang 22 | */ 23 | public class MyUrlLocator implements AssetLocator { 24 | 25 | private static final Logger logger = Logger.getLogger(MyUrlLocator.class.getName()); 26 | private URL root; 27 | 28 | public void setRootPath(String rootPath) { 29 | if (rootPath == null) { 30 | throw new IllegalArgumentException("rootPath is required: use '\' to register any url"); 31 | } else if (rootPath == "/") { 32 | // any url is okay 33 | root = null; 34 | return; 35 | } 36 | 37 | try { 38 | this.root = new URL(rootPath); 39 | } catch (MalformedURLException ex) { 40 | throw new IllegalArgumentException("Invalid rootUrl specified", ex); 41 | } 42 | } 43 | 44 | public AssetInfo locate(AssetManager manager, AssetKey key) { 45 | String name = key.getName(); 46 | if (root == null) { 47 | try { 48 | URL url = new URL(name); 49 | AssetInfo urlAssetInfo = CachedUrlAssetInfo.create(manager, key, url); 50 | return UncompressedAssetInfo.create(urlAssetInfo); 51 | } catch (MalformedURLException ex) { 52 | return null; 53 | } catch (IOException ex){ 54 | logger.log(Level.WARNING, "Error while locating " + name, ex); 55 | return null; 56 | } 57 | } 58 | 59 | else if (!name.startsWith(root.toExternalForm())) return null; 60 | try{ 61 | // See if we already have a complete URL 62 | URL url; 63 | try { 64 | url = new URL(name); 65 | } catch (MalformedURLException ex) { 66 | if (name.startsWith("/")){ 67 | name = name.substring(1); 68 | } 69 | url = new URL(root.toExternalForm() + name); 70 | } 71 | AssetInfo urlAssetInfo = CachedUrlAssetInfo.create(manager, key, url); 72 | return UncompressedAssetInfo.create(urlAssetInfo); 73 | }catch (FileNotFoundException e){ 74 | return null; 75 | }catch (IOException ex){ 76 | logger.log(Level.WARNING, "Error while locating " + name, ex); 77 | return null; 78 | } 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/UncompressedAssetInfo.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLoadException; 6 | import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import java.util.zip.GZIPInputStream; 14 | 15 | /** 16 | * AssetInfo that handles compressions 17 | * @author Angel Chang 18 | */ 19 | public class UncompressedAssetInfo extends AssetInfo { 20 | public enum CompressionFormat { 21 | GZIP, BZIP2 22 | } 23 | 24 | static final Map extToCompressionFormatMapping = 25 | new HashMap(); 26 | static { 27 | extToCompressionFormatMapping.put("bz2", CompressionFormat.BZIP2); 28 | extToCompressionFormatMapping.put("bzip2", CompressionFormat.BZIP2); 29 | extToCompressionFormatMapping.put("gzip", CompressionFormat.GZIP); 30 | extToCompressionFormatMapping.put("gz", CompressionFormat.GZIP); 31 | } 32 | static public Set compressedExtensions() { 33 | return extToCompressionFormatMapping.keySet(); 34 | } 35 | 36 | final CompressionFormat compressionFormat; 37 | final AssetInfo baseAssetInfo; 38 | 39 | public UncompressedAssetInfo(AssetInfo baseAssetInfo, AssetKey key, CompressionFormat compressionFormat) { 40 | super(baseAssetInfo.getManager(), key); 41 | this.baseAssetInfo = baseAssetInfo; 42 | this.compressionFormat = compressionFormat; 43 | } 44 | 45 | public static AssetInfo create(AssetInfo baseAssetInfo) { 46 | if (baseAssetInfo.getKey() instanceof CompressedAssetKey) { 47 | String ext = ((CompressedAssetKey) baseAssetInfo.getKey()).getCompressionExt(); 48 | CompressionFormat compressionFormat = extToCompressionFormatMapping.get(ext.toLowerCase()); 49 | AssetKey key = ((CompressedAssetKey) baseAssetInfo.getKey()).getBaseAssetKey(); 50 | return new UncompressedAssetInfo(baseAssetInfo, key, compressionFormat); 51 | } else { 52 | String ext = baseAssetInfo.getKey().getExtension(); 53 | CompressionFormat compressionFormat = extToCompressionFormatMapping.get(ext.toLowerCase()); 54 | if (compressionFormat != null) { 55 | String name = baseAssetInfo.getKey().getName(); 56 | String strippedName = name.substring(0, name.length() - ext.length() - 1); 57 | AssetKey key = new AssetKey( strippedName ); 58 | return new UncompressedAssetInfo(baseAssetInfo, key, compressionFormat); 59 | } else { 60 | return baseAssetInfo; 61 | } 62 | } 63 | } 64 | 65 | @Override 66 | public InputStream openStream() { 67 | try { 68 | switch (compressionFormat) { 69 | case BZIP2: 70 | return new BZip2CompressorInputStream(baseAssetInfo.openStream()); 71 | case GZIP: 72 | return new GZIPInputStream(baseAssetInfo.openStream()); 73 | default: 74 | throw new UnsupportedOperationException("Unsupported compression format: " + compressionFormat); 75 | } 76 | } catch (IOException ex) { 77 | throw new AssetLoadException("Error loading asset: " + baseAssetInfo.getKey(), ex); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/ZipAssetKey.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetKey; 4 | 5 | /** 6 | * Asset key in Zip file 7 | * 8 | * @author Angel Chang 9 | */ 10 | public class ZipAssetKey extends AssetKey { 11 | AssetKey baseAssetKey; 12 | 13 | public ZipAssetKey(AssetKey baseAssetKey, String name){ 14 | this.baseAssetKey = baseAssetKey; 15 | this.name = reducePath(name); 16 | this.extension = getExtension(this.name); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/edu/stanford/graphics/shapenet/jme3/asset/ZipEntryAssetInfo.java: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.asset; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLoadException; 6 | import com.jme3.asset.AssetManager; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.zip.ZipEntry; 11 | import java.util.zip.ZipFile; 12 | import java.util.zip.ZipInputStream; 13 | 14 | /** 15 | * Zip entry asset info 16 | */ 17 | public class ZipEntryAssetInfo extends AssetInfo { 18 | private final ZipFile zipFile; 19 | private final ZipEntry entry; 20 | 21 | public ZipEntryAssetInfo(AssetManager manager, AssetKey key, ZipFile zipFile, ZipEntry entry) { 22 | super(manager, key); 23 | this.zipFile = zipFile; 24 | this.entry = entry; 25 | } 26 | 27 | public InputStream openStream() { 28 | try { 29 | return this.zipFile.getInputStream(this.entry); 30 | } catch (IOException ex) { 31 | throw new AssetLoadException("Failed to load zip entry: " + this.entry, ex); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/ColladaDocumentFactory.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | import jme3dae.utilities.Tuple2; 4 | import com.jme3.asset.AssetManager; 5 | import com.jme3.scene.Node; 6 | import jme3dae.FXEnhancerInfo.IgnoreLights; 7 | import jme3dae.FXEnhancerInfo.IgnoreMeasuringUnit; 8 | import jme3dae.FXEnhancerInfo.NormalMapGenerator; 9 | import jme3dae.FXEnhancerInfo.TwoSidedMaterial; 10 | import jme3dae.FXEnhancerInfo.UseJME3Materials; 11 | import jme3dae.collada14.ColladaDocumentV14; 12 | import jme3dae.transformers.ValueTransformer; 13 | 14 | /** 15 | * A factory for collada document parsers. 16 | * 17 | * @author pgi 18 | */ 19 | public class ColladaDocumentFactory { 20 | private static ProgressListener progressListener = null; 21 | 22 | public static void setProgressListener(ProgressListener listener) { 23 | progressListener = listener; 24 | } 25 | 26 | public static ProgressListener getProgressListener() { 27 | return progressListener; 28 | } 29 | 30 | public static class FXSettingsGenerator { 31 | private FXEnhancerInfo fx = FXEnhancerInfo.create(FXEnhancerInfo.IgnoreMeasuringUnit.OFF); 32 | 33 | public FXSettingsGenerator setIgnoreMeasuringUnit(IgnoreMeasuringUnit v) { 34 | fx = new FXEnhancerInfo(fx.getAutoBump(), fx.getTwoSided(), v == IgnoreMeasuringUnit.ON, fx.getIgnoreLights(), 35 | fx.getUseJME3Materials()); 36 | return this; 37 | } 38 | 39 | public FXSettingsGenerator setNormalMapGeneration(FXEnhancerInfo.NormalMapGenerator v) { 40 | fx = new FXEnhancerInfo(v == NormalMapGenerator.ON, fx.getTwoSided(), fx.getIgnoreMeasuringUnit(), fx.getIgnoreLights(), 41 | fx.getUseJME3Materials()); 42 | return this; 43 | } 44 | 45 | public FXSettingsGenerator setIgnoreLights(IgnoreLights v) { 46 | fx = new FXEnhancerInfo(fx.getAutoBump(), fx.getTwoSided(), fx.getIgnoreMeasuringUnit(), v == IgnoreLights.ON, 47 | fx.getUseJME3Materials()); 48 | return this; 49 | } 50 | 51 | public FXSettingsGenerator setTwoSidedMaterial(TwoSidedMaterial m) { 52 | fx = new FXEnhancerInfo(fx.getAutoBump(), m == TwoSidedMaterial.ON, fx.getIgnoreMeasuringUnit(), fx.getIgnoreLights(), 53 | fx.getUseJME3Materials()); 54 | return this; 55 | } 56 | 57 | public FXSettingsGenerator setUseJME3Materials(UseJME3Materials m) { 58 | fx = new FXEnhancerInfo(fx.getAutoBump(), fx.getTwoSided(), fx.getIgnoreMeasuringUnit(), fx.getIgnoreLights(), 59 | m == UseJME3Materials.ON); 60 | return this; 61 | } 62 | 63 | public FXEnhancerInfo get() { 64 | return fx; 65 | } 66 | 67 | } 68 | 69 | private static FXEnhancerInfo fxInfo = FXEnhancerInfo.create( 70 | FXEnhancerInfo.NormalMapGenerator.OFF, 71 | FXEnhancerInfo.TwoSidedMaterial.OFF); 72 | 73 | public static void setFXEnhance(FXEnhancerInfo info) { 74 | fxInfo = info; 75 | } 76 | 77 | private ColladaDocumentFactory() { 78 | } 79 | 80 | /** 81 | * Parses a DAENode wrapping the root of a collada document (COLLADA element). Returns a parser 82 | * for that document. The root node is used to choose the parser version (if collada version is 1.4 the 83 | * factory will return a 1.4 parser, if the version is 1.5 the factory will return a ... 1.4 parser 84 | * because it's the only one available). 85 | * 86 | * @param root the DAENode wrapping the COLLADA element of a collada document. 87 | * @return a parser for the collada document wrapped by the given DAENode. 88 | */ 89 | public static ValueTransformer, Node> newColladaDocumentParser(DAENode root) { 90 | return ColladaDocumentV14.create(fxInfo); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/ColladaInfo.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | 5 | /** 6 | * Informations about the collada document, stored in the DAENode root as 7 | * parsed data. This is used to access external resources (currently just the AssetInfo 8 | * to find texture images). 9 | * 10 | * @author pgi 11 | */ 12 | public class ColladaInfo { 13 | 14 | /** 15 | * Creator for ColladaInfo instances. 16 | * 17 | * @param info the AssetInfo pointing to the dae document. The location of 18 | * the file wrapped in the asset info is used to search images. 19 | * @return a new ColladaInfo instance. 20 | */ 21 | public static ColladaInfo create(AssetInfo info) { 22 | return new ColladaInfo(info); 23 | } 24 | 25 | 26 | private final AssetInfo INFO; 27 | 28 | /** 29 | * Initialize this instance. 30 | * 31 | * @param info the asset info wrapped by this ColladInfo. 32 | */ 33 | private ColladaInfo(AssetInfo info) { 34 | INFO = info; 35 | } 36 | 37 | /** 38 | * Returns the asset info that points to the collada document file. 39 | * 40 | * @return the asset info used to load the collada model. 41 | */ 42 | public AssetInfo getInfo() { 43 | return INFO; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/ColladaLoader.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | import jme3dae.utilities.MeasuringUnit; 4 | import jme3dae.utilities.Tuple2; 5 | import com.jme3.asset.AssetInfo; 6 | import com.jme3.asset.AssetLoader; 7 | import com.jme3.asset.AssetManager; 8 | import com.jme3.asset.plugins.UrlLocator; 9 | import com.jme3.math.FastMath; 10 | import com.jme3.scene.Node; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | 17 | import jme3dae.transformers.ValueTransformer.TransformedValue; 18 | import jme3dae.utilities.TextureBaseList; 19 | import jme3dae.utilities.UpAxis; 20 | 21 | /** 22 | * A JME3 Asset loader that loads a collada file. 23 | * 24 | * @author pgi 25 | */ 26 | public class ColladaLoader implements AssetLoader { 27 | public static class ModelDirectoryUrl { 28 | final String url; 29 | 30 | public ModelDirectoryUrl(String url) { 31 | this.url = url; 32 | } 33 | } 34 | 35 | /** 36 | * Registers the loader to load elements from the given url base 37 | * 38 | * @param manager the asset manager where to register the loader 39 | * @param url the url path of the directory that contains the models to load 40 | */ 41 | public static void register(AssetManager manager, ModelDirectoryUrl url) { 42 | manager.registerLoader(ColladaLoader.class, "dae"); 43 | manager.registerLocator(url.url, UrlLocator.class); 44 | } 45 | 46 | /** 47 | * Used to set the texture base directory 48 | */ 49 | private static final TextureBaseList TEXTURE_BASE = new TextureBaseList(); 50 | 51 | /** 52 | * Add a path to instruct the loader where to find textures. The path will be 53 | * used to form an assetmanager path along with the name of the texture if that 54 | * name cannot be resolved per se. Eg. if a texture named "x/y/z/abc.png" cannot be 55 | * resolved by the asset manager, the collada loader will try to load the file 56 | * "abc.png" using the base paths setted with this method. If the path is 57 | * "/a/b/c/" then the loader will pass to the assetmanager the path "/a/b/c/abc.png". 58 | * 59 | * @param base an asset loader base path where textures might be stored. 60 | */ 61 | public static void addTextureBase(String base) { 62 | TEXTURE_BASE.add(base); 63 | } 64 | 65 | public static void removeTextureBase(String base) { 66 | TEXTURE_BASE.remove(base); 67 | } 68 | 69 | /** 70 | * Default no-arg constructor. 71 | */ 72 | public ColladaLoader() { 73 | } 74 | 75 | /** 76 | * Load a collada document. 77 | * 78 | * @param assetInfo a pointer to a collada (dae) document. 79 | * @return a Node element wrapping the contents of the loaded document or null 80 | * if some (logged) exception happens during the process. 81 | */ 82 | public Node load(AssetInfo assetInfo) { 83 | InputStream in = assetInfo.openStream(); 84 | Node node = null; 85 | try { 86 | // Clear the DAENode registry so we get our memory back 87 | DAENode.clearRegistry(); 88 | DAENode root = DAELoader.create().load(in); //generate the collada-xml root node 89 | root.setParsedData(ColladaInfo.create(assetInfo)); //stores the collada info into the root node 90 | root.setParsedData(TEXTURE_BASE); 91 | Tuple2 data = Tuple2.create(root, assetInfo.getManager()); 92 | 93 | //Creates and applies the transformed that maps the entire collada document (via its root) 94 | //to a jme node 95 | TransformedValue jmeRoot = ColladaDocumentFactory.newColladaDocumentParser(root).transform(data); 96 | node = jmeRoot.get(); //this may return null 97 | if (node != null) { 98 | UpAxis upAxis = root.getParsedData(UpAxis.class); 99 | if (upAxis != null) { 100 | node.setUserData("up", upAxis.toString()); 101 | } 102 | MeasuringUnit measuringUnit = root.getParsedData(MeasuringUnit.class); 103 | if (measuringUnit != null) { 104 | node.setUserData("unit", measuringUnit.getMeter()); 105 | } 106 | } 107 | // Clear the DAENode registry so we get our memory back 108 | DAENode.clearRegistry(); 109 | } catch (Exception ex) { 110 | Logger.getLogger(ColladaLoader.class.getName()).log(Level.SEVERE, null, ex); 111 | } finally { 112 | try { 113 | in.close(); 114 | } catch (IOException ex) { 115 | Logger.getLogger(ColladaLoader.class.getName()).log(Level.SEVERE, null, ex); 116 | } 117 | } 118 | return node; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/DAELoader.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | import javax.xml.parsers.DocumentBuilder; 8 | import javax.xml.parsers.DocumentBuilderFactory; 9 | 10 | import org.w3c.dom.Document; 11 | import org.w3c.dom.Node; 12 | import org.w3c.dom.NodeList; 13 | 14 | /** 15 | * Loads a collada xml document. This loader wraps the xml nodes of the collada 16 | * document in DAENode elements. A DAENode is an extended xml node that provides some 17 | * utility methods related to the structure of collada xml nodes. 18 | * 19 | * @author pgi 20 | */ 21 | public class DAELoader { 22 | 23 | /** 24 | * Instance creator. Creates a new DAELoader. The loader is stateless. 25 | * 26 | * @return a new DAELoader instance. 27 | */ 28 | public static DAELoader create() { 29 | return new DAELoader(); 30 | } 31 | 32 | private DAELoader() { 33 | } 34 | 35 | /** 36 | * Load the collada document from the given input stream. Closes the stream 37 | * after loading. 38 | * 39 | * @param in the input stream of the collada document 40 | * @return a DAENode wrapping the COLLADA element of the dae document. Returns 41 | * null if parsing fails for any (logged) reason. 42 | */ 43 | public DAENode load(InputStream in) { 44 | DAENode root = null; 45 | try { 46 | DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); 47 | DocumentBuilder bui = fac.newDocumentBuilder(); 48 | Document doc = bui.parse(in); 49 | root = wrap(null, doc.getDocumentElement()); 50 | } catch (Exception ex) { 51 | Logger.getLogger(getClass().getName()).log(Level.SEVERE, "", ex); 52 | } finally { 53 | if (in != null) { 54 | try { 55 | in.close(); 56 | } catch (IOException ex) { 57 | Logger.getLogger(DAELoader.class.getName()).log(Level.SEVERE, null, ex); 58 | } 59 | } 60 | } 61 | return root; 62 | } 63 | 64 | /** 65 | * Wraps the a tree of xml nodes into a tree of DAENode nodes. This method is 66 | * called recursively. 67 | * 68 | * @param parent the DAENode parent of the wrapping node produced by this 69 | * method 70 | * @param node the xml node to wrap 71 | * @return the root of the tree of DAENodes 72 | */ 73 | private DAENode wrap(DAENode parent, Node node) { 74 | DAENode dae = DAENode.create(parent, node); 75 | NodeList children = node.getChildNodes(); 76 | for (int i = 0; i < children.getLength(); i++) { 77 | Node child = children.item(i); 78 | dae.addChild(wrap(dae, child)); 79 | } 80 | return dae; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/ExAnimTest.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | import com.jme3.app.SimpleApplication; 4 | import com.jme3.asset.plugins.UrlLocator; 5 | import com.jme3.scene.Node; 6 | import com.jme3.scene.Spatial; 7 | import jme3dae.utilities.ExplicitAnimationNode; 8 | 9 | public class ExAnimTest extends SimpleApplication { 10 | 11 | public static void main(String[] args) { 12 | new ExAnimTest().start(); 13 | } 14 | 15 | @Override 16 | public void simpleInitApp() { 17 | inputManager.setCursorVisible(true); 18 | 19 | ColladaDocumentFactory.setFXEnhance(FXEnhancerInfo.create(FXEnhancerInfo.IgnoreMeasuringUnit.ON)); 20 | String base = "file:///home/as/workspace/AndroidRenderTest/assets/samples/"; 21 | 22 | assetManager.registerLoader(ColladaLoader.class, "dae"); 23 | assetManager.registerLocator(base, UrlLocator.class); 24 | Spatial node = assetManager.loadModel(base + "astroBoy_walk.dae"); 25 | 26 | rootNode.attachChild(node); 27 | 28 | /* 29 | final ExplicitAnimationNode animation = ExplicitAnimationNode.createFrom((Node) node); 30 | rootNode.attachChild(animation); 31 | animation.setCurrentSequence("walk"); 32 | animation.setAnimationLength(1f); 33 | animation.setEnabled(true); 34 | */ 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/FXEnhancerInfo.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | /** 4 | * Experimenti: this class wraps some values used by the plugin to autogenerate 5 | * some effects (like normal mapping). 6 | * 7 | * @author pgi 8 | */ 9 | public class FXEnhancerInfo { 10 | 11 | public boolean getIgnoreLights() { 12 | return ignoreLights; 13 | } 14 | 15 | public static enum IgnoreLights { 16 | ON, OFF 17 | } 18 | 19 | public static enum NormalMapGenerator { 20 | ON, OFF 21 | } 22 | 23 | public static enum TwoSidedMaterial { 24 | ON, OFF 25 | } 26 | 27 | public static enum IgnoreMeasuringUnit { 28 | ON, OFF 29 | } 30 | 31 | public static enum UseJME3Materials { 32 | ON, OFF 33 | } 34 | 35 | public static FXEnhancerInfo create(IgnoreMeasuringUnit igm) { 36 | return new FXEnhancerInfo(false, false, igm == IgnoreMeasuringUnit.ON, false, true); 37 | } 38 | 39 | public static FXEnhancerInfo create(NormalMapGenerator ngen, TwoSidedMaterial tsmat, IgnoreMeasuringUnit igm, 40 | IgnoreLights il) { 41 | return new FXEnhancerInfo( 42 | ngen == NormalMapGenerator.ON, 43 | tsmat == TwoSidedMaterial.ON, 44 | igm == IgnoreMeasuringUnit.ON, 45 | il == IgnoreLights.ON, 46 | true); 47 | } 48 | 49 | public static FXEnhancerInfo create(NormalMapGenerator ngen, TwoSidedMaterial tsmat, IgnoreMeasuringUnit igm) { 50 | return new FXEnhancerInfo( 51 | ngen == NormalMapGenerator.ON, 52 | tsmat == TwoSidedMaterial.ON, 53 | igm == IgnoreMeasuringUnit.ON, 54 | false, 55 | true); 56 | } 57 | 58 | public static FXEnhancerInfo create(NormalMapGenerator ngen, TwoSidedMaterial tsmat, IgnoreMeasuringUnit igm, IgnoreLights il, UseJME3Materials ujm) { 59 | return new FXEnhancerInfo( 60 | ngen == NormalMapGenerator.ON, 61 | tsmat == TwoSidedMaterial.ON, 62 | igm == IgnoreMeasuringUnit.ON, 63 | il == IgnoreLights.ON, 64 | ujm == UseJME3Materials.ON); 65 | } 66 | 67 | public static FXEnhancerInfo create(NormalMapGenerator ngen, TwoSidedMaterial tsmat) { 68 | return new FXEnhancerInfo( 69 | ngen == NormalMapGenerator.ON, 70 | tsmat == TwoSidedMaterial.ON, 71 | false, 72 | false, 73 | true); 74 | } 75 | 76 | public static FXEnhancerInfo create(NormalMapGenerator ngen) { 77 | return new FXEnhancerInfo(ngen == NormalMapGenerator.ON, false, false, false, true); 78 | } 79 | 80 | private final boolean autoBump; 81 | private final boolean twoSided; 82 | private final boolean ignoreMeasuringUnit; 83 | private final boolean ignoreLights; 84 | private final boolean useJME3Materials; 85 | 86 | FXEnhancerInfo(boolean autoBump, boolean twoSidedMaterials, boolean ignoreMeasuringUnit, boolean ignoreLights, boolean useJME3Materials) { 87 | this.autoBump = autoBump; 88 | twoSided = twoSidedMaterials; 89 | this.ignoreMeasuringUnit = ignoreMeasuringUnit; 90 | this.ignoreLights = ignoreLights; 91 | //this.useJME3Materials = autoBump || useJME3Materials; 92 | this.useJME3Materials = true; 93 | 94 | } 95 | 96 | public boolean getUseJME3Materials() { 97 | return useJME3Materials; 98 | } 99 | 100 | public boolean getIgnoreMeasuringUnit() { 101 | return ignoreMeasuringUnit; 102 | } 103 | 104 | public boolean getAutoBump() { 105 | return autoBump; 106 | } 107 | 108 | public boolean getTwoSided() { 109 | return twoSided; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/ProgressListener.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | /** 4 | * Progress listener 5 | */ 6 | public interface ProgressListener { 7 | public void onProgress(int progress); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/ScratchAnimationTest.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | import com.jme3.animation.*; 4 | import com.jme3.app.SimpleApplication; 5 | import com.jme3.math.ColorRGBA; 6 | import com.jme3.math.Quaternion; 7 | import com.jme3.math.Vector3f; 8 | import com.jme3.scene.Geometry; 9 | import com.jme3.scene.Mesh; 10 | import com.jme3.scene.Node; 11 | import com.jme3.scene.VertexBuffer; 12 | import com.jme3.scene.VertexBuffer.Format; 13 | import com.jme3.scene.VertexBuffer.Type; 14 | import com.jme3.scene.VertexBuffer.Usage; 15 | 16 | import java.nio.ByteBuffer; 17 | import java.nio.FloatBuffer; 18 | import java.util.HashMap; 19 | 20 | public class ScratchAnimationTest extends SimpleApplication { 21 | public static void main(String[] args) { 22 | new ScratchAnimationTest().start(); 23 | } 24 | 25 | private Mesh debmesh; 26 | private int loop; 27 | 28 | public static void printFloatBuffer(String label, VertexBuffer buffer) { 29 | FloatBuffer data = (FloatBuffer) buffer.getData(); 30 | data.rewind(); 31 | System.out.print(label); 32 | while (data.hasRemaining()) { 33 | System.out.print(data.get() + " "); 34 | } 35 | System.out.println(""); 36 | data.rewind(); 37 | } 38 | 39 | @Override 40 | public void simpleInitApp() { 41 | renderer.setBackgroundColor(ColorRGBA.Gray); 42 | float[] normals = {0, 0, 1, 0, 0, 1, 0, 0, 1}; 43 | float[] positions = {-1, 0, 0, 1, 0, 0, 0, 2, 0}; 44 | int[] indices = {0, 1, 2}; 45 | float[] weights = {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}; //just v2 affected by bone 0 46 | byte[] bones = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; //all v bound to bone 0 47 | 48 | Mesh mesh = new Mesh(); 49 | mesh.setBuffer(Type.Position, 3, positions.clone()); 50 | mesh.setBuffer(Type.Normal, 3, normals.clone()); 51 | mesh.setBuffer(Type.Index, 1, indices.clone()); 52 | mesh.setBuffer(Type.BindPosePosition, 3, positions.clone()); 53 | mesh.setBuffer(Type.BindPoseNormal, 3, normals.clone()); 54 | 55 | VertexBuffer vertexWeightBuffer = new VertexBuffer(Type.BoneWeight); 56 | VertexBuffer boneIndexBuffer = new VertexBuffer(Type.BoneIndex); 57 | vertexWeightBuffer.setupData(Usage.CpuOnly, 4, Format.Float, FloatBuffer.wrap(weights)); 58 | boneIndexBuffer.setupData(Usage.CpuOnly, 4, Format.UnsignedByte, ByteBuffer.wrap(bones)); 59 | mesh.setBuffer(vertexWeightBuffer); 60 | mesh.setBuffer(boneIndexBuffer); 61 | 62 | Geometry geom = new Geometry("triangle", mesh); 63 | geom.setMaterial(assetManager.loadMaterial("/Common/Materials/RedColor.j3m")); 64 | 65 | Node node = new Node("geom owner"); 66 | node.attachChild(geom); 67 | 68 | Bone bone = new Bone("bone 0"); 69 | bone.setBindTransforms(new Vector3f(0, 0, 0), new Quaternion(Quaternion.IDENTITY), Vector3f.UNIT_XYZ); 70 | 71 | Skeleton skeleton = new Skeleton(new Bone[]{bone}); 72 | skeleton.setBindingPose(); 73 | skeleton.updateWorldVectors(); 74 | skeleton.resetAndUpdate(); 75 | skeleton.computeSkinningMatrices(); 76 | 77 | float[] times = {0, 10}; 78 | Vector3f[] translations = {new Vector3f(0, 0, 0), new Vector3f(0, 2, 0)}; 79 | Quaternion[] rotations = {new Quaternion(0, 0, 0, 1), new Quaternion(0, 0, 0, 1)}; 80 | BoneTrack boneTrack = new BoneTrack(0, times, translations, rotations); 81 | 82 | Animation boneAnim = new Animation("test animation", 3); 83 | boneAnim.setTracks(new BoneTrack[]{boneTrack}); 84 | 85 | final AnimControl anim = new AnimControl(skeleton); 86 | anim.setAnimations(new HashMap()); 87 | anim.addAnim(boneAnim); 88 | AnimChannel channel = anim.createChannel(); 89 | channel.setAnim("test animation"); 90 | channel.setLoopMode(LoopMode.Loop); 91 | channel.setSpeed(0.5f); 92 | 93 | node.addControl(anim); 94 | 95 | rootNode.attachChild(node); 96 | 97 | debmesh = geom.getMesh(); 98 | } 99 | 100 | @Override 101 | public void simpleUpdate(float tpf) { 102 | super.simpleUpdate(tpf); 103 | if (loop++ % 1000 == 0) { 104 | printFloatBuffer("Position: ", debmesh.getBuffer(Type.Position)); 105 | printFloatBuffer("BindPosition: ", debmesh.getBuffer(Type.BindPosePosition)); 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/TestNoApp.java: -------------------------------------------------------------------------------- 1 | package jme3dae; 2 | 3 | import com.jme3.asset.DesktopAssetManager; 4 | import com.jme3.asset.plugins.UrlLocator; 5 | import com.jme3.scene.Spatial; 6 | 7 | public class TestNoApp { 8 | 9 | public static void main(String[] args) { 10 | DesktopAssetManager dam = new DesktopAssetManager(); 11 | String base = "file:///home/pgi/3d models/"; 12 | dam.registerLoader(ColladaLoader.class, "dae"); 13 | dam.registerLocator(base, UrlLocator.class.getName()); 14 | Spatial loadModel = dam.loadModel(base + "triangle_anim.dae"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/ChannelTarget.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14; 2 | 3 | import com.jme3.math.FastMath; 4 | import com.jme3.math.Quaternion; 5 | import com.jme3.math.Transform; 6 | import com.jme3.math.Vector3f; 7 | import jme3dae.transformers.ValueTransformer; 8 | 9 | public abstract class ChannelTarget implements ValueTransformer { 10 | public static final ChannelTarget ROT_X_ANGLE = new ChannelTarget() { 11 | 12 | public TransformedValue transform(float[] value) { 13 | Transform[] result = null; 14 | if (value != null) { 15 | result = new Transform[value.length]; 16 | for (int i = 0; i < value.length; i++) { 17 | float angle = value[i]; 18 | result[i] = ChannelTarget.newTransform(0, 0, 0, FastMath.DEG_TO_RAD * angle, 0, 0, 1, 1, 1); 19 | } 20 | } 21 | return TransformedValue.create(result); 22 | } 23 | }; 24 | public static final ChannelTarget ROT_Y_ANGLE = new ChannelTarget() { 25 | 26 | public TransformedValue transform(float[] value) { 27 | Transform[] result = null; 28 | if (value != null) { 29 | result = new Transform[value.length]; 30 | for (int i = 0; i < value.length; i++) { 31 | float angle = value[i]; 32 | result[i] = ChannelTarget.newTransform(0, 0, 0, 0, FastMath.DEG_TO_RAD * angle, 0, 1, 1, 1); 33 | } 34 | } 35 | return TransformedValue.create(result); 36 | } 37 | }; 38 | public static final ChannelTarget ROT_Z_ANGLE = new ChannelTarget() { 39 | 40 | public TransformedValue transform(float[] value) { 41 | Transform[] result = null; 42 | if (value != null) { 43 | result = new Transform[value.length]; 44 | for (int i = 0; i < value.length; i++) { 45 | float angle = value[i]; 46 | result[i] = ChannelTarget.newTransform(0, 0, 0, 0, 0, FastMath.DEG_TO_RAD * angle, 1, 1, 1); 47 | } 48 | } 49 | return TransformedValue.create(result); 50 | } 51 | }; 52 | public static final ChannelTarget TRANSLATE = new ChannelTarget() { 53 | 54 | public TransformedValue transform(float[] value) { 55 | Transform[] result = null; 56 | if (value != null && value.length % 3 == 0) { 57 | result = new Transform[value.length / 3]; 58 | int index = 0; 59 | for (int i = 0; i < value.length; i += 3) { 60 | float x = value[i]; 61 | float y = value[i + 1]; 62 | float z = value[i + 2]; 63 | result[index] = ChannelTarget.newTransform(x, y, z, 0, 0, 0, 1, 1, 1); 64 | index++; 65 | } 66 | } 67 | return TransformedValue.create(result); 68 | } 69 | }; 70 | 71 | public static ChannelTarget forName(String targetName) { 72 | if (targetName.contains("/")) { 73 | targetName = targetName.substring(targetName.indexOf('/') + 1, targetName.length()); 74 | } 75 | if (targetName.equals("rotateX.ANGLE")) { 76 | return ROT_X_ANGLE; 77 | } else if (targetName.equals("rotateY.ANGLE")) { 78 | return ROT_Y_ANGLE; 79 | } else if (targetName.equals("rotateZ.ANGLE")) { 80 | return ROT_Z_ANGLE; 81 | } else if (targetName.equals("translate")) { 82 | return TRANSLATE; 83 | } else { 84 | return null; 85 | } 86 | } 87 | 88 | private static Transform newTransform(float tx, float ty, float tz, float rx, float ry, float rz, float sx, float sy, float sz) { 89 | Transform t = new Transform(); 90 | t.loadIdentity(); 91 | t.setRotation(new Quaternion().fromAngles(rx, ry, rz)); 92 | t.setScale(sx, sy, sz); 93 | t.setTranslation(tx, ty, tz); 94 | return t; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Collada 1.4 Base types and utilities. 3 | */ 4 | package jme3dae.collada14; 5 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/ColorRGBATransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14.transformers; 2 | 3 | import com.jme3.math.ColorRGBA; 4 | import jme3dae.transformers.ValueTransformer; 5 | 6 | /** 7 | * Transforms a 3 or 4 float color component list (values between 0 and 1) into a 8 | * JME3 color instance. Eg. 9 | *
 String color = "0 0 0";
10 |  * TransformedValue<ColorRGBA> value = ColorRGBATransformer.create().transform(color);
11 |  * ColorRGBA black = value.get()
12 | * An invalid string results in an undefined transformed value. 13 | *
 String color = "hello world";
14 |  * TransformedValue<ColorRGBA> value = ColorRGBATransformer.create().transform(color);
15 |  * value.isDefined() <- returns false
16 | * 17 | * @author pgi 18 | */ 19 | public class ColorRGBATransformer implements ValueTransformer { 20 | 21 | /** 22 | * Instance creator. 23 | * 24 | * @return a new ColorRGBATransformer instance. 25 | */ 26 | public static ColorRGBATransformer create() { 27 | return new ColorRGBATransformer(); 28 | } 29 | 30 | private ColorRGBATransformer() { 31 | } 32 | 33 | /** 34 | * Transforms a string into a ColorRGBA if possible. The string must have 35 | * 3 or 4 float values, each clamped to 0-1. 36 | * 37 | * @param value the string to transform 38 | * @return a TransformedValue holding a ColorRGBA if the transformation 39 | * succeded, undefined (isDefined() <- false) otherwise. 40 | */ 41 | public TransformedValue transform(String value) { 42 | ColorRGBA color = null; 43 | value = value.trim(); 44 | if (value.length() != 0) { 45 | String[] rgba = value.split(" "); 46 | if (rgba.length == 3 || rgba.length == 4) { 47 | try { 48 | float red = Float.parseFloat(rgba[0]); 49 | float green = Float.parseFloat(rgba[1]); 50 | float blue = Float.parseFloat(rgba[2]); 51 | float alpha = rgba.length == 3 ? 1 : Float.parseFloat(rgba[3]); 52 | color = new ColorRGBA(red, green, blue, alpha); 53 | } catch (Exception ex) { 54 | System.err.println("COLORGBATransformer: not a number"); 55 | } 56 | } 57 | } 58 | return TransformedValue.create(color); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/PolygonsTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14.transformers; 2 | 3 | import com.jme3.bounding.BoundingBox; 4 | import com.jme3.scene.Geometry; 5 | import com.jme3.scene.Mesh; 6 | 7 | import java.util.List; 8 | 9 | import jme3dae.DAENode; 10 | import jme3dae.collada14.ColladaSpec141.Names; 11 | import jme3dae.transformers.ValueTransformer.TransformedValue; 12 | import jme3dae.utilities.Bindings; 13 | import jme3dae.utilities.MeasuringUnit; 14 | import jme3dae.utilities.PolygonArrayTransformer; 15 | import jme3dae.utilities.Todo; 16 | import jme3dae.utilities.Tuple2; 17 | 18 | /** 19 | * Transformed a collada polygons element in a JME3 Geometry. 20 | * 21 | * @author pgi 22 | */ 23 | public class PolygonsTransformer extends GeometryTransformer, Geometry> { 24 | 25 | /** 26 | * Instance creator. 27 | * 28 | * @return a new PolygonsTransformer instance. 29 | */ 30 | public static PolygonsTransformer create() { 31 | return new PolygonsTransformer(); 32 | } 33 | 34 | private PolygonsTransformer() { 35 | } 36 | 37 | /** 38 | * Transforms a DAENode-Bindings pair in a JME3 geometry. The DAENode wraps a 39 | * collada polygons element, the bindings hold a collada bind_material element 40 | * under the BIND_MATERIAL string key. 41 | * 42 | * @param value a polygons-bind_material pair 43 | * @return a JME3 Geometry or an undefined value if the transformation fails. 44 | */ 45 | public TransformedValue transform(Tuple2 value) { 46 | Geometry geom = null; 47 | DAENode polys = value.getA(); 48 | Bindings bindings = value.getB(); 49 | Tuple2> inputData = super.getInputs(polys); 50 | int chunkSize = inputData.getA(); 51 | List inputs = inputData.getB(); 52 | PolygonData[] polygons = new PolygonData[polys.getAttribute(Names.COUNT, INTEGER).get()]; 53 | int polyIndex = 0; 54 | int[] pvalues = new int[0]; 55 | for (DAENode pNode : polys.getChildren(Names.P)) { 56 | int[] indices = pNode.getContent(INTEGER_LIST).get(); 57 | int vertexCount = indices.length / chunkSize; 58 | polygons[polyIndex] = PolygonData.create(vertexCount); 59 | polyIndex += 1; 60 | pvalues = super.merge(pvalues, indices); 61 | } 62 | for (InputShared inputShared : inputs) { 63 | inputShared.transferData(chunkSize, pvalues, polygons); 64 | } 65 | MeasuringUnit unit = polys.getRootNode().getParsedData(MeasuringUnit.class); 66 | TransformedValue> mesh = PolygonArrayTransformer.create().transform(Tuple2.create(unit, polygons)); 67 | if (mesh.isDefined()) { 68 | polys.setParsedData(mesh.get().getB()); 69 | polys.setParsedData(mesh.get().getA()); 70 | geom = new Geometry("model"); 71 | geom.setMesh(mesh.get().getA()); 72 | geom.setModelBound(new BoundingBox()); 73 | geom.updateModelBound(); 74 | applyMaterial(geom, polys, bindings); 75 | } else { 76 | Todo.task("cannot create mesh, check this out."); 77 | } 78 | Todo.implementThis(); 79 | return TransformedValue.create(geom); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/PolylistTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14.transformers; 2 | 3 | import com.jme3.bounding.BoundingBox; 4 | import com.jme3.scene.Geometry; 5 | import com.jme3.scene.Mesh; 6 | 7 | import java.util.List; 8 | 9 | import jme3dae.DAENode; 10 | import jme3dae.collada14.ColladaSpec141.Names; 11 | import jme3dae.transformers.ValueTransformer.TransformedValue; 12 | import jme3dae.utilities.Bindings; 13 | import jme3dae.utilities.MeasuringUnit; 14 | import jme3dae.utilities.PolygonArrayTransformer; 15 | import jme3dae.utilities.Todo; 16 | import jme3dae.utilities.Tuple2; 17 | 18 | /** 19 | * Transforms a collada polylist element in a JME3 geometry. 20 | * 21 | * @author pgi 22 | */ 23 | public class PolylistTransformer extends GeometryTransformer, Geometry> { 24 | 25 | /** 26 | * Instance creator. 27 | * 28 | * @return a new PolylistTransformer 29 | */ 30 | public static PolylistTransformer create() { 31 | return new PolylistTransformer(); 32 | } 33 | 34 | private PolylistTransformer() { 35 | } 36 | 37 | /** 38 | * Transforms a polylist-bind_material pair into a JME3 geometry. 39 | * 40 | * @param value a pair of values where the DAENode is a polylist collada node and 41 | * the bindings holds a bind_material DAENode instance under the key Names.BIND_MATERIAL. 42 | * @return a JME3 geometry or an undefined value if the transformation fails. 43 | */ 44 | public TransformedValue transform(Tuple2 value) { 45 | Geometry geom = null; 46 | DAENode poly = value.getA(); 47 | Bindings bindings = value.getB(); 48 | Tuple2> inputData = getInputs(poly); 49 | List inputs = inputData.getB(); 50 | int chunkSize = inputData.getA(); 51 | PolygonData[] polygons = new PolygonData[poly.getAttribute(Names.COUNT, INTEGER).get()]; 52 | TransformedValue vcount = poly.getChild(Names.VCOUNT).getContent(INTEGER_LIST); 53 | if (vcount.isDefined()) { 54 | int[] vc = vcount.get(); 55 | for (int i = 0; i < vc.length; i++) { 56 | polygons[i] = PolygonData.create(vc[i]); 57 | } 58 | TransformedValue pvalues = poly.getChild(Names.P).getContent(INTEGER_LIST); 59 | if (pvalues.isDefined()) { 60 | int[] p = pvalues.get(); 61 | for (InputShared inputShared : inputs) { 62 | inputShared.transferData(chunkSize, p, polygons); 63 | } 64 | MeasuringUnit unit = poly.getRootNode().getParsedData(MeasuringUnit.class); 65 | TransformedValue> mesh = PolygonArrayTransformer.create().transform(Tuple2.create(unit, polygons)); 66 | if (mesh.isDefined()) { 67 | poly.setParsedData(mesh.get().getB()); 68 | poly.setParsedData(mesh.get().getA()); 69 | geom = new Geometry("model"); 70 | geom.setMesh(mesh.get().getA()); 71 | geom.setModelBound(new BoundingBox()); 72 | geom.updateModelBound(); 73 | applyMaterial(geom, poly, bindings); 74 | } else { 75 | Todo.task("cannot generate mesh... why"); 76 | } 77 | } else { 78 | Todo.task("polylist has no p data"); 79 | } 80 | } else { 81 | Todo.task("polylist element has no vcount data."); 82 | } 83 | return TransformedValue.create(geom); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/SemanticTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14.transformers; 2 | 3 | import jme3dae.collada14.ColladaSpec141.Semantic; 4 | import jme3dae.transformers.ValueTransformer; 5 | import jme3dae.transformers.ValueTransformer.TransformedValue; 6 | import jme3dae.utilities.Todo; 7 | 8 | /** 9 | * Transforms a string into a Semantic value. 10 | * 11 | * @author pgi 12 | */ 13 | public class SemanticTransformer implements ValueTransformer { 14 | 15 | /** 16 | * Instance initializer. 17 | * 18 | * @return a new SemanticTransformer 19 | */ 20 | public static SemanticTransformer create() { 21 | return new SemanticTransformer(); 22 | } 23 | 24 | private SemanticTransformer() { 25 | } 26 | 27 | /** 28 | * Transforms the given string into a Semantic value 29 | * 30 | * @param value a string holding the text representation of a collada semantic 31 | * value 32 | * @return the semantic value or an undefined value if parsing fails. 33 | */ 34 | public TransformedValue transform(String value) { 35 | Semantic r = null; 36 | try { 37 | r = Semantic.valueOf(value.trim()); 38 | } catch (IllegalArgumentException ex) { 39 | Todo.task("implement semantic: " + value); 40 | } 41 | return TransformedValue.create(r); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/TextureElementTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14.transformers; 2 | 3 | import com.jme3.texture.Texture; 4 | import jme3dae.DAENode; 5 | import jme3dae.collada14.ColladaSpec141.Names; 6 | import jme3dae.transformers.ValueTransformer.TransformedValue; 7 | import jme3dae.utilities.Conditions; 8 | import jme3dae.utilities.Todo; 9 | import jme3dae.utilities.TransformerPack; 10 | 11 | /** 12 | * Transforms a texture collada element in a JME3 texture. 13 | * 14 | * @author pgi 15 | */ 16 | public class TextureElementTransformer implements TransformerPack { 17 | 18 | public static TextureElementTransformer create() { 19 | return new TextureElementTransformer(); 20 | } 21 | 22 | private TextureElementTransformer() { 23 | } 24 | 25 | /** 26 | * Takes a texture node and returns a jme3 texture instance. 27 | * 28 | * @param textureNode the texture node to transform 29 | * @return a jme3 texture or an undefined value if parsing fails. 30 | */ 31 | public TransformedValue transform(DAENode textureNode) { 32 | Texture texture = null; 33 | if (textureNode != null && textureNode.hasName(Names.TEXTURE)) { 34 | TransformedValue textureLink = textureNode.getAttribute(Names.TEXTURE, TEXT); 35 | Conditions.checkTrue(textureLink.isDefined()); 36 | 37 | DAENode textureLinkedNode = textureNode.getLinkedNode(textureLink.get()); 38 | if (textureLinkedNode.hasName(Names.NEWPARAM)) { 39 | DAENode samplerNode = textureLinkedNode.getChild(Names.SAMPLER2D); 40 | Conditions.checkTrue(samplerNode.hasName(Names.SAMPLER2D)); 41 | 42 | TransformedValue sourceLink = samplerNode.getChild(Names.SOURCE).getContent(TEXT); 43 | Conditions.checkTrue(sourceLink.isDefined()); 44 | 45 | DAENode surface = samplerNode.getLinkedNode(sourceLink.get()).getChild(Names.SURFACE); 46 | DAENode initFrom = surface.getChild(Names.INIT_FROM); 47 | Conditions.checkTrue(initFrom.hasName(Names.INIT_FROM)); 48 | 49 | TransformedValue imageLink = initFrom.getContent(TEXT); 50 | DAENode imageNode = initFrom.getLinkedNode(imageLink.get()); 51 | texture = imageNode.getParsedData(Texture.class); 52 | } else if (textureLinkedNode.hasName(Names.IMAGE)) { 53 | texture = textureLinkedNode.getParsedData(Texture.class); 54 | DAENode extra = textureNode.getChild(Names.EXTRA); 55 | DAENode tech = extra.getChild(Names.TECHNIQUE); 56 | if (tech.getAttribute(Names.PROFILE, TEXT).contains("MAYA")) { 57 | TransformedValue wrapU = tech.getChild("wrapU").getContent(BOOLEAN); 58 | TransformedValue wrapV = tech.getChild("wrapV").getContent(BOOLEAN); 59 | Todo.task("parse MAYA technique"); 60 | } 61 | Todo.implementThis(); 62 | } 63 | Todo.task("parse the remaining values, check for optional/missing nodes"); 64 | } 65 | return TransformedValue.create(texture); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/TrianglesTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14.transformers; 2 | 3 | import com.jme3.bounding.BoundingBox; 4 | import com.jme3.scene.Mesh; 5 | import jme3dae.utilities.Bindings; 6 | import jme3dae.utilities.Tuple2; 7 | import com.jme3.scene.Geometry; 8 | 9 | import java.util.List; 10 | 11 | import jme3dae.DAENode; 12 | import jme3dae.collada14.ColladaSpec141.Names; 13 | import jme3dae.utilities.Conditions; 14 | import jme3dae.utilities.MeasuringUnit; 15 | import jme3dae.utilities.PolygonArrayTransformer; 16 | import jme3dae.utilities.Todo; 17 | 18 | /** 19 | * Transforms a collada 1.4.1 triangles element into a JME3 Mesh. 20 | * 21 | * @author pgi 22 | */ 23 | public class TrianglesTransformer extends GeometryTransformer, Geometry> { 24 | 25 | /** 26 | * Instance creator 27 | * 28 | * @return a new TrianglesTransformer instance 29 | */ 30 | public static TrianglesTransformer create() { 31 | return new TrianglesTransformer(); 32 | } 33 | 34 | private TrianglesTransformer() { 35 | } 36 | 37 | /** 38 | * Transforms a triangles element in a jme3 geometry. 39 | * 40 | * @param value a daenode-bindings pair where the daenode wraps a triangles element 41 | * and the bindings contains informations required to parse the parameters that the 42 | * triangles element defines for the instance material bound to the geometry. 43 | * @return a jme3 geometry or an undefined value if the parsing fails. 44 | */ 45 | public TransformedValue transform(Tuple2 value) { 46 | Geometry geom = null; 47 | DAENode triangles = value.getA(); 48 | Bindings bindings = value.getB(); 49 | Tuple2> inputData = getInputs(triangles); 50 | List inputs = inputData.getB(); 51 | int chunkSize = inputData.getA(); 52 | TransformedValue triangleCount = triangles.getAttribute(Names.COUNT, INTEGER); 53 | Conditions.checkTrue(triangleCount.isDefined(), "Collada 1.4.1 requires triangle count attribute for triangle element"); 54 | 55 | TransformedValue primitives = triangles.getChild(Names.P).getContent(INTEGER_LIST); 56 | if (primitives.isDefined()) { 57 | int[] indices = primitives.get(); 58 | PolygonData[] polygons = new PolygonData[triangleCount.get()]; 59 | for (int i = 0; i < polygons.length; i++) { 60 | polygons[i] = PolygonData.create(3); 61 | } 62 | for (InputShared inputShared : inputs) { 63 | inputShared.transferData(chunkSize, indices, polygons); 64 | } 65 | MeasuringUnit unit = triangles.getRootNode().getParsedData(MeasuringUnit.class); 66 | TransformedValue> mesh = PolygonArrayTransformer.create().transform(Tuple2.create(unit, polygons)); 67 | if (mesh.isDefined()) { 68 | triangles.setParsedData(mesh.get().getB()); 69 | triangles.setParsedData(mesh.get().getA()); 70 | geom = new Geometry("model"); 71 | geom.setMesh(mesh.get().getA()); 72 | geom.setModelBound(new BoundingBox()); 73 | geom.updateModelBound(); 74 | applyMaterial(geom, triangles, bindings); 75 | } else { 76 | Todo.task("unable to generate mesh, AHHHH!"); 77 | } 78 | } else { 79 | Todo.task("triangles element has no p child. Maybe extra element contains data?"); 80 | } 81 | return TransformedValue.create(geom); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/Vector3fTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.collada14.transformers; 2 | 3 | import com.jme3.math.Vector3f; 4 | import jme3dae.transformers.ValueTransformer; 5 | import jme3dae.transformers.ValueTransformer.TransformedValue; 6 | 7 | /** 8 | * Transfors a list of three space separated float strings in a Vector3f 9 | * 10 | * @author pgi 11 | */ 12 | public class Vector3fTransformer implements ValueTransformer { 13 | 14 | /** 15 | * Instance creator. 16 | * 17 | * @return a new Vector3f transformer. 18 | */ 19 | public static Vector3fTransformer create() { 20 | return new Vector3fTransformer(); 21 | } 22 | 23 | private Vector3fTransformer() { 24 | } 25 | 26 | /** 27 | * Transforms a string (eg 0 1.34 3) in a Vector3f. 28 | * 29 | * @param value the string to transform 30 | * @return a new vector3f or an undefined value if the parsing fails. 31 | */ 32 | public TransformedValue transform(String value) { 33 | Vector3f v = null; 34 | if (value != null && (value.length() != 0)) { 35 | String[] c = value.split(" "); 36 | if (c.length == 3) { 37 | try { 38 | float x = Float.parseFloat(c[0]); 39 | float y = Float.parseFloat(c[1]); 40 | float z = Float.parseFloat(c[2]); 41 | v = new Vector3f(x, y, z); 42 | } catch (NumberFormatException ex) { 43 | System.err.println("Vector3fTransformer: not a float string"); 44 | } 45 | } 46 | } 47 | return TransformedValue.create(v); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/collada14/transformers/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the transformed used to parse a Collada document v. 1.4 3 | */ 4 | package jme3dae.collada14.transformers; 5 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/AbstractMaterialGenerator.java: -------------------------------------------------------------------------------- 1 | package jme3dae.materials; 2 | 3 | import com.jme3.asset.AssetManager; 4 | import com.jme3.material.Material; 5 | import com.jme3.math.ColorRGBA; 6 | import com.jme3.texture.Texture; 7 | 8 | public class AbstractMaterialGenerator { 9 | protected final AssetManager ASSET_MANAGER; 10 | protected final Material MATERIAL; 11 | 12 | protected AbstractMaterialGenerator(AssetManager am, Material mat) { 13 | ASSET_MANAGER = am; 14 | MATERIAL = mat; 15 | } 16 | 17 | public Material get() { 18 | return MATERIAL; 19 | } 20 | 21 | public void setAmbient(ColorRGBA color) { 22 | //if(color != null) { 23 | // MATERIAL.setColor("ambientColor", color); 24 | // MATERIAL.setBoolean("useAmbientColor", true); 25 | //} 26 | if (color != null) { 27 | MATERIAL.setBoolean("UseMaterialColors", true); 28 | MATERIAL.setColor("Ambient", color); 29 | } 30 | } 31 | 32 | public void setAmbient(Texture texture) { 33 | if (texture != null) { 34 | MATERIAL.setTexture("ambientTexture", texture); 35 | MATERIAL.setBoolean("useAmbientTexture", true); 36 | } 37 | } 38 | 39 | public void setDiffuse(ColorRGBA color) { 40 | if (color != null) { 41 | MATERIAL.setColor("diffuseColor", color); 42 | MATERIAL.setBoolean("useDiffuseColor", true); 43 | } 44 | } 45 | 46 | public void setDiffuse(Texture texture) { 47 | if (texture != null) { 48 | MATERIAL.setTexture("diffuseTexture", texture); 49 | MATERIAL.setBoolean("useDiffuseTexture", true); 50 | } 51 | } 52 | 53 | public void setSpecular(ColorRGBA color) { 54 | if (color != null) { 55 | MATERIAL.setColor("specularColor", color); 56 | MATERIAL.setBoolean("useSpecularColor", true); 57 | } 58 | } 59 | 60 | public void setSpecular(Texture texture) { 61 | if (texture != null) { 62 | MATERIAL.setTexture("specularTexture", texture); 63 | MATERIAL.setBoolean("useSpecularTexture", true); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/BlinnMaterialGenerator.java: -------------------------------------------------------------------------------- 1 | package jme3dae.materials; 2 | 3 | import com.jme3.asset.AssetManager; 4 | import com.jme3.material.Material; 5 | import com.jme3.math.ColorRGBA; 6 | import com.jme3.texture.Texture; 7 | 8 | /** 9 | * Utility class to fill a JME3 material with the values defined in a collada blinn material node. 10 | * 11 | * @author pgi 12 | */ 13 | public class BlinnMaterialGenerator extends FXBumpMaterialGenerator { 14 | 15 | /** 16 | * Instance creator. 17 | * 18 | * @param am the AssetManager used to generate the jme3 material. 19 | * @return a new BlinnMaterialGenerator instance. 20 | */ 21 | public static BlinnMaterialGenerator create(AssetManager am) { 22 | return new BlinnMaterialGenerator(am); 23 | } 24 | 25 | private BlinnMaterialGenerator(AssetManager am) { 26 | //super(am, new Material(am, "jme3dae/materials/ColladaBlinn.j3md")); 27 | super(am, "blinn"); 28 | } 29 | 30 | public void setSpecular(ColorRGBA color) { 31 | if (color != null) { 32 | MATERIAL.setBoolean("UseMaterialColors", true); 33 | MATERIAL.setColor("Specular", color); 34 | } 35 | } 36 | 37 | public void setSpecular(Texture texture) { 38 | if (texture != null) { 39 | MATERIAL.setTexture("SpecularMap", texture); 40 | MATERIAL.setBoolean("UseMaterialColors", false); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/ColladaBlinn.j3md: -------------------------------------------------------------------------------- 1 | MaterialDef ColladaBlinn { 2 | 3 | MaterialParameters { 4 | Boolean useAmbientColor 5 | Boolean useDiffuseColor 6 | Boolean useEmissiveColor 7 | Boolean useAmbientTexture 8 | Boolean useDiffuseTexture 9 | Boolean useEmissiveTexture 10 | Color ambientColor 11 | Color diffuseColor 12 | Color emissiveColor 13 | Texture2D ambientTexture 14 | Texture2D diffuseTexture 15 | Texture2D emissiveTexture 16 | } 17 | 18 | Technique { 19 | VertexShader GLSL100: jme3dae/materials/collada_blinn.vert 20 | FragmentShader GLSL100: jme3dae/materials/collada_blinn.frag 21 | 22 | WorldParameters { 23 | WorldViewProjectionMatrix 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/ColladaConstant.j3md: -------------------------------------------------------------------------------- 1 | MaterialDef ColladaConstant { 2 | 3 | MaterialParameters { 4 | Boolean useEmissiveColor 5 | Color emissiveColor 6 | Boolean useEmissiveTexture 7 | Texture2D emissiveTexture 8 | Boolean useReflectiveColor 9 | Color reflectiveColor 10 | Boolean useReflectiveTexture 11 | Texture2D reflectiveTexture 12 | Boolean useReflectivity 13 | Float reflectivity 14 | Boolean useTransparentColor 15 | Color transparentColor 16 | Boolean useTransparentTexture 17 | Texture2D transparentTexture 18 | Boolean useIndexOfRefraction 19 | Float indexOfRefraction 20 | } 21 | 22 | Technique { 23 | VertexShader GLSL100: jme3dae/materials/collada_constant.vert 24 | FragmentShader GLSL100: jme3dae/materials/collada_constant.frag 25 | 26 | WorldParameters { 27 | WorldViewProjectionMatrix 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/ColladaLambert.j3md: -------------------------------------------------------------------------------- 1 | MaterialDef ColladaLambert { 2 | 3 | MaterialParameters { 4 | Boolean useAmbientColor 5 | Boolean useDiffuseColor 6 | Boolean useEmissiveColor 7 | Boolean useAmbientTexture 8 | Boolean useDiffuseTexture 9 | Boolean useEmissiveTexture 10 | Color ambientColor 11 | Color diffuseColor 12 | Color emissiveColor 13 | Texture2D ambientTexture 14 | Texture2D diffuseTexture 15 | Texture2D emissiveTexture 16 | } 17 | 18 | Technique { 19 | VertexShader GLSL100: jme3dae/materials/collada_lambert.vert 20 | FragmentShader GLSL100: jme3dae/materials/collada_lambert.frag 21 | 22 | WorldParameters { 23 | WorldViewProjectionMatrix 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/ColladaPhong.j3md: -------------------------------------------------------------------------------- 1 | MaterialDef ColladaPhong { 2 | 3 | MaterialParameters { 4 | Boolean useAmbientColor 5 | Boolean useDiffuseColor 6 | Boolean useEmissiveColor 7 | Boolean useAmbientTexture 8 | Boolean useDiffuseTexture 9 | Boolean useEmissiveTexture 10 | Color ambientColor 11 | Color diffuseColor 12 | Color emissiveColor 13 | Texture2D ambientTexture 14 | Texture2D diffuseTexture 15 | Texture2D emissiveTexture 16 | } 17 | 18 | Technique { 19 | VertexShader GLSL100: jme3dae/materials/collada_phong.vert 20 | FragmentShader GLSL100: jme3dae/materials/collada_phong.frag 21 | 22 | WorldParameters { 23 | WorldViewProjectionMatrix 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/ConstantMaterialGenerator.java: -------------------------------------------------------------------------------- 1 | package jme3dae.materials; 2 | 3 | import com.jme3.asset.AssetManager; 4 | import com.jme3.material.Material; 5 | 6 | public class ConstantMaterialGenerator extends FXBumpMaterialGenerator { 7 | 8 | public static ConstantMaterialGenerator create(AssetManager am) { 9 | return new ConstantMaterialGenerator(am); 10 | } 11 | 12 | private ConstantMaterialGenerator(AssetManager am) { 13 | //super(am, new Material(am, "jme3dae/materials/ColladaConstant.j3md")); 14 | super(am, "const"); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/LambertMaterialGenerator.java: -------------------------------------------------------------------------------- 1 | package jme3dae.materials; 2 | 3 | import com.jme3.asset.AssetManager; 4 | import com.jme3.material.Material; 5 | 6 | /** 7 | * Utility class to fill a JME3 material with the values defined in a collada lambert material node. 8 | * 9 | * @author pgi 10 | */ 11 | public class LambertMaterialGenerator extends FXBumpMaterialGenerator { 12 | 13 | /** 14 | * Instance creator 15 | * 16 | * @param am the asset manager used to create the jme3 material 17 | * @return a new LamberMaterialGenerator 18 | */ 19 | public static LambertMaterialGenerator create(AssetManager am) { 20 | return new LambertMaterialGenerator(am); 21 | } 22 | 23 | private LambertMaterialGenerator(AssetManager am) { 24 | //super(am, new Material(am, "jme3dae/materials/ColladaLambert.j3md")); 25 | super(am, "lambert"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/PhongMaterialGenerator.java: -------------------------------------------------------------------------------- 1 | package jme3dae.materials; 2 | 3 | import com.jme3.asset.AssetManager; 4 | import com.jme3.material.Material; 5 | import com.jme3.math.ColorRGBA; 6 | import com.jme3.texture.Texture; 7 | 8 | /** 9 | * Utility class that generates a JME3 material to map a phong material 10 | * 11 | * @author pgi 12 | */ 13 | public class PhongMaterialGenerator extends FXBumpMaterialGenerator { 14 | 15 | /** 16 | * Instance creator 17 | * 18 | * @param am the asset manager for the jme3 material 19 | * @return a new PhongMaterialGenerator 20 | */ 21 | public static PhongMaterialGenerator create(AssetManager am) { 22 | return new PhongMaterialGenerator(am); 23 | } 24 | 25 | /** 26 | * Instance initializer 27 | * 28 | * @param am the asset manager for the generated material 29 | */ 30 | private PhongMaterialGenerator(AssetManager am) { 31 | //super(am, new Material(am, "jme3dae/materials/ColladaPhong.j3md")); 32 | super(am, "phong"); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_blinn.frag: -------------------------------------------------------------------------------- 1 | uniform bool useAmbientColor; 2 | uniform bool useDiffuseColor; 3 | uniform bool useEmissiveColor; 4 | uniform bool useAmbientTexture; 5 | uniform bool useDiffuseTexture; 6 | uniform bool useEmissiveTexture; 7 | uniform vec4 ambientColor; 8 | uniform vec4 diffuseColor; 9 | uniform vec4 emissiveColor; 10 | uniform sampler2D ambientTexture; 11 | uniform sampler2D diffuseTexture; 12 | uniform sampler2D emissiveTexture; 13 | 14 | varying vec2 texCoord; 15 | 16 | void main() { 17 | 18 | vec4 diffuse; 19 | vec4 emissive; 20 | vec4 ambient; 21 | 22 | if(useAmbientColor) { 23 | ambient = ambientColor; 24 | } 25 | if(useDiffuseColor) { 26 | diffuse = diffuseColor; 27 | } 28 | if(useEmissiveColor) { 29 | emissive = emissiveColor; 30 | } 31 | if(useAmbientTexture) { 32 | vec4 texColor = texture2D(ambientTexture, texCoord); 33 | if(useAmbientColor) { 34 | ambient = vec4(mix(ambient.rgb, texColor.rgb, texColor.a), 1); 35 | } else { 36 | ambient = texColor; 37 | } 38 | } 39 | if(useDiffuseTexture) { 40 | vec4 texColor = texture2D(diffuseTexture, texCoord); 41 | if(useDiffuseColor) { 42 | diffuse = vec4(mix(diffuse.rgb, texColor.rgb, texColor.a), 1); 43 | } else { 44 | diffuse = texColor; 45 | } 46 | } 47 | if(useEmissiveTexture) { 48 | vec4 texColor = texture2D(emissiveTexture, texCoord); 49 | if(useEmissiveColor) { 50 | diffuse = vec4(mix(emissive.rgb, texColor.rgb, texColor.a), 1); 51 | } else { 52 | emissive = texColor; 53 | } 54 | } 55 | vec4 finalColor = mix(ambient, diffuse, 0.5); 56 | finalColor = mix(finalColor, emissive, 0.5); 57 | gl_FragColor = finalColor; 58 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_blinn.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 g_WorldViewProjectionMatrix; 2 | attribute vec3 inPosition; 3 | attribute vec2 inTexCoord; 4 | varying vec2 texCoord; 5 | 6 | void main() { 7 | gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); 8 | texCoord = inTexCoord; 9 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_constant.frag: -------------------------------------------------------------------------------- 1 | uniform bool useEmissiveColor; 2 | uniform vec4 emissiveColor; 3 | 4 | uniform bool useEmissiveTexture; 5 | uniform sampler2D emissiveTexture; 6 | 7 | uniform bool useReflectiveColor; 8 | uniform vec4 reflectiveColor; 9 | 10 | uniform bool useReflectiveTexture; 11 | uniform sampler2D reflectiveTexture; 12 | 13 | uniform bool useReflectivity; 14 | uniform float reflectivity; 15 | 16 | uniform bool useTransparentColor; 17 | uniform vec4 transparentColor; 18 | 19 | uniform bool useTransparentTexture; 20 | uniform sampler2D transparentTexture; 21 | 22 | uniform bool useIndexOfRefraction; 23 | uniform float indexOfRefraction; 24 | 25 | varyng vec2 texCoord; 26 | 27 | voic main() { 28 | 29 | vec4 diffuse = vec4(0, 0, 0, 0); 30 | 31 | if(useEmissiveColor) { 32 | diffuse += emissiveColor; 33 | } 34 | 35 | if(useTransparentColor) { 36 | diffuse += transparentColor; 37 | } 38 | 39 | if(useEmissiveTexture) { 40 | diffuse += texture2D(emissiveTexture, texCoord); 41 | } 42 | 43 | glFragColor = diffuse; 44 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_constant.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 g_WorldViewProjectionMatrix; 2 | attribute vec3 inPosition; 3 | attribute vec2 inTexCoord; 4 | varying vec2 texCoord; 5 | 6 | void main() { 7 | gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); 8 | texCoord = inTexCoord; 9 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_lambert.frag: -------------------------------------------------------------------------------- 1 | uniform bool useAmbientColor; 2 | uniform bool useDiffuseColor; 3 | uniform bool useEmissiveColor; 4 | uniform bool useAmbientTexture; 5 | uniform bool useDiffuseTexture; 6 | uniform bool useEmissiveTexture; 7 | uniform vec4 ambientColor; 8 | uniform vec4 diffuseColor; 9 | uniform vec4 emissiveColor; 10 | uniform sampler2D ambientTexture; 11 | uniform sampler2D diffuseTexture; 12 | uniform sampler2D emissiveTexture; 13 | 14 | varying vec2 texCoord; 15 | 16 | void main() { 17 | 18 | vec4 diffuse; 19 | vec4 emissive; 20 | vec4 ambient; 21 | 22 | if(useAmbientColor) { 23 | ambient = ambientColor; 24 | } 25 | if(useDiffuseColor) { 26 | diffuse = diffuseColor; 27 | } 28 | if(useEmissiveColor) { 29 | emissive = emissiveColor; 30 | } 31 | if(useAmbientTexture) { 32 | vec4 texColor = texture2D(ambientTexture, texCoord); 33 | if(useAmbientColor) { 34 | ambient = vec4(mix(ambient.rgb, texColor.rgb, texColor.a), 1); 35 | } else { 36 | ambient = texColor; 37 | } 38 | } 39 | if(useDiffuseTexture) { 40 | vec4 texColor = texture2D(diffuseTexture, texCoord); 41 | if(useDiffuseColor) { 42 | diffuse = vec4(mix(diffuse.rgb, texColor.rgb, texColor.a), 1); 43 | } else { 44 | diffuse = texColor; 45 | } 46 | } 47 | if(useEmissiveTexture) { 48 | vec4 texColor = texture2D(emissiveTexture, texCoord); 49 | if(useEmissiveColor) { 50 | diffuse = vec4(mix(emissive.rgb, texColor.rgb, texColor.a), 1); 51 | } else { 52 | emissive = texColor; 53 | } 54 | } 55 | vec4 finalColor = mix(ambient, diffuse, 0.5); 56 | finalColor = mix(finalColor, emissive, 0.5); 57 | gl_FragColor = finalColor; 58 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_lambert.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 g_WorldViewProjectionMatrix; 2 | attribute vec3 inPosition; 3 | attribute vec2 inTexCoord; 4 | varying vec2 texCoord; 5 | 6 | void main() { 7 | gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); 8 | texCoord = inTexCoord; 9 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_phong.frag: -------------------------------------------------------------------------------- 1 | uniform bool useAmbientColor; 2 | uniform bool useDiffuseColor; 3 | uniform bool useEmissiveColor; 4 | uniform bool useAmbientTexture; 5 | uniform bool useDiffuseTexture; 6 | uniform bool useEmissiveTexture; 7 | uniform vec4 ambientColor; 8 | uniform vec4 diffuseColor; 9 | uniform vec4 emissiveColor; 10 | uniform sampler2D ambientTexture; 11 | uniform sampler2D diffuseTexture; 12 | uniform sampler2D emissiveTexture; 13 | 14 | varying vec2 texCoord; 15 | 16 | void main() { 17 | 18 | vec4 diffuse; 19 | vec4 emissive; 20 | vec4 ambient; 21 | 22 | if(useAmbientColor) { 23 | ambient = ambientColor; 24 | } 25 | if(useDiffuseColor) { 26 | diffuse = diffuseColor; 27 | } 28 | if(useEmissiveColor) { 29 | emissive = emissiveColor; 30 | } 31 | if(useAmbientTexture) { 32 | vec4 texColor = texture2D(ambientTexture, texCoord); 33 | if(useAmbientColor) { 34 | ambient = vec4(mix(ambient.rgb, texColor.rgb, texColor.a), 1); 35 | } else { 36 | ambient = texColor; 37 | } 38 | } 39 | if(useDiffuseTexture) { 40 | vec4 texColor = texture2D(diffuseTexture, texCoord); 41 | if(useDiffuseColor) { 42 | diffuse = vec4(mix(diffuse.rgb, texColor.rgb, texColor.a), 1); 43 | } else { 44 | diffuse = texColor; 45 | } 46 | } 47 | if(useEmissiveTexture) { 48 | vec4 texColor = texture2D(emissiveTexture, texCoord); 49 | if(useEmissiveColor) { 50 | diffuse = vec4(mix(emissive.rgb, texColor.rgb, texColor.a), 1); 51 | } else { 52 | emissive = texColor; 53 | } 54 | } 55 | vec4 finalColor = mix(ambient, diffuse, 0.5); 56 | finalColor = mix(finalColor, emissive, 0.5); 57 | gl_FragColor = finalColor; 58 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/collada_phong.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 g_WorldViewProjectionMatrix; 2 | attribute vec3 inPosition; 3 | attribute vec2 inTexCoord; 4 | varying vec2 texCoord; 5 | 6 | void main() { 7 | gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); 8 | texCoord = inTexCoord; 9 | } -------------------------------------------------------------------------------- /src/main/java/jme3dae/materials/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Types and resources that maps collada materials to JME3 materials. 3 | */ 4 | package jme3dae.materials; 5 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Core elements of the jme3 collada loader. 3 | */ 4 | package jme3dae; 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/transformers/ValueTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.transformers; 2 | 3 | /** 4 | * A function that transforms a value in some other value 5 | * 6 | * @param the value type to be transformed 7 | * @param the type of the transformed value 8 | * @author pgi 9 | */ 10 | public interface ValueTransformer { 11 | 12 | /** 13 | * Transforms a value 14 | * 15 | * @param value the value to transform 16 | * @return the transformed value 17 | */ 18 | TransformedValue transform(V value); 19 | 20 | /** 21 | * Result of the transformation of some value 22 | * 23 | * @param the type of the transformed value 24 | */ 25 | class TransformedValue { 26 | 27 | /** 28 | * Instance creator 29 | * 30 | * @param the type of the transformed value 31 | * @param value the value held by the new transformed value. Can be null. 32 | * @return a new TransformedValue 33 | */ 34 | public static TransformedValue create(T value) { 35 | return new TransformedValue(value); 36 | } 37 | 38 | private final T value; 39 | 40 | /** 41 | * Initializes this instance 42 | * 43 | * @param value the value assigned to this TransformedValue 44 | */ 45 | protected TransformedValue(T value) { 46 | this.value = value; 47 | } 48 | 49 | /** 50 | * Returns true if the value is != null 51 | * 52 | * @return true if the value is not null 53 | */ 54 | public boolean isDefined() { 55 | return value != null; 56 | } 57 | 58 | /** 59 | * Returns the value held by this TransformedValue. Null iff isDefined == false 60 | * 61 | * @return the value held by this TransformedValue 62 | */ 63 | public T get() { 64 | return value; 65 | } 66 | 67 | /** 68 | * Returns true if this TransformedValue is defined and the held value 69 | * equals the given one. 70 | * 71 | * @param value the value to check agains. 72 | * @return true or false? 73 | */ 74 | public boolean contains(T value) { 75 | return isDefined() && this.value.equals(value); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/transformers/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Parsers framework: just a function+value definition. 3 | */ 4 | package jme3dae.transformers; 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/ArrayTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.DAENode; 4 | import jme3dae.collada14.ColladaSpec141.Names; 5 | import jme3dae.transformers.ValueTransformer; 6 | 7 | import java.util.function.Function; 8 | import java.util.function.IntFunction; 9 | import java.util.regex.Pattern; 10 | import java.util.stream.Stream; 11 | 12 | /** 13 | * Transforms a node to an array of element T 14 | * 15 | * @author Angel Chang 16 | */ 17 | public class ArrayTransformer implements TransformerPack { 18 | protected Function transfomerFunction; 19 | protected ValueTransformer transformer; 20 | protected IntFunction arrayCreator; 21 | 22 | public ArrayTransformer(ValueTransformer transformer, IntFunction arrayCreator) { 23 | this.transformer = transformer; 24 | this.arrayCreator = arrayCreator; 25 | this.transfomerFunction = s -> transformer.transform(s).get(); 26 | } 27 | 28 | private static final Pattern whitespacePattern = Pattern.compile("\\w+"); 29 | @Override 30 | public TransformedValue transform(DAENode node) { 31 | TransformedValue text = node.getContent(TEXT); 32 | TransformedValue count = node.getAttribute(Names.COUNT, INTEGER); 33 | Stream stream = whitespacePattern.splitAsStream(text.get()); 34 | T[] elements = stream.map(transfomerFunction).toArray(arrayCreator); 35 | return TransformedValue.create(elements); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/Bindings.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * A value holder. Currently it is used to bind material symbols to 8 | * material values. 9 | * 10 | * @author pgi 11 | */ 12 | public class Bindings { 13 | 14 | /** 15 | * Instance creator. 16 | * 17 | * @return a new Bindings. 18 | */ 19 | public static Bindings create() { 20 | return new Bindings(); 21 | } 22 | 23 | private final Map MAP = new HashMap(); 24 | 25 | private Bindings() { 26 | } 27 | 28 | /** 29 | * Put a key-value pair in the bindings 30 | * 31 | * @param key the key 32 | * @param value the value 33 | */ 34 | public void put(String key, Object value) { 35 | MAP.put(key, value); 36 | } 37 | 38 | /** 39 | * Returns a value given a key 40 | * 41 | * @param the type of the value to get 42 | * @param key the key of the value 43 | * @param type the type of the requested value 44 | * @return the typed value associated to key or null if no such value 45 | * exists or the value type is not <: T 46 | */ 47 | public T get(String key, Class type) { 48 | T result = null; 49 | Object value = MAP.get(key); 50 | if (value != null && type.isAssignableFrom(value.getClass())) { 51 | result = type.cast(value); 52 | } 53 | return result; 54 | } 55 | 56 | /** 57 | * Checks if this bindings contains a value associated with the given key 58 | * of type V <: T 59 | * 60 | * @param the type of the value to check 61 | * @param key the key 62 | * @param type the type of the value 63 | * @return true if there is a value under the given key of type V <: T 64 | */ 65 | public boolean contains(String key, Class type) { 66 | return get(key, type) != null; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/BooleanListTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.transformers.ValueTransformer; 4 | import jme3dae.transformers.ValueTransformer.TransformedValue; 5 | 6 | /** 7 | * Transforms a string-boolean list into a boolean array 8 | * 9 | * @author pgi 10 | */ 11 | public class BooleanListTransformer implements ValueTransformer { 12 | 13 | /** 14 | * Instance creator. 15 | * 16 | * @return a new BooleanListTransformer 17 | */ 18 | public static BooleanListTransformer create() { 19 | return new BooleanListTransformer(); 20 | } 21 | 22 | private BooleanListTransformer() { 23 | } 24 | 25 | /** 26 | * Transforms a string in a list of booleans 27 | * 28 | * @param value a string value, can be null. 29 | * @return an array of boolean or an undefined value if parsing is not possible. 30 | */ 31 | public TransformedValue transform(String value) { 32 | boolean[] result = null; 33 | if (value != null) { 34 | value = value.trim(); 35 | String[] data = value.split(" "); 36 | result = new boolean[data.length]; 37 | for (int i = 0; i < data.length; i++) { 38 | String string = data[i]; 39 | if ("true".equals(string)) { 40 | result[i] = true; 41 | } else if ("false".equals(string)) { 42 | result[i] = false; 43 | } else { 44 | result = null; 45 | break; 46 | } 47 | } 48 | } 49 | return TransformedValue.create(result); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/BooleanTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.transformers.ValueTransformer; 4 | import jme3dae.transformers.ValueTransformer.TransformedValue; 5 | 6 | /** 7 | * Transforms a string in boolean 8 | * 9 | * @author pgi 10 | */ 11 | public class BooleanTransformer implements ValueTransformer { 12 | 13 | /** 14 | * Instance creator 15 | * 16 | * @return a new BooleanTransformer instance 17 | */ 18 | public static BooleanTransformer create() { 19 | return new BooleanTransformer(); 20 | } 21 | 22 | private BooleanTransformer() { 23 | } 24 | 25 | /** 26 | * Transforms a string in a boolean 27 | * 28 | * @param value a string, maybe null 29 | * @return a boolean or an undefined value if parsing fails 30 | */ 31 | public TransformedValue transform(String value) { 32 | Boolean result = null; 33 | if (value != null) { 34 | value = value.trim(); 35 | if ("true".equalsIgnoreCase(value)) { 36 | result = Boolean.TRUE; 37 | } else if ("false".equalsIgnoreCase(value)) { 38 | result = Boolean.FALSE; 39 | } 40 | } 41 | return TransformedValue.create(result); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/Conditions.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import java.util.logging.Level; 4 | import java.util.logging.Logger; 5 | 6 | /** 7 | * Utility class to stop program when a condition fails. Used for debugging. 8 | * 9 | * @author pgi 10 | */ 11 | public class Conditions { 12 | 13 | private Conditions() { 14 | } 15 | 16 | private static final Conditions INSTANCE = new Conditions(); 17 | 18 | /** 19 | * The runtime exception thrown by this utility when a check fails. 20 | */ 21 | public static class CheckException extends RuntimeException { 22 | 23 | public CheckException(String message) { 24 | super(message); 25 | } 26 | } 27 | 28 | /** 29 | * Null check 30 | * 31 | * @param value the value to check 32 | * @throws jme3dae.utilities.Conditions.CheckException if value is null 33 | */ 34 | public static void checkNotNull(Object value) throws CheckException { 35 | if (value == null) { 36 | signalError("Expected not null value."); 37 | } 38 | } 39 | 40 | /** 41 | * True check 42 | * 43 | * @param expr the value to check 44 | * @throws jme3dae.utilities.Conditions.CheckException if expr is false 45 | */ 46 | public static void checkTrue(boolean expr) throws CheckException { 47 | if (!expr) { 48 | signalError("expected true"); 49 | } 50 | } 51 | 52 | /** 53 | * /** 54 | * 55 | * @param value 56 | * @param expected 57 | * @throws jme3dae.utilities.Conditions.CheckException 58 | */ 59 | public static void checkValue(int value, int expected) throws CheckException { 60 | if (value != expected) { 61 | signalError("Expected " + expected + " got " + value); 62 | } 63 | } 64 | 65 | /** 66 | * True check 67 | * 68 | * @param expr the value to check 69 | * @param errorMessage the message to emit if expr is false 70 | * @throws jme3dae.utilities.Conditions.CheckException if expr is false 71 | */ 72 | public static void checkTrue(boolean expr, String errorMessage) throws CheckException { 73 | if (!expr) { 74 | signalError(errorMessage); 75 | } 76 | } 77 | 78 | /** 79 | * False check 80 | * 81 | * @param expr the value to check 82 | * @param errorMessage the message to emit if expr is true 83 | * @throws jme3dae.utilities.Conditions.CheckException if expr is true 84 | */ 85 | public static void checkFalse(boolean expr, String errorMessage) throws CheckException { 86 | if (expr) { 87 | signalError(errorMessage); 88 | } 89 | } 90 | 91 | private static void signalError(String errorMessage) { 92 | if (errorMessage == null) { 93 | errorMessage = ""; 94 | } 95 | String log = "Condition failure"; 96 | StackTraceElement[] stack = Thread.currentThread().getStackTrace(); 97 | if (stack.length >= 3) { 98 | log += "\nAt: " + stack[3]; 99 | } 100 | Logger.getLogger(Conditions.class.getName()).log(Level.SEVERE, log); 101 | if (errorMessage == null) { 102 | errorMessage = ""; 103 | } 104 | throw new CheckException(errorMessage); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/FileType.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * A couple of file types, used in case the COLLADA document stores image data 5 | * in line (as hex byte sequence). 6 | * 7 | * @author pgi 8 | */ 9 | public enum FileType { 10 | 11 | /** 12 | * Unknown type 13 | */ 14 | UNKNOWNW("unknown"), 15 | 16 | /** 17 | * BMP image 18 | */ 19 | BMP("bmp", 0x42, 0x4D), 20 | 21 | /** 22 | * GIF89A image 23 | */ 24 | GIF89A("gif", 0x47, 0x49, 0x46, 0x38, 0x39, 0x61), 25 | 26 | /** 27 | * GIF87A image 28 | */ 29 | GIF87A("gif", 0x47, 0x49, 0x46, 0x38, 0x37, 0x61), 30 | 31 | /** 32 | * JPEG image 33 | */ 34 | JPEG("jpg", 0xFF, 0xD8), 35 | 36 | /** 37 | * JFIF image 38 | */ 39 | JFIF("jpg", 0x4A, 0x46, 0x49, 0x46), 40 | 41 | /** 42 | * MIDI file 43 | */ 44 | MIDI("midi", 0x4D, 0x54, 0x68, 0x64), 45 | 46 | /** 47 | * TIFF image 48 | */ 49 | TIFF1("tiff", 0x49, 0x49, 0x2A, 0x00), 50 | 51 | /** 52 | * TIFF image 53 | */ 54 | TIFF2("tiff", 0x4D, 0x4D, 0x00, 0x2A), 55 | 56 | /** 57 | * PNG image 58 | */ 59 | PNG("png", 137, 80, 78, 71, 13, 10, 26, 10); 60 | 61 | private int[] header; 62 | private String ext; 63 | 64 | FileType(String ext, int... bytes) { 65 | header = bytes; 66 | } 67 | 68 | /** 69 | * Returns the typical file extension of this file format 70 | * 71 | * @return the file extension of this format (no dots). 72 | */ 73 | public String getExtension() { 74 | return ext; 75 | } 76 | 77 | /** 78 | * True if the given byte array starts with the header of this file type 79 | * 80 | * @param values the byte array holding the data of the header of a file 81 | * @return true if the array matches the header of this file type. 82 | */ 83 | public boolean matches(byte[] values) { 84 | if (values.length == header.length) { 85 | for (int i = 0; i < values.length; i++) { 86 | if (values[i] == header[i]) { 87 | return true; 88 | } 89 | } 90 | return false; 91 | } else { 92 | return false; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/FileTypeFinder.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * Scans an array of bytes searching for a known header sequence. Used in the 5 | * (nasty) case where COLLADA stores an image file in a hex sequence without 6 | * declaring the image format. 7 | * 8 | * @author pgi 9 | */ 10 | public class FileTypeFinder { 11 | 12 | /** 13 | * Instance creator. 14 | * 15 | * @return a new FileTypeFinder instance. 16 | */ 17 | public static FileTypeFinder create() { 18 | return new FileTypeFinder(); 19 | } 20 | 21 | private FileTypeFinder() { 22 | } 23 | 24 | /** 25 | * Returns the file type for the given byte header. 26 | * 27 | * @param header the bytes to check for a known file header match. 28 | * @return the file type denoted by the given header (maybe FileType.UNKNONW). 29 | */ 30 | public FileType getFileType(byte[] header) { 31 | FileType[] types = FileType.values(); 32 | for (int i = 0; i < types.length; i++) { 33 | if (types[i].matches(header)) { 34 | return types[i]; 35 | } 36 | } 37 | return FileType.UNKNOWNW; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/FloatListTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import java.util.ListIterator; 4 | import java.util.function.Function; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | import java.util.regex.Pattern; 8 | import java.util.stream.Collector; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | 12 | import jme3dae.transformers.ValueTransformer; 13 | 14 | /** 15 | * Transformed a list of white space separated float strings in a float array 16 | * 17 | * @author pgi 18 | */ 19 | public class FloatListTransformer implements ValueTransformer { 20 | 21 | /** 22 | * Instance creator 23 | * 24 | * @return a new FloatListTransformer instance. 25 | */ 26 | public static FloatListTransformer create() { 27 | return new FloatListTransformer(); 28 | } 29 | 30 | private FloatListTransformer() { 31 | } 32 | 33 | private static final Collector toFloatArray = 34 | Collectors.collectingAndThen(Collectors.toList(), floatList -> { 35 | float[] array = new float[floatList.size()]; 36 | for (ListIterator iterator = floatList.listIterator(); iterator.hasNext(); ) { 37 | array[iterator.nextIndex()] = iterator.next(); 38 | } 39 | return array; 40 | }); 41 | 42 | private static final Pattern whitespacePattern = Pattern.compile("\\s+"); 43 | /** 44 | * Transforms a string in a float array 45 | * 46 | * @param value the string to transform 47 | * @return an array of float or an undefined value if parsing fails. 48 | */ 49 | public TransformedValue transform(String value) { 50 | float[] result; 51 | try { 52 | Function mapper = token -> Float.parseFloat(token); 53 | Stream stream = whitespacePattern.splitAsStream(value); 54 | result = stream.filter( s -> !s.isEmpty()).map(mapper).collect(toFloatArray); 55 | } catch (NumberFormatException ex) { 56 | Logger.getLogger(getClass().getName()).log(Level.SEVERE, "", ex); 57 | result = null; 58 | } 59 | return TransformedValue.create(result); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/FloatTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.transformers.ValueTransformer; 4 | import jme3dae.transformers.ValueTransformer.TransformedValue; 5 | 6 | /** 7 | * Transforms a string in a float value. 8 | * 9 | * @author pgi 10 | */ 11 | public class FloatTransformer implements ValueTransformer { 12 | 13 | /** 14 | * Instance creator 15 | * 16 | * @return a new FloatTransformer instance. 17 | */ 18 | public static FloatTransformer create() { 19 | return new FloatTransformer(); 20 | } 21 | 22 | private FloatTransformer() { 23 | } 24 | 25 | /** 26 | * Tranforms a string in a float value. 27 | * 28 | * @param value the string to transform 29 | * @return a float or an undefined value is parsing fails. 30 | */ 31 | public TransformedValue transform(String value) { 32 | Float r = null; 33 | if (value != null) { 34 | value = value.trim(); 35 | try { 36 | r = new Float(value); 37 | } catch (NumberFormatException ex) { 38 | } 39 | } 40 | return TransformedValue.create(r); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/HexSequenceTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.transformers.ValueTransformer; 4 | import jme3dae.transformers.ValueTransformer.TransformedValue; 5 | 6 | /** 7 | * Transforms a string in a sequence of bytes. The string is read as a hexadecimal 8 | * encoded byte sequence. 9 | * 10 | * @author pgi 11 | */ 12 | public class HexSequenceTransformer implements ValueTransformer { 13 | 14 | /** 15 | * Instance creator 16 | * 17 | * @return a new HexSequenceTransformer instance. 18 | */ 19 | public static HexSequenceTransformer create() { 20 | return new HexSequenceTransformer(); 21 | } 22 | 23 | private HexSequenceTransformer() { 24 | } 25 | 26 | /** 27 | * Transforms a string carrying a sequence of hexadecimal bytes in a byte array 28 | * 29 | * @param value a sequence of hexadecimal characters 30 | * @return a byte array or an undefined value if the parsing fails. 31 | */ 32 | public TransformedValue transform(String value) { 33 | byte[] data = null; 34 | if (value != null && (value.length() != 0)) { 35 | data = parse(value); 36 | } 37 | return TransformedValue.create(data); 38 | } 39 | 40 | private byte[] parse(String value) { 41 | byte[] data = new byte[value.length() / 2]; 42 | for (int i = 0; i < value.length(); i += 2) { 43 | String s = "0x" + value.substring(i, i + 2); 44 | data[i / 2] = (byte) (0x000000FF & Integer.decode(s)); 45 | } 46 | return data; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/IndexExtractor.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * Extract an array of indices from an array of integers read as a sequence of 5 | * chunks. 6 | * 7 | * @author pgi 8 | */ 9 | public class IndexExtractor { 10 | 11 | /** 12 | * Instance creator 13 | * 14 | * @param chunkSize the number of integers that form a chunk in the data 15 | * array. 16 | * @param data the array of indices to read 17 | * @return a new IndexExtractor. 18 | */ 19 | public static IndexExtractor create(int chunkSize, int[] data) { 20 | return new IndexExtractor(chunkSize, data); 21 | } 22 | 23 | private final int[] DATA; 24 | private final int CHUNK_SIZE; 25 | 26 | private IndexExtractor(int chunkSize, int[] data) { 27 | CHUNK_SIZE = chunkSize; 28 | DATA = data; 29 | } 30 | 31 | /** 32 | * Returns the indices in the data set that can be mapped to the given offset. 33 | * For an data set {1, 2, 3, 4, 5, 6} where the chunkSize is set to 3, the offset 34 | * 0 returns the values {1, 4} the offset 1 the values {2, 5} and so on 35 | * 36 | * @param offset the offset of the indices to extract 37 | * @return an array of indices. 38 | */ 39 | public int[] get(int offset) { 40 | int[] buffer = new int[DATA.length / CHUNK_SIZE]; 41 | int index = 0; 42 | for (int i = 0; i < DATA.length; i += CHUNK_SIZE) { 43 | buffer[index] = DATA[i + offset]; 44 | index++; 45 | } 46 | return buffer; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/IntegerListTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import java.util.function.ToIntFunction; 4 | import java.util.logging.Level; 5 | import java.util.logging.Logger; 6 | import java.util.regex.Pattern; 7 | import java.util.stream.Stream; 8 | 9 | import jme3dae.transformers.ValueTransformer; 10 | 11 | /** 12 | * Transforms a sequence of white space separated int strings into an array 13 | * of int values 14 | * 15 | * @author pgi 16 | */ 17 | public class IntegerListTransformer implements ValueTransformer { 18 | 19 | /** 20 | * Instance creator 21 | * 22 | * @return a new IntegerListTransformer 23 | */ 24 | public static IntegerListTransformer create() { 25 | return new IntegerListTransformer(); 26 | } 27 | 28 | private IntegerListTransformer() { 29 | } 30 | 31 | private static final Pattern whitespacePattern = Pattern.compile("\\s+"); 32 | /** 33 | * Transforms a string in a sequence of integers 34 | * 35 | * @param value the string to transform 36 | * @return an array of int or an undefined value if parsing fails. 37 | */ 38 | public TransformedValue transform(String value) { 39 | int[] result; 40 | try { 41 | ToIntFunction mapper = token -> Integer.parseInt(token); 42 | Stream stream = whitespacePattern.splitAsStream(value); 43 | result = stream.filter( s -> !s.isEmpty()).mapToInt(mapper).toArray(); 44 | } catch (NumberFormatException ex) { 45 | Logger.getLogger(getClass().getName()).log(Level.SEVERE, "", ex); 46 | result = null; 47 | } 48 | return TransformedValue.create(result); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/IntegerTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import java.util.logging.Level; 4 | import java.util.logging.Logger; 5 | 6 | import jme3dae.transformers.ValueTransformer; 7 | import jme3dae.transformers.ValueTransformer.TransformedValue; 8 | 9 | /** 10 | * Transforms a string in an integer value. 11 | * 12 | * @author pgi 13 | */ 14 | public class IntegerTransformer implements ValueTransformer { 15 | 16 | /** 17 | * Instance creator. 18 | * 19 | * @return a new IntegerTransformer 20 | */ 21 | public static IntegerTransformer create() { 22 | return new IntegerTransformer(); 23 | } 24 | 25 | private IntegerTransformer() { 26 | } 27 | 28 | /** 29 | * Transforms a string in a Integer 30 | * 31 | * @param value the string to transform 32 | * @return a Integer value of an undefined value if parsing fails. 33 | */ 34 | public TransformedValue transform(String value) { 35 | Integer v = null; 36 | value = value.trim(); 37 | if (value.length() != 0) { 38 | try { 39 | v = new Integer(value.trim()); 40 | } catch (NumberFormatException ex) { 41 | Logger.getLogger(getClass().getName()).log(Level.SEVERE, "", ex); 42 | } 43 | } 44 | return TransformedValue.create(v); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/MEMAssetLocator.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import com.jme3.asset.AssetInfo; 4 | import com.jme3.asset.AssetKey; 5 | import com.jme3.asset.AssetLocator; 6 | import com.jme3.asset.AssetManager; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.InputStream; 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * An asset locator that finds assets registered in its cache in byte array form. 15 | * 16 | * @author pgi 17 | */ 18 | public class MEMAssetLocator implements AssetLocator { 19 | private static final Map CACHE = new ConcurrentHashMap(); 20 | 21 | /** 22 | * Register a key-data pair in the MEMAssetLocator registry 23 | * 24 | * @param key the name of the resource stored. Must begin with "mem://" 25 | * @param data the data associated with the the key. 26 | */ 27 | public static void register(final String key, byte[] data) { 28 | byte[] b = new byte[data.length]; 29 | System.arraycopy(data, 0, b, 0, b.length); 30 | ByteArrayInputStream bin = new ByteArrayInputStream(b); 31 | CACHE.put(key, bin); 32 | } 33 | 34 | public MEMAssetLocator() { 35 | } 36 | 37 | public void setRootPath(String rootPath) { 38 | } 39 | 40 | public AssetInfo locate(AssetManager manager, AssetKey key) { 41 | if (CACHE.containsKey(key.getName())) { 42 | final ByteArrayInputStream stream = CACHE.get(key.getName()); 43 | 44 | return new AssetInfo(manager, key) { 45 | 46 | @Override 47 | public InputStream openStream() { 48 | return stream; 49 | } 50 | }; 51 | } else { 52 | return null; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/Matrix4fTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import com.jme3.math.Matrix4f; 4 | import jme3dae.transformers.ValueTransformer.TransformedValue; 5 | 6 | public class Matrix4fTransformer implements TransformerPack { 7 | 8 | public static Matrix4fTransformer create() { 9 | return new Matrix4fTransformer(); 10 | } 11 | 12 | protected Matrix4fTransformer() { 13 | } 14 | 15 | public TransformedValue transform(String value) { 16 | Matrix4f[] result = null; 17 | if (value != null) { 18 | float[] data = FLOAT_LIST.transform(value).get(); 19 | result = new Matrix4f[data.length / 16]; 20 | for (int i = 0; i < data.length; i += 16) { 21 | Matrix4f m = new Matrix4f(); 22 | m.m00 = data[i + 0]; 23 | m.m01 = data[i + 1]; 24 | m.m02 = data[i + 2]; 25 | m.m03 = data[i + 3]; 26 | m.m10 = data[i + 4]; 27 | m.m11 = data[i + 5]; 28 | m.m12 = data[i + 6]; 29 | m.m13 = data[i + 7]; 30 | m.m20 = data[i + 8]; 31 | m.m21 = data[i + 9]; 32 | m.m22 = data[i + 10]; 33 | m.m23 = data[i + 11]; 34 | m.m30 = data[i + 12]; 35 | m.m31 = data[i + 13]; 36 | m.m32 = data[i + 14]; 37 | m.m33 = data[i + 15]; 38 | result[i / 16] = m; 39 | } 40 | } 41 | return TransformedValue.create(result); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/MatrixTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import com.jme3.math.Matrix4f; 4 | 5 | import java.util.Scanner; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | 9 | import jme3dae.transformers.ValueTransformer; 10 | import jme3dae.transformers.ValueTransformer.TransformedValue; 11 | 12 | 13 | /** 14 | * Transforms a string in a Matrix4f 15 | * 16 | * @author pgi 17 | */ 18 | public class MatrixTransformer implements ValueTransformer { 19 | 20 | /** 21 | * Instance initializer 22 | * 23 | * @return a new MatrixTransformer 24 | */ 25 | public static MatrixTransformer create() { 26 | return new MatrixTransformer(); 27 | } 28 | 29 | private MatrixTransformer() { 30 | } 31 | 32 | /** 33 | * Transforms a string in a Matrix3f. The string is read as a sequence of 34 | * space separated float values. The first float is the m00 component, the 35 | * second one the m01, then m02, m03, m10, m11 and so on. 36 | * 37 | * @param value the string to transform 38 | * @return a matrix4f or an undefined value if the parsing fails. 39 | */ 40 | public TransformedValue transform(String value) { 41 | Matrix4f m = null; 42 | if (value != null && (value.length() != 0)) { 43 | m = new Matrix4f(); 44 | //float[] v = getMatrixElements(value); 45 | float[] v = FloatListTransformer.create().transform(value).get(); 46 | m.m00 = v[0]; 47 | m.m01 = v[1]; 48 | m.m02 = v[2]; 49 | m.m03 = v[3]; 50 | m.m10 = v[4]; 51 | m.m11 = v[5]; 52 | m.m12 = v[6]; 53 | m.m13 = v[7]; 54 | m.m20 = v[8]; 55 | m.m21 = v[9]; 56 | m.m22 = v[10]; 57 | m.m23 = v[11]; 58 | m.m30 = v[12]; 59 | m.m31 = v[13]; 60 | m.m32 = v[14]; 61 | m.m33 = v[15]; 62 | } 63 | return TransformedValue.create(m); 64 | } 65 | 66 | private float[] getMatrixElements(String value) { 67 | float[] values = new float[16]; 68 | Scanner in = new Scanner(value); 69 | int i = 0; 70 | while (in.hasNextFloat()) { 71 | if (i == 16) { 72 | Todo.task("This matrix has more than 16 elements..."); 73 | break; 74 | } 75 | values[i] = in.nextFloat(); 76 | i++; 77 | } 78 | return values; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/MeasuringUnit.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * Data stored in the DAENode wrapping the COLLADA element, defines the measuring unit 5 | * of the collada scene. 6 | * 7 | * @author pgi 8 | */ 9 | public class MeasuringUnit { 10 | 11 | /** 12 | * Instance creator 13 | * 14 | * @param meter the float value that defines the unit scale 15 | * @return a new MeasuringUnit 16 | */ 17 | public static MeasuringUnit create(float meter) { 18 | return new MeasuringUnit(meter); 19 | } 20 | 21 | private final float METER; 22 | 23 | private MeasuringUnit(float meter) { 24 | METER = meter; 25 | } 26 | 27 | /** 28 | * Returns the unit scale (1 is 1 meter). 29 | * 30 | * @return the unit scale. 31 | */ 32 | public float getMeter() { 33 | return METER; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/NameListTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.transformers.ValueTransformer; 4 | import jme3dae.transformers.ValueTransformer.TransformedValue; 5 | 6 | /** 7 | * Transforms a String into an array of strings. 8 | * 9 | * @author pgi 10 | */ 11 | public class NameListTransformer implements ValueTransformer { 12 | 13 | /** 14 | * Instance initializer. 15 | * 16 | * @return a new NameListTransformer 17 | */ 18 | public static NameListTransformer create() { 19 | return new NameListTransformer(); 20 | } 21 | 22 | private NameListTransformer() { 23 | } 24 | 25 | /** 26 | * Transforms a string in an array of strings. The array has one component 27 | * for each space-separated name in the given string 28 | * 29 | * @param value the string to transform 30 | * @return an array of strings or an undefined value if parsing fails. 31 | */ 32 | public TransformedValue transform(String value) { 33 | String[] names = null; 34 | if (value != null && (value.length() != 0)) { 35 | names = value.trim().split(" "); 36 | } 37 | return TransformedValue.create(names); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/NormalGenerator.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import com.jme3.math.Triangle; 4 | import com.jme3.math.Vector3f; 5 | import com.jme3.scene.Mesh; 6 | import com.jme3.scene.VertexBuffer; 7 | import com.jme3.scene.VertexBuffer.Type; 8 | import com.jme3.util.BufferUtils; 9 | 10 | import java.nio.FloatBuffer; 11 | import java.util.LinkedList; 12 | import java.util.List; 13 | 14 | public class NormalGenerator { 15 | 16 | public static NormalGenerator create() { 17 | return new NormalGenerator(); 18 | } 19 | 20 | public void generateNormals(Mesh mesh) { 21 | VertexBuffer positionBuffer = mesh.getBuffer(Type.Position); 22 | if (positionBuffer != null && positionBuffer.getData() instanceof FloatBuffer) { 23 | FloatBuffer buffer = (FloatBuffer) positionBuffer.getData(); 24 | FloatBuffer normals = generateNormals(buffer, mesh); 25 | if (normals != null) { 26 | mesh.setBuffer(Type.Normal, 3, normals); 27 | } 28 | } 29 | } 30 | 31 | private FloatBuffer generateNormals(FloatBuffer positions, Mesh mesh) { 32 | FloatBuffer normals = BufferUtils.createFloatBuffer(positions.capacity()); 33 | for (int i = 0; i < positions.capacity(); i += 3) { 34 | float x = positions.get(i); 35 | float y = positions.get(i + 1); 36 | float z = positions.get(i + 2); 37 | Vector3f n = new Vector3f(); 38 | List triangles = getTrianglesSharingIndex(i / 3, mesh); 39 | 40 | for (Triangle t : triangles) { 41 | t.calculateNormal(); 42 | n.addLocal(t.getNormal()); 43 | } 44 | n.normalizeLocal(); 45 | normals.put(n.x).put(n.y).put(n.z); 46 | // normals.put(0).put(0).put(0); 47 | } 48 | normals.flip(); 49 | return normals; 50 | } 51 | 52 | private List getTrianglesSharingIndex(int index, Mesh mesh) { 53 | List result = new LinkedList(); 54 | int[] buffer = new int[3]; 55 | for (int i = 0; i < mesh.getTriangleCount(); i++) { 56 | mesh.getTriangle(i, buffer); 57 | if (buffer[0] == index || buffer[1] == index || buffer[2] == index) { 58 | Triangle t = new Triangle(new Vector3f(), new Vector3f(), new Vector3f()); 59 | mesh.getTriangle(i, t); 60 | result.add(t); 61 | } 62 | } 63 | return result; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/NormalMapFilter.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import java.awt.color.ColorSpace; 4 | import java.awt.image.BufferedImage; 5 | import java.awt.image.ColorConvertOp; 6 | 7 | public class NormalMapFilter { 8 | private static class Vec3f { 9 | float x, y, z; 10 | 11 | void divideLocal(float d) { 12 | x /= d; 13 | y /= d; 14 | z /= d; 15 | } 16 | } 17 | 18 | ; 19 | 20 | public static NormalMapFilter create() { 21 | return new NormalMapFilter(); 22 | } 23 | 24 | protected NormalMapFilter() { 25 | } 26 | 27 | public BufferedImage filter(BufferedImage sourceImage, float factor) { 28 | BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); 29 | BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); 30 | ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); 31 | gscale.filter(sourceImage, heightMap); 32 | for (int x = 0; x < bumpMap.getWidth(); x++) { 33 | for (int y = 0; y < bumpMap.getHeight(); y++) { 34 | bumpMap.setRGB(x, y, generateBumpPixel(heightMap, x, y, factor)); 35 | } 36 | } 37 | return bumpMap; 38 | } 39 | 40 | public int generateBumpPixel(BufferedImage image, int x, int y, float a) { 41 | Vec3f S = new Vec3f(); 42 | Vec3f T = new Vec3f(); 43 | Vec3f N = new Vec3f(); 44 | Vec3f ST = new Vec3f(); 45 | S.x = 1; 46 | S.y = 0; 47 | S.z = a * getHeight(image, x + 1, y) - a * getHeight(image, x - 1, y); 48 | T.x = 0; 49 | T.y = 1; 50 | T.z = a * getHeight(image, x, y + 1) - a * getHeight(image, x, y - 1); 51 | 52 | float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1); 53 | N.x = -S.z; 54 | N.y = -T.z; 55 | N.z = 1; 56 | N.divideLocal(den); 57 | return vectorToColor(N.x, N.y, N.z); 58 | } 59 | 60 | private float getHeight(BufferedImage image, int x, int y) { 61 | if (x < 0) { 62 | x = 0; 63 | } else if (x >= image.getWidth()) { 64 | x = image.getWidth() - 1; 65 | } 66 | if (y < 0) { 67 | y = 0; 68 | } else if (y >= image.getHeight()) { 69 | y = image.getHeight() - 1; 70 | } 71 | return image.getRGB(x, y) & 0xff; 72 | } 73 | 74 | public int vectorToColor(float x, float y, float z) { 75 | int r = Math.round(255 * ((x + 1f) / 2f)); 76 | int g = Math.round(255 * ((y + 1f) / 2f)); 77 | int b = Math.round(255 * ((z + 1f) / 2f)); 78 | return (255 << 24) + (r << 16) + (g << 8) + b; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "Bump Map"; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/PlainTextTransformer.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.transformers.ValueTransformer; 4 | import jme3dae.transformers.ValueTransformer.TransformedValue; 5 | 6 | /** 7 | * Transforms a string in a string. 8 | * 9 | * @author pgi 10 | */ 11 | public class PlainTextTransformer implements ValueTransformer { 12 | 13 | /** 14 | * Instance creator. 15 | * 16 | * @return a new PlainTextTransformer 17 | */ 18 | public static PlainTextTransformer create() { 19 | return new PlainTextTransformer(); 20 | } 21 | 22 | private PlainTextTransformer() { 23 | } 24 | 25 | /** 26 | * Returns the given string if not null and not empty 27 | * 28 | * @param value the string to transform 29 | * @return the given string or an undefined value if the string is null or 30 | * empty. 31 | */ 32 | public TransformedValue transform(String value) { 33 | if (value.length() == 0) { 34 | value = null; 35 | } 36 | return TransformedValue.create(value); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/SpatialTreeIterable.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import com.jme3.scene.Node; 4 | import com.jme3.scene.Spatial; 5 | 6 | import java.util.Iterator; 7 | import java.util.LinkedList; 8 | 9 | public class SpatialTreeIterable implements Iterable { 10 | private final Spatial root; 11 | 12 | public SpatialTreeIterable(Spatial root) { 13 | this.root = root; 14 | } 15 | 16 | public Iterator iterator() { 17 | final LinkedList list = new LinkedList(); 18 | list.add(root); 19 | return new Iterator() { 20 | 21 | public boolean hasNext() { 22 | return !list.isEmpty(); 23 | } 24 | 25 | public Spatial next() { 26 | Spatial s = list.pop(); 27 | if (s instanceof Node) list.addAll(((Node) s).getChildren()); 28 | return s; 29 | } 30 | 31 | public void remove() { 32 | throw new UnsupportedOperationException("Not supported yet."); 33 | } 34 | }; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/TextureBaseList.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import java.util.LinkedList; 4 | 5 | public class TextureBaseList extends LinkedList { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/Todo.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * Utility class to print todo tasks. The printed message includes the line of 5 | * the code where the Todo has been called. Used for debug purposes. Enabled if 6 | * the system property "checkenabled" is not null. 7 | * 8 | * @author pgi 9 | */ 10 | public class Todo { 11 | private static final boolean DISABLED = System.getProperty("checksenabled") == null; 12 | 13 | private static void log(String message) { 14 | if (DISABLED) return; 15 | 16 | StackTraceElement[] trace = Thread.currentThread().getStackTrace(); 17 | String where = ""; 18 | if (trace != null && trace.length >= 3) { 19 | where = trace[3].toString(); 20 | } 21 | System.out.printf("***TODO***[%s][%s]%n", where, message); 22 | } 23 | 24 | /** 25 | * Prins a message if enabled. 26 | * 27 | * @param description the description of the task. Eg Todo.task("do something"). 28 | */ 29 | public static void task(String description) { 30 | if (DISABLED) return; 31 | log(description); 32 | } 33 | 34 | /** 35 | * A predefined message to implement some part of code. 36 | */ 37 | public static void implementThis() { 38 | if (DISABLED) return; 39 | log("implement this method"); 40 | } 41 | 42 | /** 43 | * A predefined message to parse some node. 44 | * 45 | * @param node the element to parse 46 | */ 47 | public static void parse(Object node) { 48 | if (DISABLED) return; 49 | log("implement parsing of node\n" + node); 50 | } 51 | 52 | /** 53 | * A predefined message to check the parsing of some node. 54 | * 55 | * @param node the node to parse 56 | * @param why the reason why parsing is not fully implemented. 57 | */ 58 | public static void checkParsingOf(Object node, String why) { 59 | if (DISABLED) return; 60 | log("Can't parse node \n" + node + "\n" + why); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/TransformerPack.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | import jme3dae.collada14.transformers.SemanticTransformer; 4 | import jme3dae.transformers.ValueTransformer; 5 | 6 | /** 7 | * A base interface for transformers that make frequent use of some value 8 | * transformers (numbers, text...) 9 | * 10 | * @param the type of the value required by the transformer 11 | * @param the type of the restul returned by the transformer 12 | * @author pgi 13 | */ 14 | public interface TransformerPack extends ValueTransformer { 15 | 16 | /** 17 | * Transformes a string into a Semantic value 18 | */ 19 | SemanticTransformer SEMANTIC = SemanticTransformer.create(); 20 | 21 | /** 22 | * Transforms a string into an integer 23 | */ 24 | IntegerTransformer INTEGER = IntegerTransformer.create(); 25 | 26 | /** 27 | * Transforms a string into an integer array 28 | */ 29 | IntegerListTransformer INTEGER_LIST = IntegerListTransformer.create(); 30 | 31 | /** 32 | * Transforms a string into a float array 33 | */ 34 | FloatListTransformer FLOAT_LIST = FloatListTransformer.create(); 35 | 36 | /** 37 | * Transforms a string into a string 38 | */ 39 | PlainTextTransformer TEXT = PlainTextTransformer.create(); 40 | 41 | /** 42 | * Transforms a string into a string array 43 | */ 44 | NameListTransformer NAME_LIST = NameListTransformer.create(); 45 | 46 | /** 47 | * Transforms a string into a float 48 | */ 49 | FloatTransformer FLOAT = FloatTransformer.create(); 50 | 51 | /** 52 | * Transforms a string into a boolean 53 | */ 54 | BooleanTransformer BOOLEAN = BooleanTransformer.create(); 55 | 56 | /** 57 | * Transforms a string into a boolean array 58 | */ 59 | BooleanListTransformer BOOLEAN_LIST = BooleanListTransformer.create(); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/Tuple2.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * A value pair 5 | * 6 | * @param the type of the first value 7 | * @param the type of the second value. 8 | * @author pgi 9 | */ 10 | public class Tuple2 { 11 | 12 | /** 13 | * Instance creator. 14 | * 15 | * @param the type of the first value 16 | * @param the type of the second value 17 | * @param a the first value 18 | * @param b the second value 19 | * @return a new Tuple2 20 | */ 21 | public static Tuple2 create(A a, B b) { 22 | return new Tuple2(a, b); 23 | } 24 | 25 | private final A a; 26 | private final B b; 27 | 28 | /** 29 | * Instance initializer 30 | * 31 | * @param a the first value 32 | * @param b the second value 33 | */ 34 | protected Tuple2(A a, B b) { 35 | this.a = a; 36 | this.b = b; 37 | } 38 | 39 | /** 40 | * Returns the first value. 41 | * 42 | * @return the first value 43 | */ 44 | public A getA() { 45 | return a; 46 | } 47 | 48 | /** 49 | * Returns the second value 50 | * 51 | * @return the second value. 52 | */ 53 | public B getB() { 54 | return b; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/Tuple3.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * A 3 values tuple 5 | * 6 | * @param the type of the first value 7 | * @param the type of the second value 8 | * @param the type of the third value 9 | * @author pgi 10 | */ 11 | public class Tuple3 extends Tuple2 { 12 | 13 | /** 14 | * Instance creator 15 | * 16 | * @param the type of the first value 17 | * @param the type of the second value 18 | * @param the type of the third value 19 | * @param a the first value 20 | * @param b the second value 21 | * @param c the third value 22 | * @return a new Tuple3 instance 23 | */ 24 | public static Tuple3 create(A a, B b, C c) { 25 | return new Tuple3(a, b, c); 26 | } 27 | 28 | private final C c; 29 | 30 | /** 31 | * Instance initializer 32 | * 33 | * @param a the first value 34 | * @param b the second value 35 | * @param c the third value 36 | */ 37 | protected Tuple3(A a, B b, C c) { 38 | super(a, b); 39 | this.c = c; 40 | } 41 | 42 | /** 43 | * Returns the third value 44 | * 45 | * @return the third value 46 | */ 47 | public C getC() { 48 | return c; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/UpAxis.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * Value stored in the daenode wrapping the root of the collada document, denotes 5 | * the up axis of the coordinate system. 6 | * 7 | * @author pgi 8 | */ 9 | public enum UpAxis { 10 | 11 | Z_UP, 12 | Y_UP, 13 | X_UP, 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/Vertex.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | /** 4 | * A class used to define the vertex of a polygon. The vertex carries an index 5 | * value. The index is used to bind this vertex to the original vertex indexing of 6 | * the collada document. 7 | * 8 | * @author pgi 9 | */ 10 | public class Vertex { 11 | 12 | /** 13 | * Instance creator 14 | * 15 | * @param index the index of the vertex in the collada document 16 | * @param data the components of the vertex (x, y, z) 17 | * @return a new Vertex 18 | */ 19 | public static Vertex create(int index, float[] data) { 20 | return new Vertex(index, data); 21 | } 22 | 23 | private final int index; 24 | private final float[] data; 25 | 26 | private Vertex(int index, float[] data) { 27 | this.index = index; 28 | this.data = data; 29 | } 30 | 31 | /** 32 | * Returns the index of this vertex in the collada document vertex set. 33 | * 34 | * @return the index of this vertex in the collada document vertex set. 35 | */ 36 | public int getIndex() { 37 | return index; 38 | } 39 | 40 | /** 41 | * Returns the components of this vertex. The returned array is the same array 42 | * held by this instance: changes to the returned array may have effects on other 43 | * objects. 44 | * 45 | * @return the components of this vertex. 46 | */ 47 | public float[] getData() { 48 | return data; 49 | } 50 | 51 | /** 52 | * A string representing this vertex, for debug purposes. 53 | * 54 | * @return a string representation of this vertex. 55 | */ 56 | @Override 57 | public String toString() { 58 | return "(" + data[0] + "," + data[1] + "," + data[2] + ")"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/VertexSkinningData.java: -------------------------------------------------------------------------------- 1 | package jme3dae.utilities; 2 | 3 | public class VertexSkinningData { 4 | 5 | public static VertexSkinningData create(int vertexIndex, int boneIndex, float weight) { 6 | return new VertexSkinningData(vertexIndex, boneIndex, weight); 7 | } 8 | 9 | private final int boneIndex; 10 | private final float weight; 11 | private final int vertexIndex; 12 | 13 | private VertexSkinningData(int vertexIndex, int boneIndex, float weight) { 14 | this.vertexIndex = vertexIndex; 15 | this.boneIndex = boneIndex; 16 | this.weight = weight; 17 | } 18 | 19 | public int getVertexIndex() { 20 | return vertexIndex; 21 | } 22 | 23 | public int getBoneIndex() { 24 | return boneIndex; 25 | } 26 | 27 | public float getWeight() { 28 | return weight; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/jme3dae/utilities/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities for the parsing process. 3 | */ 4 | package jme3dae.utilities; 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | true 7 | %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %logger{36} - %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/UserDataConstants.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet 2 | 3 | object UserDataConstants { 4 | // For identifying model/modelinstance 5 | val MODEL_ID = "modelId" 6 | val MODEL_INDEX = "modelIndex" 7 | val MODEL = "model" 8 | val MESH_INDEX = "meshIndex" 9 | val PART_INDEX = "partIndex" 10 | val NODE_ID = "id" 11 | val MODEL_FORMAT = "modelFormat" 12 | 13 | // For saving and reverting materials 14 | val ORIG_MATERIALS = "origMaterials" 15 | val IS_SELECTED = "isSelected" 16 | 17 | // Camera positions 18 | val CAMERA_LOCATION = "cameraLocation" 19 | val CAMERA_TARGET = "cameraTarget" 20 | val CAMERA_DIRECTION = "cameraDir" 21 | val CAMERA_UP = "cameraUp" 22 | val CAMERA_LEFT = "cameraLeft" 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/apps/GenerateViewpoints.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.apps 2 | 3 | import au.com.bytecode.opencsv.CSVWriter 4 | import com.jme3.math.Vector3f 5 | import com.jme3.renderer.Camera 6 | import edu.stanford.graphics.shapenet.Constants 7 | import edu.stanford.graphics.shapenet.common.{FullId, CameraInfo} 8 | import edu.stanford.graphics.shapenet.jme3.Jme 9 | import edu.stanford.graphics.shapenet.jme3.loaders.ModelLoadOptions 10 | import edu.stanford.graphics.shapenet.jme3.stringToVector3f 11 | import edu.stanford.graphics.shapenet.jme3.viewer._ 12 | import edu.stanford.graphics.shapenet.util.{IOUtils, ConfigHelper} 13 | import edu.stanford.graphics.shapenet.util.ConversionUtils.l2Or 14 | 15 | /** 16 | * Program to generate viewpoints for looking at a object 17 | * 18 | * @author Angel Chang 19 | */ 20 | object GenerateViewpoints extends App { 21 | val configFile = ConfigHelper.fromOptions(args:_*) 22 | val config = ViewerConfig(configFile) 23 | 24 | val loadModel = true 25 | val id = ConfigHelper.getString("input", config.defaultModelId)(configFile) 26 | val summaryFilename = ConfigHelper.getString("output", Constants.WORK_DIR + "viewpoints.tsv")(configFile) 27 | val fovy = ConfigHelper.getDouble("fovy", 30.0)(configFile) 28 | val up = ConfigHelper.getStringOption("up")(configFile).map( x => stringToVector3f(x) ) 29 | val front = ConfigHelper.getStringOption("front")(configFile).map( x => stringToVector3f(x) ) 30 | val unit = ConfigHelper.getDoubleOption("unit")(configFile) 31 | 32 | // Setup camera generator 33 | var cam = new Camera(config.width.get, config.height.get) 34 | cam.setFrustumPerspective(fovy.toFloat, cam.getWidth().toFloat / cam.getHeight().toFloat, 1.0F, 1000.0F) 35 | cam.setLocation(new Vector3f(0.0F, 0.0F, 10.0F)) 36 | cam.lookAt(new Vector3f(0.0F, 0.0F, 0.0F), Vector3f.UNIT_Y) 37 | 38 | val cameraPositionOptions = new CameraPositionOptions( 39 | cameraPositioningStrategy = config.cameraPositionStrategy, 40 | cameraAngleFromHorizontal = Option(config.cameraAngleFromHorizontal), 41 | startRotation = Option(config.cameraStartOrientation), 42 | distanceFromObjectRatio = Option(config.defaultModelDistanceScale) 43 | ) 44 | 45 | val cameraPositionGenerator = if (config.includeCanonicalViews) { 46 | // Create 6 canonical views + 8 views around at height xxx 47 | val camPosGen1 = CameraPositionGenerator.canonicalViewsToFit(cam) 48 | val camPosGen2 = new RotatingCameraPositionGenerator(cam, cameraPositionOptions, nPositions = config.nImagesPerModel) 49 | new CombinedCameraPositionGenerator(camPosGen1, camPosGen2) 50 | } else { 51 | new RotatingCameraPositionGenerator(cam, cameraPositionOptions, nPositions = config.nImagesPerModel) 52 | } 53 | 54 | // Load scene 55 | val jme = Jme() 56 | if (this.unit.isDefined || this.up.isDefined || this.front.isDefined) { 57 | val fullId = FullId(id) 58 | val loadOpts = jme.dataManager.getModelLoadOptions(fullId, null).copy(unit = this.unit, up = this.up, front = this.front) 59 | jme.dataManager.registerCustomLoadOptions(id, loadOpts) 60 | } 61 | 62 | val scene = if (loadModel) jme.loadModelAsAlignedScene(id) else jme.loadAlignedScene(id) 63 | val cameraPositions = cameraPositionGenerator.generatePositions(scene.node) 64 | val scenebb = jme.getBoundingBox(scene.node) 65 | val bbmin = scenebb.getMin(null) 66 | val bbmax = scenebb.getMax(null) 67 | 68 | println("Opening " + summaryFilename) 69 | val append = false 70 | val hasData = IOUtils.isReadableFileWithData(summaryFilename) 71 | val summaryFile = new CSVWriter(IOUtils.filePrintWriter(summaryFilename, append), '\t', CSVWriter.NO_QUOTE_CHARACTER) 72 | if (!append || !hasData) { 73 | // Only output header if empty 74 | summaryFile.writeNext(Array("scene","image","bbmin","bbmax","camera.position","camera.up","camera.target","camera.direction")) 75 | } 76 | 77 | def toString[T >: Null](o:T) = if (o == null) "" else o.toString() 78 | for ((p,i) <- cameraPositions.zipWithIndex) { 79 | val camInfo = CameraInfo("cam", p.position, p.up, p.direction, p.target) 80 | val sceneCam = jme.transformCameraInfoFromWorldToScene(camInfo, scene.scene) 81 | val row = Array(scene.scene.sceneId, i, bbmin, bbmax, 82 | sceneCam.position, sceneCam.up, sceneCam.target, sceneCam.direction) 83 | summaryFile.writeNext(row.map( x => if (x.isInstanceOf[Vector3f]) { 84 | val v = x.asInstanceOf[Vector3f] 85 | v.x + "," + v.y + "," + v.z 86 | } else { 87 | toString(x) 88 | })) 89 | } 90 | summaryFile.close() 91 | } 92 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/colors/ColorPalette.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.colors 2 | 3 | import java.awt.Color 4 | import javax.imageio.ImageIO 5 | import java.io.File 6 | 7 | import edu.stanford.graphics.shapenet.Constants 8 | 9 | /** 10 | * A color palette 11 | * @author Angel Chang 12 | */ 13 | trait ColorPalette { 14 | def getColor(id: Int): Color 15 | def getColorCount(): Int = -1 16 | 17 | def getColor(id: Int, alpha: Float): Color = { 18 | val c = getColor(id) 19 | edu.stanford.graphics.shapenet.colors.getColor(c, alpha) 20 | } 21 | } 22 | 23 | class ColorBar(rgbColors: Array[Color]) extends ColorPalette { 24 | val nColors = rgbColors.length 25 | def getColor(r: Double): Color = getColor((r*(nColors-1)).toInt) 26 | def getColor(id: Int): Color = rgbColors(id % nColors) 27 | override def getColorCount() = nColors 28 | } 29 | 30 | object ColorBar { 31 | val texturesDir = Constants.ASSETS_DIR + "Textures" + File.separator 32 | lazy val coolwarmBar = ColorBar(texturesDir + "Cool2WarmBar.png") 33 | lazy val warmBar = ColorBar(texturesDir + "heatmap.png") 34 | def apply(filename: String): ColorBar = { 35 | val img = ImageIO.read(new File(filename)) 36 | val rgb = Array.ofDim[Color](img.getWidth) 37 | for (x <- 0 until rgb.length) { 38 | rgb(x) = new Color(img.getRGB(x, 0)) 39 | } 40 | new ColorBar(rgb) 41 | } 42 | } 43 | 44 | object PhiColorPalette extends ColorPalette { 45 | def getColor(id: Int): Color = { 46 | val startColor = new Color(0x4FD067) 47 | val hsb = Color.RGBtoHSB(startColor.getRed, startColor.getGreen, startColor.getBlue, null) 48 | val invPhi = 1.0/Constants.phi 49 | var hue = hsb(0) + id*invPhi 50 | hue = hue - math.floor(hue) 51 | val c = Color.getHSBColor(hue.toFloat, 0.5f, 0.95f) 52 | // Switch blue and green for nice pretty colors 53 | new Color(c.getRed, c.getBlue, c.getGreen) 54 | } 55 | } 56 | 57 | object DefaultColorPalette extends ColorPalette { 58 | def getColor(id: Int): Color = { 59 | var h = (-3.88 * id) % (2*Math.PI) 60 | if (h<0) h += 2*Math.PI 61 | h /= 2*Math.PI 62 | val c = Color.getHSBColor(h.toFloat, (0.4 + 0.2 * Math.sin(0.42 * id)).toFloat, 0.5f) 63 | c 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/colors/package.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet 2 | 3 | import java.awt.Color 4 | 5 | /** 6 | * Color utilities 7 | * @author Angel Chang 8 | */ 9 | package object colors { 10 | def getColor(id: Int, alpha: Float): Color = DefaultColorPalette.getColor(id, alpha) 11 | def getColor(id: Int): Color = DefaultColorPalette.getColor(id) 12 | def getColor(c: Color, alpha: Float): Color = { 13 | new Color(c.getRed, c.getGreen, c.getBlue, (alpha*255).toInt) 14 | } 15 | def toHex(c: Color): String = { 16 | val hexColor = "#%06X%02X".format((0xFFFFFF & c.getRGB), c.getAlpha) 17 | hexColor 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/Attributes.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | /** 4 | * Attributes 5 | * @author Angel Chang 6 | */ 7 | trait HasAttributes { 8 | var attributes: IndexedSeq[(String,String)] 9 | 10 | def addToIndexedSeq[T](s: IndexedSeq[T], t: T, allowDuplicates: Boolean = false): (IndexedSeq[T], Int) = { 11 | if (s == null) { 12 | (IndexedSeq(t), 0) 13 | } else { 14 | val nextIndex = s.size 15 | if (allowDuplicates) { 16 | // Don't check for duplicates, just add it to the end 17 | (s :+ t, nextIndex) 18 | } else { 19 | // Check if there is a existing value that is the same, and only add if there isn't 20 | val i = s.indexOf(t) 21 | if (i >= 0) (s,i) else (s :+ t, nextIndex) 22 | } 23 | } 24 | } 25 | 26 | def setValue[K,V](s: IndexedSeq[(K,V)], k: K, v: V): (IndexedSeq[(K,V)], Int) = { 27 | if (s == null) { 28 | (IndexedSeq((k,v)), 0) 29 | } else { 30 | // Check if there is a existing value that is the same, and only add if there isn't 31 | val i = s.indexOf(k) 32 | val nextIndex = s.size 33 | if (i >= 0) (s.updated(i,(k,v)),i) else (s :+ (k,v), nextIndex) 34 | } 35 | } 36 | 37 | def addAttribute(attrType: String, attrValue: String, allowDuplicates: Boolean = false): Int = { 38 | val (a,i) = addToIndexedSeq(attributes, (attrType, attrValue), allowDuplicates) 39 | attributes = a 40 | i 41 | } 42 | 43 | def setAttribute(attrType: String, attrValue: String): Int = { 44 | val (a,i) = setValue(attributes, attrType, attrValue) 45 | attributes = a 46 | i 47 | } 48 | 49 | def setAttribute(attrType: String, attrValue: Boolean): Int = { 50 | val (a,i) = setValue(attributes, attrType, attrValue.toString) 51 | attributes = a 52 | i 53 | } 54 | 55 | def setAttribute(attrType: String, attrValue: Number): Int = { 56 | val (a,i) = setValue(attributes, attrType, attrValue.toString()) 57 | attributes = a 58 | i 59 | } 60 | 61 | def getAttributes(name: String): IndexedSeq[(String,String)] = { 62 | if (attributes != null) { 63 | attributes.filter( x => x._1 == name) 64 | } else { 65 | IndexedSeq() 66 | } 67 | } 68 | 69 | def getAttributeValue(name: String): Option[String] = { 70 | getAttributes(name).headOption.map( x => x._2 ) 71 | } 72 | 73 | def getAttributeBoolean(name: String): Option[Boolean] = { 74 | getAttributes(name).headOption.map( x => x._2.toBoolean ) 75 | } 76 | 77 | def getAttributeInt(name: String): Option[Int] = { 78 | getAttributes(name).headOption.map( x => x._2.toInt ) 79 | } 80 | 81 | def getAttributeDouble(name: String): Option[Double] = { 82 | getAttributes(name).headOption.map( x => x._2.toDouble ) 83 | } 84 | } 85 | 86 | /** 87 | * Attribute type constants 88 | * @author Angel Chang 89 | */ 90 | // Constants on possible attribute types (mainly so we don't mistype) 91 | object AttributeTypes { 92 | val MODEL_ID = "modelId" 93 | val OBJECT_INDEX = "objIndex" 94 | // Unused attributes, but ones we would like to have (maybe these should be promoted to be full relations) 95 | val COLOR = "color" 96 | val SHAPE = "shape" 97 | val MATERIAL = "material" 98 | 99 | // Not quite attribute types, but effectively attributes 100 | val CATEGORY = "category" 101 | val DESCRIPTION = "description" 102 | } 103 | 104 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/CategoryUtils.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | import edu.stanford.graphics.shapenet.util.StringUtils 4 | 5 | /** 6 | * Utility functions for normalizing category names... 7 | * @author Angel Chang 8 | */ 9 | object CategoryUtils { 10 | val ROOT = "ROOT" 11 | val UNKNOWN = "UNKNOWN" 12 | 13 | def normalize(s: String) = { 14 | StringUtils.toCamelCase(s) 15 | } 16 | 17 | def toKeyword(s: String) = { 18 | if (s != null) { 19 | StringUtils.camelCaseToText(s, lowercase = true) 20 | } else null 21 | } 22 | 23 | def getKeywords(category: String, location: String = null): IndexedSeq[String] = { 24 | if (location != null) { 25 | IndexedSeq(toKeyword(category), toKeyword(location)) 26 | } else { 27 | IndexedSeq(toKeyword(category)) 28 | } 29 | } 30 | 31 | def isSameCategory(cat1: String, cat2: String): Boolean = { 32 | // TODO: normalize? 33 | cat1 == cat2 34 | } 35 | 36 | def getCategories(m: Option[ModelInfo]): Seq[String] = { 37 | getCategories(m.getOrElse(null)) 38 | } 39 | 40 | def getCategories(m: ModelInfo, allowEmpty: Boolean = true): Seq[String] = { 41 | if (m != null) { 42 | val cats = m.category //m.relaxedCategory 43 | if (cats != null && cats.nonEmpty) { 44 | cats.toSeq.map( x => normalize(x) ) 45 | } else if (allowEmpty) Seq() else Seq( CategoryUtils.UNKNOWN ) 46 | } else { 47 | if (allowEmpty) Seq() else Seq( CategoryUtils.UNKNOWN ) 48 | } 49 | } 50 | 51 | def hasCategory(m: ModelInfo, category: String): Boolean = { 52 | getCategories(m).exists( x => x == category) 53 | } 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/FullId.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | import java.io.File 4 | 5 | import scala.util.matching.Regex 6 | 7 | /** 8 | * Represents a resource/asset with a source and a id 9 | * @author Angel Chang 10 | */ 11 | case class FullId(source: String, id: String) { 12 | lazy val fullid = source + "." + id 13 | } 14 | 15 | object FullId { 16 | val fullIdRegex = new Regex("([a-zA-z0-9_-]+)\\.([a-zA-z0-9_-]+)") 17 | def apply(fullid: String, defaultSource: Option[String] = None): FullId = { 18 | val dotIndex = fullid.indexOf('.') 19 | val (source, id) = if (fullid.startsWith("http://") || fullid.startsWith("https://")) { 20 | ("raw", fullid) 21 | } else if (fullid.startsWith("file://")) { 22 | ("raw", fullid.substring(7)) 23 | } else if (fullid.startsWith("/")) { 24 | ("raw", fullid) 25 | } else if (new File(fullid).isAbsolute) { 26 | ("raw", fullid) 27 | } else if (dotIndex > 0) { 28 | (fullid.substring(0, dotIndex), fullid.substring(dotIndex + 1)) 29 | } else { 30 | val s = defaultSource.getOrElse(if (fullid.contains("scene")) "wssScenes" else "3dw") 31 | (s, fullid) 32 | } 33 | new FullId(source,id) 34 | } 35 | def matches(id1: String, id2: String): Boolean = { 36 | val f1 = FullId(id1) 37 | val f2 = FullId(id2) 38 | f1 == f2 39 | } 40 | def isFullId(s: String): Boolean = { 41 | fullIdRegex.pattern.matcher(s).matches() 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/GeometricScene.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | import scala.collection.mutable 4 | import com.jme3.bounding.BoundingBox 5 | 6 | /** 7 | * Geometric representation of a scene 8 | * @author Angel Chang 9 | */ 10 | abstract class GeometricScene[NODE](val node: NODE) { 11 | var scene: Scene = null 12 | var modelInstances: mutable.ArrayBuffer[ModelInstance[NODE]] = null //multiple instances of same model contained here 13 | // Cached information 14 | // TODO: Refactor!!! 15 | var cachedBoundingBoxes: IndexedSeq[BoundingBox] = null 16 | var cachedBoundingBoxesWithChildren: IndexedSeq[BoundingBox] = null 17 | // Was the geometric scene modified without updating the underlying scene? 18 | var geometricSceneModified: Boolean = false 19 | 20 | def getModelInstance(modelInstIndex: Int): ModelInstance[NODE] = { 21 | if (modelInstIndex >= 0 && modelInstIndex < modelInstances.size) { 22 | modelInstances(modelInstIndex) 23 | } else null 24 | } 25 | def getNumberOfObjects(): Int = { 26 | modelInstances.size 27 | } 28 | // Removes the specified model from the scene 29 | def delete(modelInstIndex: Int) 30 | // Add the specified model instance to the scene 31 | def insert(modelInst: ModelInstance[NODE]) 32 | def copy(): GeometricScene[NODE] 33 | // Synchronizes the transforms in the geometric scene to the actual scene 34 | def syncToScene() { 35 | applyTransformToScene(this.scene, undoWorldAlignAndScale = true) 36 | geometricSceneModified = false 37 | } 38 | // Apply the transforms in the geometric scene to a target scene 39 | def applyTransformToScene(targetScene: Scene, undoWorldAlignAndScale: Boolean = false) { 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/Material.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.models3d 2 | 3 | import edu.stanford.graphics.shapenet.Constants 4 | import edu.stanford.graphics.shapenet.util.CSVFile 5 | 6 | import scala.collection.mutable 7 | 8 | /** 9 | * Material 10 | * @author Angel Chang 11 | */ 12 | case class Material(name: String, density: Double, staticFrictionCoeff: Double) { 13 | } 14 | 15 | object Materials { 16 | lazy val materials: Map[String, Material] = Materials.readMaterials(Constants.MATERIAL_DENSITIES_FILE) 17 | 18 | def readMaterials(filename: String): Map[String, Material] = { 19 | val map = new mutable.HashMap[String, Material]() 20 | val csvfile = new CSVFile(filename, includesHeader = true) 21 | val iMaterial: Int = csvfile.index("Material") 22 | val iDensity: Int = csvfile.index("Density") 23 | val iStaticFrictionCoeff: Int = csvfile.index("StaticFrictionCoeff") 24 | for (row <- csvfile) { 25 | val material: String = row(iMaterial).trim 26 | val density: Double = row(iDensity).trim.toDouble 27 | val staticFrictionCoeff: Double = row(iStaticFrictionCoeff).trim.toDouble 28 | val matInfo = map.getOrElseUpdate(material, Material(material, density, staticFrictionCoeff)) 29 | } 30 | map.toMap 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/Model.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | import com.jme3.math.Vector3f 4 | import edu.stanford.graphics.shapenet.Constants 5 | 6 | /** 7 | * Represents a model of an object 8 | * Includes the geometry, and materials for the model 9 | * @author Angel Chang 10 | */ 11 | class Model[NODE](val node: NODE) { 12 | /** 13 | * Semantic information associated with a model 14 | */ 15 | var modelInfo: ModelInfo = null 16 | 17 | def fullId: String = if (modelInfo != null) modelInfo.fullId else null 18 | def name: String = if (modelInfo != null) modelInfo.name else null 19 | def up: Vector3f = { 20 | val up = if (modelInfo != null) modelInfo.up else null 21 | if (up != null) up else Constants.DEFAULT_MODEL_UP 22 | } 23 | def front: Vector3f = { 24 | val front = if (modelInfo != null) modelInfo.front else null 25 | if (front != null) front else Constants.DEFAULT_MODEL_FRONT 26 | } 27 | def isRoom: Boolean = { 28 | if (modelInfo != null) modelInfo.isRoom else false 29 | } 30 | } 31 | 32 | /** 33 | * Represents a instantiation of a model in a scene 34 | */ 35 | class ModelInstance[NODE](val node: NODE, var index: Int) { 36 | // Information about the model this node is derived from 37 | var model: Model[NODE] = null 38 | // my self (no children) 39 | var nodeSelf: NODE = node 40 | def up: Vector3f = { 41 | if (Constants.useSemanticCoordFront) { 42 | Constants.SEMANTIC_UP 43 | } else { 44 | val up = if (model != null) model.up else null 45 | if (up != null) up else Constants.DEFAULT_MODEL_UP 46 | } 47 | } 48 | def front: Vector3f = { 49 | if (Constants.useSemanticCoordFront) { 50 | Constants.SEMANTIC_FRONT 51 | } else { 52 | val front = if (model != null) model.front else null 53 | if (front != null) front else Constants.DEFAULT_MODEL_FRONT 54 | } 55 | } 56 | def withChildren = node 57 | def withoutChildren = nodeSelf 58 | def getLabel: String = { 59 | toString 60 | } 61 | override def toString() = { 62 | model.name + "(" + index + ")" 63 | } 64 | } 65 | 66 | case class MaterialInfo ( 67 | diffuse: Array[Double] = null, // Color as RGB (0 to 1) 68 | ambient: Array[Double] = null, 69 | specular: Array[Double] = null, 70 | diffuseMap: String = null, 71 | shininess: Double = Double.NaN, 72 | transparent: Boolean = false, 73 | opacity: Double = 1, 74 | shadeless: Boolean = false, 75 | doubleSided: Boolean = false 76 | ) 77 | 78 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/Parts.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | import edu.stanford.graphics.shapenet.util.StringUtils 4 | 5 | /** 6 | * Data structure for parts 7 | * @author Angel Chang 8 | */ 9 | object PartType extends Enumeration { 10 | val MESH, SGPATH, OBJECT, CLUSTER = Value 11 | } 12 | 13 | case class PartId(partType: PartType.Value, partIndex: Int, partIdValue: String) { 14 | override def toString(): String = { 15 | if (partType != null) partType + "-" + partIdValue else null 16 | } 17 | } 18 | 19 | object PartId { 20 | def parse(partId: String): PartId = { 21 | if (partId != null) { 22 | val x = partId.split("-", 2) 23 | val pt = PartType.withName(x(0)) 24 | val pi: Int = if (StringUtils.isAllDigits(x(1))) { 25 | x(1).toInt 26 | } else { 27 | -1 28 | } 29 | PartId(pt, pi, x(1)) 30 | } else null 31 | } 32 | def apply(partType: PartType.Value, partIndex: Int): PartId = 33 | if (partType != null && partIndex >= 0) 34 | PartId(partType, partIndex, partIndex.toString) 35 | else null 36 | def toString(partType: PartType.Value, partIndex: Int) = if (partType != null) partType + "-" + partIndex else null 37 | def toString(partType: PartType.Value, partIdValue: String) = if (partType != null) partType + "-" + partIdValue else null 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/SceneSelection.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | import scala.collection.SeqProxy 4 | import scala.collection.mutable.ArrayBuffer 5 | 6 | /** 7 | * Represents a selection in a scene 8 | * @author Angel Chang 9 | */ 10 | case class SceneSelection(objectIndex: Int, 11 | partId: PartId = null) { 12 | def partType = if (partId != null) partId.partType else null 13 | def partIndex = if (partId != null) partId.partIndex else -1 14 | def partIdString = if (partId != null) partId.toString else null 15 | def id = if (partId != null) objectIndex + "." + partId else objectIndex 16 | } 17 | 18 | class SceneSelections(val self: Seq[SceneSelection]) extends SeqProxy[SceneSelection] { 19 | def getSelectedParts(partType: PartType.Value): Seq[SceneSelection] = { 20 | self.filter(x => x.partType == partType ) 21 | } 22 | 23 | def getSelectedObjects: Seq[SceneSelection] = { 24 | self.filter(x => x.partType == null ) 25 | } 26 | 27 | def getSelectedForObject(objectIndex: Int): Seq[SceneSelection] = { 28 | self.filter(x => x.objectIndex == objectIndex) 29 | } 30 | 31 | def findSelectionIndex(objectIndex: Int): Int = { 32 | self.indexWhere(x => x.objectIndex == objectIndex && x.partType == null) 33 | } 34 | 35 | def findSelectionIndex(ss: SceneSelection): Int = { 36 | self.indexOf(ss) 37 | } 38 | } 39 | 40 | class MutableSceneSelections(buffer: ArrayBuffer[SceneSelection] = new ArrayBuffer) extends SceneSelections(buffer) { 41 | def remove(i: Int) = buffer.remove(i) 42 | def add(ss: SceneSelection) = buffer.append(ss) 43 | def clear() = buffer.clear() 44 | def select(ss: SceneSelection): Int = { 45 | val index = findSelectionIndex(ss) 46 | if (index >= 0) index 47 | else { 48 | buffer.append(ss) 49 | buffer.length-1 50 | } 51 | } 52 | def unselect(ss: SceneSelection) { 53 | val index = findSelectionIndex(ss) 54 | if (index >= 0) { 55 | remove(index) 56 | } 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/common/SceneState.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.common 2 | 3 | /** 4 | * State of a scene (scene + selections) 5 | * @author Angel Chang 6 | */ 7 | case class SceneState(scene: Scene, 8 | var selections: Seq[SceneSelection] = Seq()) { 9 | } -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/jme3/JmeConfig.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3 2 | 3 | import com.typesafe.config.{ConfigFactory, Config} 4 | import edu.stanford.graphics.shapenet.util.ConfigHelper 5 | import edu.stanford.graphics.shapenet.jme3.loaders.LoadFormat 6 | import scala.collection.JavaConversions._ 7 | 8 | /** 9 | * Configuration for the main JME object 10 | * @author Angel Chang 11 | */ 12 | class JmeConfig(val modelCacheSize: Option[Int] = None, 13 | val defaultLoadFormat: Option[LoadFormat.Value] = None) { 14 | } 15 | 16 | object JmeConfig { 17 | // Stupid type-config - have to define defaults for everything.... 18 | val defaults = ConfigFactory.parseMap( 19 | Map[String,Object]( 20 | ) 21 | ) 22 | def apply(): JmeConfig = JmeConfig(ConfigFactory.empty()) 23 | def apply(inputConfig: Config, name: String = "jme"): JmeConfig = { 24 | val config = if (inputConfig == null) defaults else inputConfig.withFallback(defaults) 25 | val configHelper = new ConfigHelper(config) 26 | new JmeConfig( 27 | modelCacheSize = configHelper.getIntOption(name + ".modelCacheSize"), 28 | defaultLoadFormat = configHelper.getStringOption(name + ".defaultLoadFormat").map( s => LoadFormat.withName(s)) 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/jme3/viewer/OffscreenAnalyzer.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.viewer 2 | 3 | import com.jme3.renderer.{Camera, RenderManager} 4 | import com.jme3.math.Transform 5 | import edu.stanford.graphics.shapenet.common.CameraState 6 | 7 | /** 8 | * Wrapper for offscreen analysis 9 | * @author Angel Chang 10 | */ 11 | class OffscreenAnalyzer(val renderManager: RenderManager, 12 | val width: Int, val height: Int, 13 | val transform: Transform = null) { 14 | // Member variables for offscreen analysis 15 | private var offscreen: OffscreenView = null 16 | private var offscreenDisplay: ImageDisplaySceneProcessor = null 17 | private var sceneStats: SceneStatsSceneProcessor = null 18 | prepareOffscreen() 19 | 20 | def getSceneStats = sceneStats 21 | def getOffScreen = offscreen 22 | def getOffScreenDisplay = offscreenDisplay 23 | 24 | private def prepareOffscreen() { 25 | // Prepare a offscreen view for offscreen computations 26 | offscreen = new OffscreenView(renderManager, width, height, transform) 27 | // Add processor for debugging off screen image by displaying 28 | offscreenDisplay = new ImageDisplaySceneProcessor(false) 29 | offscreen.addProcessor(offscreenDisplay) 30 | 31 | // Add processor for getting scene stats 32 | sceneStats = new SceneStatsSceneProcessor() 33 | offscreen.addProcessor(sceneStats) 34 | } 35 | 36 | def enableDisplay(flag: Boolean) { 37 | offscreenDisplay.setEnabled(flag) 38 | } 39 | 40 | def update(tpf: Float) { 41 | offscreen.update(tpf) 42 | } 43 | 44 | def setCamera(cam: Camera) { 45 | offscreen.setCamera(cam) 46 | } 47 | 48 | def setCameraState(cam: CameraState) { 49 | offscreen.setCamera(cam) 50 | } 51 | 52 | def setCameraFrame(cam: Camera) { 53 | offscreen.setCameraFrame(cam) 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/jme3/viewer/OffscreenView.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.viewer 2 | 3 | import edu.stanford.graphics.shapenet.common.CameraState 4 | import edu.stanford.graphics.shapenet.util.{IOUtils, ImageWriter, Loggable} 5 | import com.jme3.math.{ColorRGBA, Transform, Vector3f} 6 | import com.jme3.post.SceneProcessor 7 | import com.jme3.renderer.{Camera, RenderManager} 8 | import com.jme3.scene.{Node, Spatial} 9 | import com.jme3.texture.FrameBuffer 10 | import com.jme3.texture.Image.Format 11 | import com.jme3.util.BufferUtils 12 | import java.io.{File, IOException, OutputStream} 13 | 14 | /** 15 | * Offscreen view 16 | * @author Angel Chang 17 | */ 18 | class OffscreenView(val renderManager: RenderManager, 19 | val width: Int, val height: Int, val transform: Transform = null) extends Loggable { 20 | val camera: Camera = new Camera(width,height) 21 | camera.setFrustumPerspective(30f, camera.getWidth().toFloat / camera.getHeight(), 1f, 1000f) 22 | camera.setLocation(new Vector3f(0f, 0f, 10f)) 23 | camera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y) 24 | 25 | // create offscreen framebuffer 26 | val framebuffer = new FrameBuffer(width, height, 1) 27 | //setup framebuffer to use renderbuffer 28 | // this is faster for gpu -> cpu copies 29 | framebuffer.setDepthBuffer(Format.Depth) 30 | framebuffer.setColorBuffer(Format.RGBA8) 31 | // offBuffer.setColorTexture(offTex); 32 | 33 | // create a pre-view. a view that is rendered before the main view 34 | val viewport = renderManager.createPreView("Offscreen View", camera) 35 | viewport.setBackgroundColor(new ColorRGBA(1.0f, 1.0f, 1.0f, 0.0f)) 36 | viewport.setClearFlags(true, true, true) 37 | 38 | //set viewport to render to offscreen framebuffer 39 | viewport.setOutputFrameBuffer(framebuffer) 40 | 41 | val rootNode = new Node("Offscreen rootnode") 42 | if (transform != null) rootNode.setLocalTransform(transform) 43 | // attach the scene to the viewport to be rendered 44 | viewport.attachScene(rootNode) 45 | 46 | // Offscreen view functions 47 | 48 | def addProcessor(sceneProcessor: SceneProcessor) { 49 | // this will let us know when the scene has been rendered to the 50 | // frame buffer 51 | if (!sceneProcessor.isInitialized) { 52 | sceneProcessor.initialize(renderManager, viewport) 53 | } 54 | viewport.addProcessor(sceneProcessor) 55 | } 56 | 57 | def viewScene(scene: Spatial){ 58 | rootNode.detachAllChildren() 59 | rootNode.attachChild(scene) 60 | } 61 | 62 | def setCamera(c: CameraState) { 63 | c.setCamera(camera) 64 | } 65 | 66 | def setCamera(c: Camera) { 67 | camera.copyFrom(c) 68 | } 69 | 70 | // Don't copy width/height... 71 | def setCameraFrame(cam: Camera) { 72 | camera.setRotation(cam.getRotation) 73 | camera.setLocation(cam.getLocation) 74 | } 75 | 76 | def update(tpf: Float) { 77 | // These need to be call before the scene can be rendered 78 | // (should be done as part of the AppState update) 79 | rootNode.updateLogicalState(tpf) 80 | rootNode.updateGeometricState() 81 | } 82 | 83 | def saveImage(filename: String, imageFormat: String = "png") { 84 | val outBuf = BufferUtils.createByteBuffer(width * height * 4) 85 | renderManager.getRenderer.readFrameBuffer(framebuffer, outBuf) 86 | val file = new File(filename) 87 | var outStream: OutputStream = null 88 | logger.info("Saving offscreen view to: {0}", file.getAbsolutePath()) 89 | try { 90 | outStream = IOUtils.fileOutputStream(filename) 91 | ImageWriter.writeImageFile(outStream, imageFormat, outBuf, width, height) 92 | } catch { 93 | case ex: IOException => { 94 | logger.error("Error while saving offscreen view", ex) 95 | } 96 | } finally { 97 | if (outStream != null) { 98 | try { 99 | outStream.close 100 | } 101 | catch { 102 | case ex: IOException => { 103 | logger.error("Error while saving offscreen view", ex) 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | def clearProcessors() { 111 | viewport.clearProcessors() 112 | } 113 | } -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/jme3/viewer/ProgressBarControl.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.viewer 2 | 3 | import de.lessvoid.nifty.controls.{Parameters, Controller} 4 | import de.lessvoid.nifty.elements.Element 5 | import de.lessvoid.nifty.Nifty 6 | import de.lessvoid.nifty.screen.Screen 7 | import de.lessvoid.nifty.input.NiftyInputEvent 8 | 9 | /** 10 | * Progress bar controller 11 | * @author Angel Chang 12 | */ 13 | class ProgressBarControl extends Controller { 14 | var progressBarElement: Element = null 15 | override def bind(nifty: Nifty, screen: Screen, elem: Element, params: Parameters) { 16 | progressBarElement = elem.findElementById("progressbar") 17 | } 18 | 19 | override def inputEvent(inputEvent: NiftyInputEvent) = true 20 | 21 | override def init(params: Parameters) {} 22 | 23 | override def onFocus(getFocus: Boolean) {} 24 | 25 | override def onStartScreen() {} 26 | } 27 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/jme3/viewer/RenderTask.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.viewer 2 | 3 | import com.jme3.app.state.AbstractAppState 4 | import scala.util.control.Breaks 5 | import java.util.concurrent._ 6 | import java.util.concurrent.locks.{Condition, ReentrantLock} 7 | import scala.Some 8 | 9 | /** 10 | * A task that requires doing rendering, potentially going through multiple render cycles 11 | * At each update step, only one RenderTask is executed 12 | * @author Angel Chang 13 | */ 14 | trait RenderTask { 15 | // Update for rendering 16 | def update(tpf: Float): RenderTaskStatus.Value 17 | // Done 18 | def done() = {} 19 | // is done? 20 | def isDone: Boolean = false 21 | } 22 | 23 | object RenderTaskStatus extends Enumeration { 24 | type RenderTaskStatus = Value 25 | val Updated, Waiting, Cancelled, Done = Value 26 | } 27 | 28 | trait RenderTaskListener[T] { 29 | def updated(result: T) = {} 30 | def done(result: T) = {} 31 | } 32 | 33 | trait RenderTaskListenerWithResult[T] extends RenderTaskListener[T] with Future[Option[T]] { 34 | private var resultOption: Option[T] = null 35 | 36 | private var exception: ExecutionException = null 37 | private var cancelled: Boolean = false 38 | private var finished: Boolean = false 39 | private final val stateLock: ReentrantLock = new ReentrantLock 40 | private final val finishedCondition: Condition = stateLock.newCondition 41 | 42 | override def done(result: T): Unit = { 43 | stateLock.lock() 44 | try { 45 | finished = true 46 | resultOption = Some(result) 47 | finishedCondition.signalAll() 48 | } finally { 49 | stateLock.unlock() 50 | } 51 | } 52 | 53 | // Implements future.... 54 | def get(): Option[T] = { 55 | stateLock.lock() 56 | try { 57 | while (!isDone) { 58 | finishedCondition.await() 59 | } 60 | if (exception != null) { 61 | throw exception 62 | } 63 | resultOption 64 | } finally { 65 | stateLock.unlock() 66 | } 67 | } 68 | def get(timeout: Long, unit: TimeUnit): Option[T] = { 69 | stateLock.lock() 70 | try { 71 | if (!isDone) { 72 | finishedCondition.await(timeout, unit) 73 | } 74 | if (exception != null) { 75 | throw exception 76 | } 77 | if (isDone) { 78 | resultOption 79 | } else { 80 | throw new TimeoutException("Object not returned in time allocated.") 81 | } 82 | } finally { 83 | stateLock.unlock() 84 | } 85 | } 86 | 87 | override def isDone: Boolean = { 88 | stateLock.lock() 89 | try { 90 | finished || cancelled || (exception != null) 91 | } finally { 92 | stateLock.unlock() 93 | } 94 | } 95 | 96 | def isCancelled: Boolean = { 97 | stateLock.lock() 98 | try { 99 | cancelled 100 | } finally { 101 | stateLock.unlock() 102 | } 103 | } 104 | 105 | def cancel(mayInterruptIfRunning: Boolean): Boolean = { 106 | stateLock.lock() 107 | try { 108 | if (isDone) { 109 | return false 110 | } 111 | cancelled = true 112 | finishedCondition.signalAll() 113 | return cancelled 114 | } 115 | finally { 116 | stateLock.unlock() 117 | } 118 | } 119 | } 120 | 121 | class RenderTaskQueue extends AbstractAppState { 122 | val taskQueue = new scala.collection.mutable.Queue[RenderTask] 123 | val mybreaks = new Breaks 124 | import mybreaks.{break, breakable} 125 | 126 | override def update(tpf: Float) { 127 | // Go through taskQueue until we have executed something 128 | var toRemove: RenderTask = null 129 | breakable { 130 | for (t <- taskQueue) { 131 | if (!t.isDone) { 132 | val status = t.update(tpf) 133 | status match { 134 | case RenderTaskStatus.Updated => { 135 | break() 136 | } 137 | case RenderTaskStatus.Done => { 138 | t.done() 139 | toRemove = t 140 | break() 141 | } 142 | case _ => {} 143 | } 144 | } else { 145 | toRemove = t 146 | break() 147 | } 148 | } 149 | } 150 | if (toRemove != null) { 151 | taskQueue.dequeueFirst( x => toRemove == x ) 152 | } 153 | } 154 | def enqueue(tasks: RenderTask*) = taskQueue.enqueue(tasks:_*) 155 | def isEmpty() = { 156 | taskQueue.isEmpty 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/jme3/viewer/SceneImagesGenerator.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.viewer 2 | 3 | import edu.stanford.graphics.shapenet.common.FullId 4 | import edu.stanford.graphics.shapenet.jme3.Jme 5 | import edu.stanford.graphics.shapenet.common.GeometricScene 6 | import edu.stanford.graphics.shapenet.util.{Loggable, IOUtils} 7 | import com.jme3.scene.Node 8 | import java.io.File 9 | import scala.util.Random 10 | 11 | /** 12 | * Generates and saves away images for scenes 13 | * @author Angel Chang 14 | */ 15 | class SceneImagesGenerator(val viewer: Viewer, 16 | val sceneProcessor: (GenerateImagesFnOptions,GeometricScene[Node]) => _ = null, 17 | val randomize: Boolean = false, 18 | val skipExisting: Boolean = false, 19 | val getOutputDirFn: FullId => String = null) extends Loggable { 20 | val generateImagesState: GenerateImagesAppState = viewer.generateImagesState 21 | 22 | protected var camPositionGenerator: CameraPositionGenerator = null 23 | 24 | def configCameraPositions(cameraPositionOptions: CameraPositionOptions = CameraPositionGenerator.defaultCameraPositionOptions, 25 | nViews: Int = 1, 26 | useDebugPosition: Boolean = viewer.config.useDebugPositionsForSceneImages) = { 27 | this.camPositionGenerator = CameraPositionGenerator(viewer, cameraPositionOptions, nViews, useDebugPosition) 28 | } 29 | 30 | def configCameraPositions(cameraPositionGenerator: CameraPositionGenerator): Unit = { 31 | this.camPositionGenerator = cameraPositionGenerator 32 | } 33 | 34 | def process(inputSceneIds: Iterable[String], outputDirName: String, appendMode: Boolean = false)(implicit jme: Jme) { 35 | val outputDir = IOUtils.ensureDirname(outputDirName) 36 | if (camPositionGenerator == null) { 37 | configCameraPositions() 38 | } 39 | val sceneIds = if (randomize) Random.shuffle(inputSceneIds.toSeq) else inputSceneIds 40 | var nProcessed = 0 41 | val genFn = new GenerateImagesFn { 42 | def apply(options: GenerateImagesFnOptions, scene: GeometricScene[Node]): Seq[ScreenShotInfo] = { 43 | // take scene and get extract surfaces 44 | nProcessed += 1 45 | logger.info("Processing " + nProcessed) 46 | if (sceneProcessor != null) { 47 | sceneProcessor.apply(options, scene) 48 | } 49 | // produce screen shot infos 50 | val cameraPositions = camPositionGenerator.generatePositions(scene.node) 51 | val screenshots = cameraPositions.zipWithIndex.map(x => 52 | toScreenShot(options, x._1, x._2) 53 | ) 54 | logger.info("Processed: " + scene.scene.sceneId) 55 | screenshots.toSeq 56 | } 57 | } 58 | 59 | // Make sure that the menu is hidden 60 | viewer.hideMenu() 61 | generateImagesState.imageFilenameUseFullId = false 62 | 63 | generateImagesState.setScreenShotDir(outputDir) 64 | generateImagesState.setSummaryFile(outputDir + "summary.csv", appendMode) 65 | // Have different output dir per model 66 | var skipped = 0 67 | var enqueued = 0 68 | for (sceneId <- sceneIds) { 69 | // Skip if we already have a directory with images and stuff 70 | val fullId = FullId(sceneId) 71 | val dir = if (getOutputDirFn != null) { 72 | outputDir + File.separator + getOutputDirFn(fullId) 73 | } else { 74 | outputDir + File.separator + fullId.source + File.separator + fullId.id + File.separator 75 | } 76 | val files = IOUtils.listFiles(dir) 77 | val doScene = if (skipExisting) { 78 | val pngs = files.filter( x => x.getName.endsWith(".png")).length 79 | pngs < camPositionGenerator.nViews 80 | } else true 81 | if (doScene) { 82 | generateImagesState.setScreenShotDir(dir) 83 | generateImagesState.enqueueSceneIds(genFn, ActionStates.EMPTY, sceneId) 84 | enqueued += 1 85 | } else { 86 | skipped += 1 87 | } 88 | } 89 | logger.info("Enqueued: " + enqueued + ", Skipped: " + skipped) 90 | 91 | generateImagesState.closeSummaryFile() 92 | generateImagesState.setScreenShotDir(viewer.screenShotDir) 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/jme3/viewer/SimpleRenderer.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.jme3.viewer 2 | 3 | import com.jme3.system._ 4 | import com.jme3.renderer.{ViewPort, Renderer, Camera, RenderManager} 5 | import com.jme3.scene.{Geometry, Node, Spatial} 6 | import com.jme3.math.{Vector3f, ColorRGBA} 7 | import edu.stanford.graphics.shapenet.jme3.Jme 8 | import com.jme3.scene.shape.Box 9 | import com.jme3.material.Material 10 | 11 | /** 12 | * Simple API to render a scene 13 | * @author Angel Chang 14 | */ 15 | class SimpleRenderer(val width: Int, val height: Int, val isOffscreen: Boolean = false) extends SystemListener { 16 | val settings: AppSettings = new AppSettings(true) 17 | settings.setWidth(width) 18 | settings.setHeight(height) 19 | 20 | val contextType = if (isOffscreen) JmeContext.Type.OffscreenSurface else JmeContext.Type.Display 21 | val context = JmeSystem.newContext(settings, contextType) 22 | context.setSystemListener(this) 23 | context.create(true) 24 | 25 | var timer: Timer = null 26 | var renderer: Renderer = null 27 | var renderManager: RenderManager = null 28 | 29 | var speed = 1f 30 | var camera: Camera = null 31 | var viewport: ViewPort = null 32 | var rootNode: Node = null 33 | private var scene: Spatial = null 34 | 35 | def getCamera = camera 36 | def setCamera(cam: Camera) { 37 | this.camera.copyFrom(cam) 38 | } 39 | 40 | def renderScene(scene: Spatial) { 41 | this.scene = scene 42 | //renderManager.renderScene() 43 | } 44 | 45 | def destroy() {} 46 | 47 | def handleError(errorMsg: String, t: Throwable) {} 48 | 49 | def loseFocus() {} 50 | 51 | def gainFocus() {} 52 | 53 | def requestClose(esc: Boolean) {} 54 | 55 | def update() { 56 | val tpf: Float = timer.getTimePerFrame * speed 57 | if (scene != null) { 58 | rootNode.detachAllChildren() 59 | rootNode.attachChild(scene) 60 | } 61 | rootNode.updateLogicalState(tpf) 62 | rootNode.updateGeometricState() 63 | renderManager.render(tpf, context.isRenderable) 64 | } 65 | 66 | def reshape(width: Int, height: Int) {} 67 | 68 | def initialize() { 69 | timer = context.getTimer 70 | renderer = context.getRenderer 71 | renderManager = new RenderManager(renderer) 72 | renderManager.setTimer(timer) 73 | 74 | camera = new Camera(width,height) 75 | camera.setFrustumPerspective(45f, camera.getWidth().toFloat / camera.getHeight(), 1f, 1000f) 76 | camera.setLocation(new Vector3f(0f, 0f, 10f)) 77 | camera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y) 78 | 79 | // Attach viewport to renderer 80 | // val viewport = renderManager.createPreView("Offscreen View", camera) 81 | viewport = renderManager.createMainView("Default View", camera) 82 | viewport.setBackgroundColor(ColorRGBA.White) 83 | viewport.setClearFlags(true, true, true) 84 | 85 | rootNode = new Node() 86 | viewport.attachScene(rootNode) 87 | } 88 | } 89 | 90 | class SimpleOffscreenRenderer(width: Int, height: Int) extends SimpleRenderer(width, height, true) { 91 | private var offscreenAnalyzer: OffscreenAnalyzer = null 92 | def getSceneStats = offscreenAnalyzer.getSceneStats 93 | def getOffScreen = offscreenAnalyzer.getOffScreen 94 | def getOffScreenDisplay = offscreenAnalyzer.getOffScreenDisplay 95 | def getOffScreenAnalyzer = offscreenAnalyzer 96 | val renderTasks = new RenderTaskQueue() 97 | 98 | private def prepareOffscreen() { 99 | // Prepare a offscreen view for offscreen computations 100 | offscreenAnalyzer = new OffscreenAnalyzer(renderManager, camera.getWidth, camera.getHeight/*, rootSceneNode.getLocalTransform*/) 101 | } 102 | 103 | override def initialize() { 104 | super.initialize() 105 | prepareOffscreen() 106 | } 107 | 108 | override def update() { 109 | super.update() 110 | val tpf: Float = timer.getTimePerFrame * speed 111 | renderTasks.update(tpf) 112 | if (offscreenAnalyzer != null) offscreenAnalyzer.update(tpf) 113 | } 114 | } 115 | 116 | 117 | object SimpleRendererTest extends App { 118 | val jme = Jme() 119 | val b = new Box(1, 1, 1) // create cube shape 120 | val geom = new Geometry("Box", b) // create cube geometry from the shape 121 | val mat = new Material(jme.assetManager, 122 | "Common/MatDefs/Misc/Unshaded.j3md") // create a simple material 123 | mat.setColor("Color", ColorRGBA.Blue) // set color of material to blue 124 | geom.setMaterial(mat); // set the cube's material 125 | 126 | val simpleRenderer = new SimpleRenderer(800,600,isOffscreen=false) 127 | simpleRenderer.renderScene(geom) 128 | } 129 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/util/BatchSampler.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.util 2 | 3 | import scala.util.Random 4 | import java.util.PriorityQueue 5 | import scala.collection.JavaConversions._ 6 | 7 | /** 8 | * Given set of elements, samples n elements 9 | * 10 | * @author Angel Chang 11 | */ 12 | class BatchSampler { 13 | def random(size: Int, rand: Random = Random): Int = { 14 | rand.nextInt(size) 15 | } 16 | 17 | def sampleOne[E](samples: IndexedSeq[E], rand: Random = Random): Option[E] = { 18 | val total = samples.size 19 | if (total > 0) { 20 | val i = rand.nextInt(total) 21 | Some(samples(i)) 22 | } else { 23 | None 24 | } 25 | } 26 | 27 | def sampleIndices(sampleSize: Int, totalSize: Int, shuffleOrder: Boolean = true, rand: Random = Random): IndexedSeq[Int] = { 28 | if (sampleSize >= totalSize) { 29 | val samples = (0 until totalSize).toIndexedSeq 30 | if (shuffleOrder) rand.shuffle(samples) 31 | else samples 32 | } else { 33 | val samples = Array.ofDim[Int](sampleSize) 34 | var t = 0 // # of total processed 35 | var m = 0 // # of samples selected 36 | while (m < sampleSize && t < totalSize) { 37 | val r = rand.nextFloat() 38 | if ( (totalSize - t)*r < sampleSize - m) { 39 | samples(m) = t 40 | m = m+1 41 | } 42 | t = t+1 43 | } 44 | if (shuffleOrder) 45 | rand.shuffle(samples.toIndexedSeq) 46 | else samples.toIndexedSeq 47 | } 48 | } 49 | 50 | def sampleWithoutReplacement[E](allSamples: Iterable[E], nSamples: Int, randOrder: Boolean = true, rand: Random = Random): IndexedSeq[E] = { 51 | val indexed = allSamples.toIndexedSeq 52 | if (nSamples < 10 || nSamples < indexed.size/2) { 53 | val indices = sampleIndices(nSamples, indexed.size, randOrder) 54 | indices.map( x => indexed(x) ) 55 | } else { 56 | val permutedList = rand.shuffle(indexed) 57 | permutedList.slice(0, nSamples) 58 | } 59 | } 60 | 61 | def sampleWithReplacement[E](allSamples: Iterable[E], nSamples: Int, rand: Random = Random): IndexedSeq[E] = { 62 | val ordered = allSamples.toIndexedSeq 63 | for (i <- 0 until nSamples) yield { 64 | val r = rand.nextInt(ordered.size) 65 | ordered(r) 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/util/ConversionUtils.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.util 2 | 3 | /** 4 | * Some generic conversion utils that are helpful 5 | * @author Angel Chang 6 | */ 7 | object ConversionUtils { 8 | type or[L,R] = Either[L,R] 9 | 10 | implicit def l2Or[L,R](l: L): L or R = Left(l) 11 | implicit def r2Or[L,R](r: R): L or R = Right(r) 12 | 13 | def seqToJavaList[T](seq: Seq[T]): java.util.List[T] = { 14 | val list = new java.util.ArrayList[T](seq.size) 15 | for (elem <- seq) { 16 | list.add(elem) 17 | } 18 | list 19 | } 20 | 21 | import java.util.function.{ Function => JFunction, Predicate => JPredicate, BiPredicate } 22 | 23 | //usage example: `i: Int => 42` 24 | implicit def toJavaFunction[A, B](f: Function1[A, B]) = new JFunction[A, B] { 25 | override def apply(a: A): B = f(a) 26 | } 27 | 28 | //usage example: `i: Int => true` 29 | implicit def toJavaFunction[A, B, C](f: Function1[A, C], res: B) = new JFunction[A, B] { 30 | override def apply(a: A): B = { 31 | f(a) 32 | res 33 | } 34 | } 35 | 36 | implicit def toJavaPredicate[A](f: Function1[A, Boolean]) = new JPredicate[A] { 37 | override def test(a: A): Boolean = f(a) 38 | } 39 | 40 | //usage example: `(i: Int, s: String) => true` 41 | implicit def toJavaBiPredicate[A, B](predicate: (A, B) => Boolean) = 42 | new BiPredicate[A, B] { 43 | def test(a: A, b: B) = predicate(a, b) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/util/LRUCache.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.util 2 | 3 | import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap 4 | import scala.ref.SoftReference 5 | 6 | // Simple LRU - wrapper around concurrentlinkedhashmap (can use spray-caching in future) 7 | case class LRUCache[A, B](maxCapacity: Int, initialCapacity: Int = 16) 8 | { 9 | val maxWeightedCapacity = if (maxCapacity < 0) Long.MaxValue else maxCapacity 10 | protected val cache = new ConcurrentLinkedHashMap.Builder[A, B] 11 | .initialCapacity(initialCapacity) 12 | .maximumWeightedCapacity(maxWeightedCapacity) 13 | .build() 14 | //LruCache[A,B](maxCapacity = MAX_ENTRIES) 15 | 16 | def getOrElse(key: A)(fn: => B): B = { 17 | get(key).getOrElse { 18 | val result = fn 19 | put(key, result) 20 | result 21 | } 22 | } 23 | 24 | def get(key: A): Option[B] = Option(cache.get(key)) 25 | 26 | def put(key: A, value: B) = cache.put(key, value) 27 | 28 | def remove(key: A) = cache.remove(key) 29 | 30 | def clear() = cache.clear() 31 | } 32 | 33 | case class SoftLRUCache[A,B <: AnyRef](maxCapacity: Int, initialCapacity: Int = 16) 34 | { 35 | val lruCache = LRUCache[A,SoftReference[B]](maxCapacity, initialCapacity) 36 | 37 | def getOrElse(key: A)(fn: => B): B = { 38 | val v = get(key) 39 | if (v.isEmpty) { 40 | val result = fn 41 | lruCache.put(key, new SoftReference(result)) 42 | result 43 | } else v.get 44 | } 45 | 46 | def get(key: A): Option[B] = { 47 | val v = lruCache.get(key) 48 | if (v.isEmpty || v.get.get.isEmpty) { 49 | None 50 | } else v.get.get 51 | } 52 | 53 | def put(key: A, value: B) = lruCache.put(key, new SoftReference(value)) 54 | 55 | def remove(key: A) = lruCache.remove(key) 56 | 57 | def clear() = lruCache.clear() 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/util/Logger.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.util 2 | 3 | import org.slf4j.LoggerFactory 4 | import java.io.File 5 | 6 | import org.slf4j.bridge.SLF4JBridgeHandler 7 | import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J 8 | 9 | /** 10 | * Logging utilities 11 | * @author Angel Chang 12 | */ 13 | object Logger { 14 | setup() 15 | 16 | private def setup(): Unit = { 17 | // Forward system out and err to slf4j 18 | SysOutOverSLF4J.sendSystemOutAndErrToSLF4J() 19 | Console.setOut(System.out) 20 | Console.setErr(System.err) 21 | 22 | // Forward j.u.l to slf4j 23 | // Optionally remove existing handlers attached to j.u.l root logger 24 | SLF4JBridgeHandler.removeHandlersForRootLogger() // (since SLF4J 1.6.5) 25 | 26 | // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during 27 | // the initialization phase of your application 28 | SLF4JBridgeHandler.install() 29 | } 30 | 31 | private val DEFAULT_PATTERN = "%date %level [%thread] %logger{10} [%file:%line] %msg%n" 32 | 33 | def appendToFile(filename: String, 34 | pattern: String = DEFAULT_PATTERN, 35 | loggerName: String = org.slf4j.Logger.ROOT_LOGGER_NAME, 36 | /* set to true if root should log too */ 37 | additive: Boolean = false) = { 38 | import ch.qos.logback.classic.spi.ILoggingEvent 39 | import ch.qos.logback.classic.Level 40 | import ch.qos.logback.classic.LoggerContext 41 | import ch.qos.logback.classic.encoder.PatternLayoutEncoder 42 | import ch.qos.logback.core.FileAppender 43 | 44 | // Make sure log directory is created 45 | val file: File = new File(filename) 46 | val parent: File = file.getParentFile 47 | if (parent != null) parent.mkdirs 48 | 49 | val loggerContext = LoggerFactory.getILoggerFactory().asInstanceOf[LoggerContext] 50 | val logger = loggerContext.getLogger(loggerName) 51 | 52 | // Setup pattern 53 | val patternLayoutEncoder = new PatternLayoutEncoder() 54 | patternLayoutEncoder.setPattern(pattern) 55 | patternLayoutEncoder.setContext(loggerContext) 56 | patternLayoutEncoder.start() 57 | 58 | // Setup appender 59 | val fileAppender = new FileAppender[ILoggingEvent]() 60 | fileAppender.setFile(filename) 61 | fileAppender.setEncoder(patternLayoutEncoder) 62 | fileAppender.setContext(loggerContext) 63 | fileAppender.start() 64 | 65 | // Attach appender to logger 66 | logger.addAppender(fileAppender) 67 | //logger.setLevel(Level.DEBUG) 68 | logger.setAdditive(additive) 69 | 70 | fileAppender.getName 71 | } 72 | 73 | def detachAppender(appenderName: String, loggerName: String = org.slf4j.Logger.ROOT_LOGGER_NAME): Unit = { 74 | import ch.qos.logback.classic.LoggerContext 75 | 76 | val loggerContext = LoggerFactory.getILoggerFactory().asInstanceOf[LoggerContext] 77 | val logger = loggerContext.getLogger(loggerName) 78 | logger.detachAppender(appenderName) 79 | } 80 | 81 | def getLogger(clazz: Class[_]): org.slf4j.Logger = { 82 | LoggerFactory.getLogger(clazz) 83 | } 84 | 85 | def getLogger(name: String): org.slf4j.Logger = { 86 | LoggerFactory.getLogger(name) 87 | } 88 | } 89 | 90 | trait Loggable { 91 | lazy val logger = Logger.getLogger(this.getClass) 92 | 93 | def startTrack(name: String): Unit = { 94 | logger.debug("Starting " + name) 95 | } 96 | 97 | def endTrack(name: String): Unit = { 98 | logger.debug("Finished " + name) 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/util/StringUtils.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.util 2 | 3 | import java.io.File 4 | import java.util.regex.Pattern 5 | import java.text.SimpleDateFormat 6 | 7 | /** 8 | * String Utilities 9 | * @author Angel Chang 10 | */ 11 | object StringUtils { 12 | private final val camelCasePattern: Pattern = Pattern.compile("([a-z])([A-Z])") 13 | final val underscorePattern: Pattern = Pattern.compile("_") 14 | final val whitespacePattern: Pattern = Pattern.compile("\\s+") 15 | val datetimeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss") 16 | val datetimeFormatMillis = new SimpleDateFormat("yyyyMMdd'T'HHmmss.SSS") 17 | 18 | def ensureDir(str:String):String = if (str.endsWith(File.separator)) str else str + File.separator 19 | 20 | def formatDatetime(millis: Long): String = { 21 | datetimeFormat.format(millis) 22 | } 23 | 24 | def formatMillis(millis: Long): String = { 25 | datetimeFormatMillis.format(millis) 26 | } 27 | 28 | def currentDatetime(): String = { 29 | formatDatetime(System.currentTimeMillis()) 30 | } 31 | 32 | def toMillis(str:String): Long = { 33 | datetimeFormat.parse(str).getTime 34 | } 35 | 36 | // If a string begins and ends with '"', trims the quotes 37 | def trimQuotes(str: String): String = { 38 | if (str != null && str.length > 0) { 39 | if (str.head == '"' && str.last == '"') { 40 | return str.substring(1, str.length-1) 41 | } 42 | } 43 | return str 44 | } 45 | 46 | // Converts string of the form LivingRoom to Living Room 47 | def camelCaseToText(cc: String, lowercase: Boolean = false): String = { 48 | if (cc != null) { 49 | val s = camelCasePattern.matcher(cc).replaceAll("$1 $2") 50 | if (lowercase) s.toLowerCase() 51 | else s 52 | } else null 53 | } 54 | 55 | def splitCamelCase(cc: String): Array[String] = { 56 | val s = camelCasePattern.matcher(cc).replaceAll("$1 $2") 57 | s.split(" ") 58 | } 59 | 60 | def textToCamelCase(x: String): String = { 61 | if (x != null) 62 | x.split("\\s+").map( a => a.capitalize ).mkString("") 63 | else null 64 | } 65 | 66 | // Converts string of the form living_room to living room 67 | def delimitedToText(x: String, delimiterPattern: Pattern = underscorePattern, lowercase: Boolean = false): String = { 68 | if (x != null) { 69 | val s = delimiterPattern.matcher(x).replaceAll(" ") 70 | if (lowercase) s.toLowerCase() 71 | else s 72 | } else null 73 | } 74 | 75 | def textToDelimited(x: String, delimiter: String = "_", lowercase: Boolean = false): String = { 76 | if (x != null) { 77 | val s = whitespacePattern.matcher(x).replaceAll(delimiter) 78 | if (lowercase) s.toLowerCase() 79 | else s 80 | } else null 81 | } 82 | 83 | def toCamelCase(s: String) = { 84 | val t = StringUtils.delimitedToText(s) 85 | StringUtils.textToCamelCase(t) 86 | } 87 | 88 | def toLowercase(s: String, delimiter: String = " ") = { 89 | val t = StringUtils.camelCaseToText(s, lowercase = true) 90 | StringUtils.textToDelimited(t, delimiter) 91 | } 92 | 93 | def isAllDigits(s: String): Boolean = { 94 | s.matches("^\\d*$") 95 | } 96 | 97 | def isInt(s: String) = isAllDigits(s) 98 | 99 | private def _vowels = Set('a','A','e','E','i','I','o','O','u','U','y','Y') 100 | def isVowel(ch: Char): Boolean = { 101 | _vowels.contains(ch) 102 | } 103 | 104 | def toVerticalString[T<:IndexedSeq[String]](rows: Seq[T]): String = { 105 | val maxLengths = new scala.collection.mutable.ArrayBuffer[Int]() 106 | for (row <- rows) { 107 | for (j <- maxLengths.size until row.length) { 108 | maxLengths.append(0) 109 | } 110 | for (i <- 0 until row.length) { 111 | val length = row(i).length 112 | if (length > maxLengths(i)) { 113 | maxLengths(i) = length 114 | } 115 | } 116 | } 117 | val sb = new StringBuilder() 118 | for (row <- rows) { 119 | if (sb.length > 0) { 120 | sb.append("\n") 121 | } 122 | for (i <- 0 until row.length) { 123 | sb.append(row(i)) 124 | val width = maxLengths(i) + 4 125 | for (j <- row(i).length until width) 126 | sb.append(" ") 127 | } 128 | } 129 | sb.toString 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/util/Threads.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.util 2 | 3 | import java.util.concurrent.{Future, Executors} 4 | 5 | object Threads extends Loggable { 6 | lazy val threadPool = Executors.newCachedThreadPool() 7 | def execute(runnable: Runnable, logger: org.slf4j.Logger = this.logger, desc: String = ""): Future[_] = { 8 | val wrappedRunnable = new RunnableWithLogging(runnable, logger, desc) 9 | threadPool.submit(wrappedRunnable) 10 | } 11 | } 12 | 13 | /** 14 | * Simple wrapper for runnable that catches and logs exceptions 15 | * @author Angel Chang 16 | */ 17 | class RunnableWithLogging(val runnable: Runnable, val logger: org.slf4j.Logger, val desc: String) extends Runnable { 18 | override def run(): Unit = { 19 | try { 20 | runnable.run() 21 | } catch { 22 | case ex: Throwable => { 23 | logger.error("Error running " + desc, ex) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/edu/stanford/graphics/shapenet/util/UserData.scala: -------------------------------------------------------------------------------- 1 | package edu.stanford.graphics.shapenet.util 2 | 3 | import scala.collection.mutable 4 | 5 | /** 6 | * Utility class for providing user data 7 | * @author Angel Chang 8 | */ 9 | class UserData { 10 | val userData = new mutable.WeakHashMap[Any, mutable.HashMap[String,Any]]() 11 | 12 | def get(s: Any) = userData.get(s) 13 | def clear(s: Any) = userData.get(s).map( x => x.clear() ) 14 | def copy(s: Any, t: Any) { 15 | clear(t) 16 | val sData = userData.getOrElse(s, null) 17 | if (sData != null) { 18 | val tData = userData.getOrElseUpdate(t, new mutable.HashMap[String,Any]()) 19 | for ((k,v) <- sData) 20 | tData.put(k,v) 21 | } 22 | } 23 | 24 | def get[T](s: Any, key: String): Option[T] = { 25 | val sData = userData.getOrElse(s, null) 26 | if (sData != null) 27 | sData.get(key).map( x => x.asInstanceOf[T] ) 28 | else None 29 | } 30 | 31 | def getOrElse[T](s: Any, key: String, default: => T): T = { 32 | val sData = userData.getOrElseUpdate(s, new mutable.HashMap[String,Any]()) 33 | sData.getOrElse(key, default).asInstanceOf[T] 34 | } 35 | 36 | def getOrElseUpdate[T](s: Any, key: String, default: => T): T = { 37 | val sData = userData.getOrElseUpdate(s, new mutable.HashMap[String,Any]()) 38 | sData.getOrElseUpdate(key, default).asInstanceOf[T] 39 | } 40 | 41 | def set[T](s: Any, key: String, data:T ): Option[T] = { 42 | val sData = userData.getOrElseUpdate(s, new mutable.HashMap[String,Any]()) 43 | sData.put(key, data).map( x => x.asInstanceOf[T] ) 44 | } 45 | 46 | def put[T](s: Any, key: String, data:T ): Option[T] = set(s,key,data) 47 | 48 | def remove[T](s: Any, key: String): Option[T] = { 49 | val sData = userData.get(s) 50 | sData.map( x => x.remove(key).map( y => y.asInstanceOf[T]) ).flatten 51 | } 52 | 53 | def contains(s: Any, key: String): Boolean = { 54 | val sData = userData.get(s) 55 | if (sData.isDefined) sData.get.contains(key) 56 | else false 57 | } 58 | 59 | 60 | } 61 | --------------------------------------------------------------------------------