Compare commits
155 Commits
v3.3.2-sta
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
6402aad452 | ||
|
e78f303a9d | ||
|
dd0c169d62 | ||
|
32e8b68ea0 | ||
|
eb7aab9704 | ||
|
d29d8f5100 | ||
|
acc5cfa995 | ||
|
f34e660f03 | ||
|
0f06c14c28 | ||
|
6ade1a027e | ||
|
30bddef8c0 | ||
|
f808187142 | ||
|
d6b0069407 | ||
|
c0692df207 | ||
|
036b08a385 | ||
|
407ebc9d5e | ||
|
f7352ffcfe | ||
|
599a0d5c56 | ||
|
8aa50e9b48 | ||
|
5ae9285903 | ||
|
bb6ac612ff | ||
|
9751e23cd7 | ||
|
ceea055cc4 | ||
|
d636049a6f | ||
|
917509a04e | ||
|
3fa85bc139 | ||
|
f3d81d11a1 | ||
|
37f1e7fabe | ||
|
a057a6d74d | ||
|
15e3acbe4d | ||
|
e3161f4622 | ||
|
c9357b4db1 | ||
|
2e56e1f24c | ||
|
32f71397c5 | ||
|
f6c467b495 | ||
|
84c88709b5 | ||
|
221acaadfe | ||
|
94451c4d35 | ||
|
7212f9b2b2 | ||
|
a5e0213b01 | ||
|
a1dcbf9b06 | ||
|
a42103651c | ||
|
cd7d497c79 | ||
|
1e7c95cbb8 | ||
|
ee910859ac | ||
|
5a2499ccbe | ||
|
4c62d5008e | ||
|
4d81fc6035 | ||
|
fa62959451 | ||
|
607e068f76 | ||
|
bb7d2cab01 | ||
|
6cbf62a3b5 | ||
|
03673ac1f7 | ||
|
32ab16e026 | ||
|
84836c6260 | ||
|
851793cde6 | ||
|
bee3d36f16 | ||
|
50dc8349a1 | ||
|
e99c8a74a3 | ||
|
f46a6fac11 | ||
|
64bfe42878 | ||
|
1c3ed5122f | ||
|
70ae48af1e | ||
|
c7a734d590 | ||
|
822bcd1300 | ||
|
ae9854df2b | ||
|
88c9371a4d | ||
|
297443ada4 | ||
|
60967b9f04 | ||
|
a6f2c12700 | ||
|
6c5611d3e2 | ||
|
89df7909d3 | ||
|
b92e7a0abe | ||
|
26f393d09c | ||
|
2e1a8dcb52 | ||
|
76210fe415 | ||
|
b48391399b | ||
|
4c8e400a83 | ||
|
d109e4739e | ||
|
16a3e7630c | ||
|
b8287a2d0d | ||
|
fef5c101d8 | ||
|
51022fd521 | ||
|
b613be35fa | ||
|
2a0fa2554a | ||
|
3385cdaa3f | ||
|
1433f98ad3 | ||
|
11d3bc67f8 | ||
|
bd4691e06b | ||
|
fafe8a74fe | ||
|
efc1962092 | ||
|
989b2a66cf | ||
|
0115616652 | ||
|
64797f9bff | ||
|
21b7a71cf8 | ||
|
8f8fc19043 | ||
|
646e6741b9 | ||
|
2276197a2b | ||
|
2379cfba77 | ||
|
df39677381 | ||
|
c198b1d546 | ||
|
d19ae582a1 | ||
|
461227bdef | ||
|
728a05c4f3 | ||
|
0c6f240222 | ||
|
4d81d00f45 | ||
|
312fb5bb57 | ||
|
7c01019f0c | ||
|
f505bdeb21 | ||
|
ff58abca3d | ||
|
59793d4c06 | ||
|
0aa50a0f15 | ||
|
0252c1b623 | ||
|
a407fc3224 | ||
|
b93ea18fa2 | ||
|
c1d359ca59 | ||
|
8e334aa756 | ||
|
426969daeb | ||
|
f652591281 | ||
|
12481c08f6 | ||
|
ab96460853 | ||
|
f268d00222 | ||
|
5584c93d4a | ||
|
8219d7fc02 | ||
|
dde0906963 | ||
|
e3b44db4aa | ||
|
31476679be | ||
|
0244ab230b | ||
|
1a0b6ecac3 | ||
|
acbddc2763 | ||
|
3c5dd5c168 | ||
|
0fd70b81c9 | ||
|
427ae0a28b | ||
|
2023440acf | ||
|
867e46190e | ||
|
eee37022f2 | ||
|
124ad35677 | ||
|
233bc6f0da | ||
|
ffd9cfcf25 | ||
|
81f9b9d92a | ||
|
9b29e05968 | ||
|
933b0912e6 | ||
|
ec491575be | ||
|
04e7bed5e7 | ||
|
77c521fefa | ||
|
bc64238635 | ||
|
3f59008566 | ||
|
b059c7c0dd | ||
|
68fb1afe5d | ||
|
b2ae269ede | ||
|
5db3ac4fac | ||
|
124ef031d4 | ||
|
cdcf0512d9 | ||
|
6b7dd5b325 | ||
|
8d9d091576 |
54
.github/workflows/main.yml
vendored
54
.github/workflows/main.yml
vendored
@ -66,16 +66,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Build
|
||||
- name: Validate the Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
- name: Build
|
||||
run: |
|
||||
# Build
|
||||
# Note: since this is crossbuild we use the buildForPlatforms filter to tell
|
||||
# the buildscript wich platforms it should build for.
|
||||
gradle -PuseCommitHashAsVersionName=true --no-daemon -PbuildForPlatforms=LinuxArm,LinuxArmHF,LinuxArm64 -PbuildNativeProjects=true \
|
||||
./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildForPlatforms=LinuxArm,LinuxArmHF,LinuxArm64 -PbuildNativeProjects=true \
|
||||
:jme3-bullet-native:assemble
|
||||
|
||||
- name: Upload natives
|
||||
@ -93,13 +94,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
gradle -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \
|
||||
- name: Validate the Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
- name: Build
|
||||
run: |
|
||||
./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \
|
||||
:jme3-android-native:assemble \
|
||||
:jme3-bullet-native-android:assemble
|
||||
|
||||
@ -129,7 +131,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@ -137,8 +139,9 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
architecture: x64
|
||||
|
||||
architecture: x64
|
||||
- name: Validate the Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
- name: Build Natives
|
||||
shell: bash
|
||||
env:
|
||||
@ -159,7 +162,7 @@ jobs:
|
||||
fi
|
||||
|
||||
# Build
|
||||
gradle -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true -Dmaven.repo.local="$PWD/dist/maven" \
|
||||
./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true -Dmaven.repo.local="$PWD/dist/maven" \
|
||||
build \
|
||||
:jme3-bullet-native:build
|
||||
|
||||
@ -194,7 +197,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@ -202,7 +205,7 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: ${{ matrix.jdk }}
|
||||
architecture: x64
|
||||
architecture: x64
|
||||
|
||||
- name: Download natives for linux
|
||||
uses: actions/download-artifact@master
|
||||
@ -215,7 +218,7 @@ jobs:
|
||||
with:
|
||||
name: windows-natives
|
||||
path: build/native
|
||||
|
||||
|
||||
- name: Download natives for mac
|
||||
uses: actions/download-artifact@master
|
||||
with:
|
||||
@ -233,12 +236,13 @@ jobs:
|
||||
with:
|
||||
name: linuxarm-natives
|
||||
path: build/native
|
||||
|
||||
- name: Validate the Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
- name: Build Engine
|
||||
shell: bash
|
||||
run: |
|
||||
# Build
|
||||
gradle -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true build
|
||||
./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true build
|
||||
|
||||
if [ "${{ matrix.deploy }}" = "true" ];
|
||||
then
|
||||
@ -247,7 +251,7 @@ jobs:
|
||||
sudo apt-get install -y zip
|
||||
|
||||
# Create the zip release and the javadoc
|
||||
gradle -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true mergedJavadoc createZipDistribution
|
||||
./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true mergedJavadoc createZipDistribution
|
||||
|
||||
# We prepare the release for deploy
|
||||
mkdir -p ./dist/release/
|
||||
@ -255,7 +259,7 @@ jobs:
|
||||
|
||||
# Create the maven artifacts
|
||||
mkdir -p ./dist/maven/
|
||||
gradle -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true install -Dmaven.repo.local="$PWD/dist/maven"
|
||||
./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true install -Dmaven.repo.local="$PWD/dist/maven"
|
||||
|
||||
# Zip the natives into a single archive (we are going to use this to deploy native snapshots)
|
||||
echo "Create native zip"
|
||||
@ -346,6 +350,8 @@ jobs:
|
||||
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-android-native/)"; fi
|
||||
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet-native-android/)"; fi
|
||||
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet/)"; fi
|
||||
# The bulletUrl (in gradle.properties) might have changed.
|
||||
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- gradle.properties)"; fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -402,7 +408,7 @@ jobs:
|
||||
|
||||
# We need to clone everything again for uploadToMaven.sh ...
|
||||
- name: Clone the repo
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@ -539,10 +545,10 @@ jobs:
|
||||
git config --global user.name "Github Actions"
|
||||
git config --global user.email "actions@users.noreply.github.com"
|
||||
|
||||
git add .
|
||||
git commit -m "$version"
|
||||
git add . || true
|
||||
git commit -m "$version" || true
|
||||
|
||||
branch="gh-pages"
|
||||
git push origin "$branch" --force
|
||||
git push origin "$branch" --force || true
|
||||
|
||||
fi
|
||||
|
29
LICENSE
Normal file
29
LICENSE
Normal file
@ -0,0 +1,29 @@
|
||||
Copyright (c) 2009-2020 jMonkeyEngine
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,4 +1,4 @@
|
||||
jMonkeyEngine
|
||||
jMonkeyEngine
|
||||
=============
|
||||
|
||||
[](https://github.com/jMonkeyEngine/jmonkeyengine/actions)
|
||||
@ -20,6 +20,9 @@ The engine is used by several commercial game studios and computer-science cours
|
||||
- [Lightspeed Frontier (on Steam)](https://store.steampowered.com/app/548650/Lightspeed_Frontier/)
|
||||
- [Skullstone](http://www.skullstonegame.com/)
|
||||
- [Spoxel (on Steam)](https://store.steampowered.com/app/746880/Spoxel/)
|
||||
- [Nine Circles of Hell (on Steam)](https://store.steampowered.com/app/1200600/Nine_Circles_of_Hell/)
|
||||
- [Leap](https://gamejolt.com/games/leap/313308)
|
||||
- [Jumping Jack Flag](http://timealias.bplaced.net/jack/)
|
||||
|
||||
## Getting started
|
||||
|
||||
@ -34,8 +37,8 @@ Note: The master branch on GitHub is a development version of the engine and is
|
||||
- NetBeans Platform
|
||||
- Gradle
|
||||
|
||||
Plus a bunch of awesome libraries & tight integrations like Bullet, Blender, NiftyGUI and other goodies.
|
||||
|
||||
Plus a bunch of awesome libraries & tight integrations like Bullet, NiftyGUI and other goodies.
|
||||
|
||||
### Documentation
|
||||
|
||||
Did you miss it? Don't sweat it, [here it is again](https://jmonkeyengine.github.io/wiki).
|
||||
|
50
build.gradle
50
build.gradle
@ -9,6 +9,7 @@ buildscript {
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
|
||||
classpath 'me.tatarka:gradle-retrolambda:3.7.1'
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +23,8 @@ allprojects {
|
||||
apply plugin: 'base'
|
||||
apply from: file('version.gradle')
|
||||
|
||||
apply plugin: 'me.tatarka.retrolambda'
|
||||
|
||||
// This is applied to all sub projects
|
||||
subprojects {
|
||||
if(!project.name.equals('jme3-android-examples')) {
|
||||
@ -158,29 +161,29 @@ task configureAndroidNDK {
|
||||
|
||||
gradle.rootProject.ext.set("usePrebuildNatives", buildNativeProjects!="true");
|
||||
|
||||
if(skipPrebuildLibraries!="true"&&buildNativeProjects!="true"){
|
||||
if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") {
|
||||
String rootPath = rootProject.projectDir.absolutePath
|
||||
|
||||
Properties nativesSnasphotProp = new Properties()
|
||||
File nativesSnasphotPropF=new File("${rootPath}/natives-snapshot.properties");
|
||||
|
||||
if(nativesSnasphotPropF.exists()){
|
||||
File nativesSnasphotPropF = new File("${rootPath}/natives-snapshot.properties");
|
||||
|
||||
if (nativesSnasphotPropF.exists()) {
|
||||
|
||||
nativesSnasphotPropF.withInputStream { nativesSnasphotProp.load(it) }
|
||||
|
||||
String nativesSnasphot=nativesSnasphotProp.getProperty("natives.snapshot");
|
||||
String nativesUrl=PREBUILD_NATIVES_URL.replace('${natives.snapshot}',nativesSnasphot)
|
||||
println "Use natives snapshot: "+nativesUrl
|
||||
String nativesSnasphot = nativesSnasphotProp.getProperty("natives.snapshot");
|
||||
String nativesUrl = PREBUILD_NATIVES_URL.replace('${natives.snapshot}', nativesSnasphot)
|
||||
println "Use natives snapshot: " + nativesUrl
|
||||
|
||||
String nativesZipFile="${rootPath}" + File.separator + "build"+ File.separator +nativesSnasphot+"-natives.zip"
|
||||
String nativesPath="${rootPath}" + File.separator + "build"+ File.separator +"native"
|
||||
String nativesZipFile = "${rootPath}" + File.separator + "build" + File.separator + nativesSnasphot + "-natives.zip"
|
||||
String nativesPath = "${rootPath}" + File.separator + "build" + File.separator + "native"
|
||||
|
||||
|
||||
task getNativesZipFile {
|
||||
outputs.file nativesZipFile
|
||||
doFirst {
|
||||
File target = file(nativesZipFile);
|
||||
println("Download natives from "+nativesUrl+" to "+nativesZipFile);
|
||||
println("Download natives from " + nativesUrl + " to " + nativesZipFile);
|
||||
target.getParentFile().mkdirs();
|
||||
ant.get(src: nativesUrl, dest: target);
|
||||
}
|
||||
@ -192,28 +195,26 @@ if(skipPrebuildLibraries!="true"&&buildNativeProjects!="true"){
|
||||
dependsOn getNativesZipFile
|
||||
|
||||
doFirst {
|
||||
for(File src : zipTree(nativesZipFile)){
|
||||
String srcRel=src.getAbsolutePath().substring((int)(nativesZipFile.length()+1));
|
||||
srcRel=srcRel.substring(srcRel.indexOf( File.separator)+1);
|
||||
for (File src : zipTree(nativesZipFile)) {
|
||||
String srcRel = src.getAbsolutePath().substring((int) (nativesZipFile.length() + 1));
|
||||
srcRel = srcRel.substring(srcRel.indexOf(File.separator) + 1);
|
||||
|
||||
File dest=new File(nativesPath+File.separator+srcRel);
|
||||
File dest = new File(nativesPath + File.separator + srcRel);
|
||||
boolean doCopy = !(dest.exists() && dest.lastModified() > src.lastModified())
|
||||
if (doCopy) {
|
||||
println("Copy "+src+" "+dest);
|
||||
println("Copy " + src + " " + dest);
|
||||
dest.getParentFile().mkdirs();
|
||||
Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
build.dependsOn extractPrebuiltNatives
|
||||
|
||||
assemble.dependsOn extractPrebuiltNatives
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//class IncrementalReverseTask extends DefaultTask {
|
||||
// @InputDirectory
|
||||
// def File inputDir
|
||||
@ -249,3 +250,14 @@ if(skipPrebuildLibraries!="true"&&buildNativeProjects!="true"){
|
||||
// enableAssertions = true // true by default
|
||||
// }
|
||||
//}
|
||||
|
||||
wrapper {
|
||||
gradleVersion = '5.6.4'
|
||||
}
|
||||
|
||||
|
||||
retrolambda {
|
||||
javaVersion JavaVersion.VERSION_1_7
|
||||
incremental true
|
||||
jvmArgs '-noverify'
|
||||
}
|
@ -12,6 +12,12 @@ version = jmeFullVersion
|
||||
sourceCompatibility = '1.8'
|
||||
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile) { // compile-time options:
|
||||
options.compilerArgs << '-Xlint:unchecked'
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
|
@ -1,13 +1,17 @@
|
||||
# Version number: Major.Minor.SubMinor (e.g. 3.3.0)
|
||||
jmeVersion = 3.3.0
|
||||
jmeVersion = 3.4.0
|
||||
|
||||
# Leave empty to autogenerate
|
||||
# Leave empty to autogenerate
|
||||
# (use -PjmeVersionName="myVersion" from commandline to specify a custom version name )
|
||||
jmeVersionName =
|
||||
jmeVersionName =
|
||||
|
||||
# If true, the version name will contain the commit hash
|
||||
useCommitHashAsVersionName = false
|
||||
|
||||
# Set to true if a non-master branch name should be included in the automatically
|
||||
# generated version.
|
||||
includeBranchInVersion = false
|
||||
|
||||
# specify if JavaDoc should be built
|
||||
buildJavaDoc = true
|
||||
|
||||
@ -23,9 +27,9 @@ skipPrebuildLibraries=false
|
||||
ndkPath = /opt/android-ndk-r16b
|
||||
|
||||
# Path for downloading native Bullet
|
||||
# 2.88+
|
||||
bulletUrl = https://github.com/bulletphysics/bullet3/archive/28039903b14c2aec28beff5b3609c9308b17e76a.zip
|
||||
bulletFolder = bullet3-28039903b14c2aec28beff5b3609c9308b17e76a
|
||||
# 2.89+ (circa 26 April 2020, to avoid jMonkeyEngine issue #1283)
|
||||
bulletUrl = https://github.com/bulletphysics/bullet3/archive/cd8cf7521cbb8b7808126a6adebd47bb83ea166a.zip
|
||||
bulletFolder = bullet3-cd8cf7521cbb8b7808126a6adebd47bb83ea166a
|
||||
bulletZipFile = bullet3.zip
|
||||
|
||||
# POM settings
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
10
gradle/wrapper/gradle-wrapper.properties
vendored
10
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
22
gradlew
vendored
22
gradlew
vendored
@ -1,5 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@ -109,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@ -1,3 +1,19 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
@ -286,7 +286,7 @@ public class MainActivity extends AppCompatActivity implements OnItemClickListen
|
||||
private boolean checkClassType(String className) {
|
||||
boolean include = true;
|
||||
try {
|
||||
Class<?> clazz = (Class<?>) Class.forName(className);
|
||||
Class<?> clazz = Class.forName(className);
|
||||
if (Application.class.isAssignableFrom(clazz)) {
|
||||
Log.d(TAG, "Class " + className + " is a jME Application");
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
String tremorZipFile = "TremorAndroid.zip"
|
||||
String stbiUrl = 'https://raw.githubusercontent.com/nothings/stb/master/stb_image.h'
|
||||
String stbiUrl = 'https://raw.githubusercontent.com/jMonkeyEngine/stb/0224a44a10564a214595797b4c88323f79a5f934/stb_image.h'
|
||||
|
||||
// Working directories for the ndk build.
|
||||
String decodeBuildDir = "${buildDir}" + File.separator + 'decode'
|
||||
|
@ -1,11 +1,11 @@
|
||||
// OpenAL Soft r1.16
|
||||
String openALSoftUrl = 'http://repo.or.cz/w/openal-soft.git/snapshot/e5016f814a265ed592a88acea95cf912c4bfdf12.zip'
|
||||
String openALSoftUrl = 'https://github.com/jMonkeyEngine/openal-soft/archive/e5016f814a265ed592a88acea95cf912c4bfdf12.zip'
|
||||
String openALSoftZipFile = 'OpenALSoft.zip'
|
||||
|
||||
// OpenAL Soft directory the download is extracted into
|
||||
// Typically, the downloaded OpenAL Soft zip file will extract to a directory
|
||||
// called "openal-soft"
|
||||
String openALSoftFolder = 'openal-soft-e5016f8'
|
||||
String openALSoftFolder = 'openal-soft-e5016f814a265ed592a88acea95cf912c4bfdf12'
|
||||
|
||||
//Working directories for the ndk build.
|
||||
String openalsoftBuildDir = "${buildDir}" + File.separator + 'openalsoft'
|
||||
|
@ -239,9 +239,8 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
|
||||
// Create application instance
|
||||
try {
|
||||
if (app == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends LegacyApplication> clazz = (Class<? extends LegacyApplication>) Class.forName(appClass);
|
||||
app = clazz.newInstance();
|
||||
Class clazz = Class.forName(appClass);
|
||||
app = (LegacyApplication)clazz.newInstance();
|
||||
}
|
||||
|
||||
app.setSettings(settings);
|
||||
@ -362,6 +361,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
|
||||
* @param dialog
|
||||
* @param whichButton
|
||||
*/
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
if (whichButton != -2) {
|
||||
if (app != null) {
|
||||
@ -473,6 +473,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
|
||||
handler.setLevel(Level.ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
app.initialize();
|
||||
if (handleExitHook) {
|
||||
@ -488,10 +489,12 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reshape(int width, int height) {
|
||||
app.reshape(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
app.update();
|
||||
// call to remove the splash screen, if present.
|
||||
@ -503,10 +506,12 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestClose(boolean esc) {
|
||||
app.requestClose(esc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (app != null) {
|
||||
app.destroy();
|
||||
@ -516,6 +521,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gainFocus() {
|
||||
logger.fine("gainFocus");
|
||||
if (view != null) {
|
||||
@ -547,6 +553,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loseFocus() {
|
||||
logger.fine("loseFocus");
|
||||
if (app != null) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -257,9 +257,8 @@ public class AndroidHarnessFragment extends Fragment implements
|
||||
// Create application instance
|
||||
try {
|
||||
if (app == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends LegacyApplication> clazz = (Class<? extends LegacyApplication>) Class.forName(appClass);
|
||||
app = clazz.newInstance();
|
||||
Class clazz = Class.forName(appClass);
|
||||
app = (LegacyApplication)clazz.newInstance();
|
||||
}
|
||||
|
||||
app.setSettings(settings);
|
||||
@ -684,10 +683,10 @@ public class AndroidHarnessFragment extends Fragment implements
|
||||
if (viewWidth > viewHeight && viewWidth > maxResolutionDimension) {
|
||||
// landscape
|
||||
fixedSizeWidth = maxResolutionDimension;
|
||||
fixedSizeHeight = (int)(maxResolutionDimension * ((float)viewHeight / (float)viewWidth));
|
||||
fixedSizeHeight = (int)(maxResolutionDimension * (viewHeight / (float)viewWidth));
|
||||
} else if (viewHeight > viewWidth && viewHeight > maxResolutionDimension) {
|
||||
// portrait
|
||||
fixedSizeWidth = (int)(maxResolutionDimension * ((float)viewWidth / (float)viewHeight));
|
||||
fixedSizeWidth = (int)(maxResolutionDimension * (viewWidth / (float)viewHeight));
|
||||
fixedSizeHeight = maxResolutionDimension;
|
||||
} else if (viewWidth == viewHeight && viewWidth > maxResolutionDimension) {
|
||||
fixedSizeWidth = maxResolutionDimension;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014 jMonkeyEngine
|
||||
* Copyright (c) 2014-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -82,6 +82,7 @@ import com.jme3.renderer.queue.RenderQueue;
|
||||
public class DefaultAndroidProfiler implements AppProfiler {
|
||||
private int androidApiLevel = Build.VERSION.SDK_INT;
|
||||
|
||||
@Override
|
||||
public void appStep(AppStep appStep) {
|
||||
if (androidApiLevel >= 18) {
|
||||
switch(appStep) {
|
||||
@ -140,6 +141,7 @@ public class DefaultAndroidProfiler implements AppProfiler {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vpStep(VpStep vpStep, ViewPort vp, RenderQueue.Bucket bucket) {
|
||||
if (androidApiLevel >= 18) {
|
||||
switch (vpStep) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -478,7 +478,7 @@ public class MjpegFileWriter {
|
||||
baos.write(fcc);
|
||||
baos.write(intBytes(swapInt(cb)));
|
||||
for (int i = 0; i < ind.size(); i++) {
|
||||
AVIIndex in = (AVIIndex) ind.get(i);
|
||||
AVIIndex in = ind.get(i);
|
||||
baos.write(in.toBytes());
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -73,6 +73,7 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
private Application app;
|
||||
private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() {
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread th = new Thread(r);
|
||||
th.setName("jME3 Video Processor");
|
||||
@ -239,6 +240,7 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
renderer.readFrameBufferWithFormat(out, item.buffer, Image.Format.BGRA8);
|
||||
executor.submit(new Callable<Void>() {
|
||||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
if (fastMode) {
|
||||
item.data = item.buffer.array();
|
||||
@ -260,6 +262,7 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(RenderManager rm, ViewPort viewPort) {
|
||||
logger.log(Level.INFO, "initialize in VideoProcessor");
|
||||
this.camera = viewPort.getCamera();
|
||||
@ -275,13 +278,16 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reshape(ViewPort vp, int w, int h) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return this.isInitilized;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preFrame(float tpf) {
|
||||
if (null == writer) {
|
||||
try {
|
||||
@ -292,14 +298,17 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postQueue(RenderQueue rq) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postFrame(FrameBuffer out) {
|
||||
numFrames++;
|
||||
addImage(renderManager.getRenderer(), out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
logger.log(Level.INFO, "cleanup in VideoProcessor");
|
||||
logger.log(Level.INFO, "VideoProcessor numFrames: {0}", numFrames);
|
||||
@ -332,22 +341,27 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
this.ticks = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTime() {
|
||||
return (long) (this.ticks * (1.0f / this.framerate) * 1000f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getResolution() {
|
||||
return 1000L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFrameRate() {
|
||||
return this.framerate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTimePerFrame() {
|
||||
return (float) (1.0f / this.framerate);
|
||||
return 1.0f / this.framerate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
long time = System.currentTimeMillis();
|
||||
long difference = time - lastTime;
|
||||
@ -364,6 +378,7 @@ public class VideoRecorderAppState extends AbstractAppState {
|
||||
this.ticks++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.ticks = 0;
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ public class AndroidLocator implements AssetLocator {
|
||||
public AndroidLocator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRootPath(String rootPath) {
|
||||
this.rootPath = rootPath;
|
||||
}
|
||||
|
@ -10,44 +10,64 @@ public final class AndroidAL implements AL {
|
||||
public AndroidAL() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public native String alGetString(int parameter);
|
||||
|
||||
@Override
|
||||
public native int alGenSources();
|
||||
|
||||
@Override
|
||||
public native int alGetError();
|
||||
|
||||
@Override
|
||||
public native void alDeleteSources(int numSources, IntBuffer sources);
|
||||
|
||||
@Override
|
||||
public native void alGenBuffers(int numBuffers, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alDeleteBuffers(int numBuffers, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alSourceStop(int source);
|
||||
|
||||
@Override
|
||||
public native void alSourcei(int source, int param, int value);
|
||||
|
||||
@Override
|
||||
public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency);
|
||||
|
||||
@Override
|
||||
public native void alSourcePlay(int source);
|
||||
|
||||
@Override
|
||||
public native void alSourcePause(int source);
|
||||
|
||||
@Override
|
||||
public native void alSourcef(int source, int param, float value);
|
||||
|
||||
@Override
|
||||
public native void alSource3f(int source, int param, float value1, float value2, float value3);
|
||||
|
||||
@Override
|
||||
public native int alGetSourcei(int source, int param);
|
||||
|
||||
@Override
|
||||
public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alListener(int param, FloatBuffer data);
|
||||
|
||||
@Override
|
||||
public native void alListenerf(int param, float value);
|
||||
|
||||
@Override
|
||||
public native void alListener3f(int param, float value1, float value2, float value3);
|
||||
|
||||
@Override
|
||||
public native void alSource3i(int source, int param, int value1, int value2, int value3);
|
||||
|
||||
}
|
||||
|
@ -12,19 +12,27 @@ public final class AndroidALC implements ALC {
|
||||
public AndroidALC() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public native void createALC();
|
||||
|
||||
@Override
|
||||
public native void destroyALC();
|
||||
|
||||
@Override
|
||||
public native boolean isCreated();
|
||||
|
||||
@Override
|
||||
public native String alcGetString(int parameter);
|
||||
|
||||
@Override
|
||||
public native boolean alcIsExtensionPresent(String extension);
|
||||
|
||||
@Override
|
||||
public native void alcGetInteger(int param, IntBuffer buffer, int size);
|
||||
|
||||
@Override
|
||||
public native void alcDevicePauseSOFT();
|
||||
|
||||
@Override
|
||||
public native void alcDeviceResumeSOFT();
|
||||
}
|
||||
|
@ -8,25 +8,36 @@ public class AndroidEFX implements EFX {
|
||||
public AndroidEFX() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alGenEffects(int numEffects, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alEffecti(int effect, int param, int value);
|
||||
|
||||
@Override
|
||||
public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value);
|
||||
|
||||
@Override
|
||||
public native void alDeleteEffects(int numEffects, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alGenFilters(int numFilters, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alFilteri(int filter, int param, int value);
|
||||
|
||||
@Override
|
||||
public native void alFilterf(int filter, int param, float value);
|
||||
|
||||
@Override
|
||||
public native void alDeleteFilters(int numFilters, IntBuffer buffers);
|
||||
|
||||
@Override
|
||||
public native void alEffectf(int effect, int param, float value);
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ public class NativeVorbisLoader implements AssetLoader {
|
||||
throw new IOException("Not supported for audio streams");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTime(float time) {
|
||||
try {
|
||||
file.seekTime(time);
|
||||
|
@ -113,7 +113,7 @@ public class AndroidJoystickJoyInput14 {
|
||||
joysticks.clear();
|
||||
joystickIndex.clear();
|
||||
|
||||
ArrayList gameControllerDeviceIds = new ArrayList();
|
||||
ArrayList<Integer> gameControllerDeviceIds = new ArrayList<>();
|
||||
int[] deviceIds = InputDevice.getDeviceIds();
|
||||
for (int deviceId : deviceIds) {
|
||||
InputDevice dev = InputDevice.getDevice(deviceId);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2018 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -561,7 +561,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (sensorData != null) {
|
||||
} else {
|
||||
if (!sensorData.haveData) {
|
||||
sensorData.haveData = true;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -131,8 +131,8 @@ public class AndroidTouchInput implements TouchInput {
|
||||
|
||||
// view width and height are 0 until the view is displayed on the screen
|
||||
if (androidInput.getView().getWidth() != 0 && androidInput.getView().getHeight() != 0) {
|
||||
scaleX = (float)settings.getWidth() / (float)androidInput.getView().getWidth();
|
||||
scaleY = (float)settings.getHeight() / (float)androidInput.getView().getHeight();
|
||||
scaleX = settings.getWidth() / (float)androidInput.getView().getWidth();
|
||||
scaleY = settings.getHeight() / (float)androidInput.getView().getHeight();
|
||||
}
|
||||
logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}",
|
||||
new Object[]{scaleX, scaleY});
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -87,7 +87,7 @@ public class TouchEventPool {
|
||||
TouchEvent evt = null;
|
||||
int curSize = eventPool.size();
|
||||
while (curSize > 0) {
|
||||
evt = (TouchEvent)eventPool.pop();
|
||||
evt = eventPool.pop();
|
||||
if (evt.isConsumed()) {
|
||||
break;
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2015 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -46,6 +46,7 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
|
||||
IntBuffer tmpBuff = BufferUtils.createIntBuffer(1);
|
||||
|
||||
@Override
|
||||
public void resetStats() {
|
||||
}
|
||||
|
||||
@ -86,10 +87,12 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLES20.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glAttachShader(int program, int shader) {
|
||||
GLES20.glAttachShader(program, shader);
|
||||
}
|
||||
@ -99,144 +102,179 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
GLES30.glBeginQuery(target, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindBuffer(int target, int buffer) {
|
||||
GLES20.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindTexture(int target, int texture) {
|
||||
GLES20.glBindTexture(target, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBlendFunc(int sfactor, int dfactor) {
|
||||
GLES20.glBlendFunc(sfactor, dfactor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha) {
|
||||
GLES20.glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferData(int target, FloatBuffer data, int usage) {
|
||||
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferData(int target, ShortBuffer data, int usage) {
|
||||
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferData(int target, ByteBuffer data, int usage) {
|
||||
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferData(int target, long data_size, int usage) {
|
||||
GLES20.glBufferData(target, (int) data_size, null, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferSubData(int target, long offset, FloatBuffer data) {
|
||||
GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferSubData(int target, long offset, ShortBuffer data) {
|
||||
GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferSubData(int target, long offset, ByteBuffer data) {
|
||||
GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGetBufferSubData(int target, long offset, ByteBuffer data) {
|
||||
throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClear(int mask) {
|
||||
GLES20.glClear(mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLES20.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
GLES20.glColorMask(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompileShader(int shader) {
|
||||
GLES20.glCompileShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, ByteBuffer data) {
|
||||
GLES20.glCompressedTexImage2D(target, level, internalformat, width, height, 0, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, ByteBuffer data) {
|
||||
GLES20.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCreateProgram() {
|
||||
return GLES20.glCreateProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCreateShader(int shaderType) {
|
||||
return GLES20.glCreateShader(shaderType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCullFace(int mode) {
|
||||
GLES20.glCullFace(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(IntBuffer buffers) {
|
||||
checkLimit(buffers);
|
||||
GLES20.glDeleteBuffers(buffers.limit(), buffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteProgram(int program) {
|
||||
GLES20.glDeleteProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteShader(int shader) {
|
||||
GLES20.glDeleteShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(IntBuffer textures) {
|
||||
checkLimit(textures);
|
||||
GLES20.glDeleteTextures(textures.limit(), textures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthFunc(int func) {
|
||||
GLES20.glDepthFunc(func);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthMask(boolean flag) {
|
||||
GLES20.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthRange(double nearVal, double farVal) {
|
||||
GLES20.glDepthRangef((float)nearVal, (float)farVal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDetachShader(int program, int shader) {
|
||||
GLES20.glDetachShader(program, shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDisable(int cap) {
|
||||
GLES20.glDisable(cap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDisableVertexAttribArray(int index) {
|
||||
GLES20.glDisableVertexAttribArray(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDrawArrays(int mode, int first, int count) {
|
||||
GLES20.glDrawArrays(mode, first, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices) {
|
||||
GLES20.glDrawElements(mode, count, type, (int)indices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glEnable(int cap) {
|
||||
GLES20.glEnable(cap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glEnableVertexAttribArray(int index) {
|
||||
GLES20.glEnableVertexAttribArray(index);
|
||||
}
|
||||
@ -246,11 +284,13 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
GLES30.glEndQuery(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(IntBuffer buffers) {
|
||||
checkLimit(buffers);
|
||||
GLES20.glGenBuffers(buffers.limit(), buffers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenTextures(IntBuffer textures) {
|
||||
checkLimit(textures);
|
||||
GLES20.glGenTextures(textures.limit(), textures);
|
||||
@ -261,29 +301,35 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
GLES30.glGenQueries(num, buff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetAttribLocation(int program, String name) {
|
||||
return GLES20.glGetAttribLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGetBoolean(int pname, ByteBuffer params) {
|
||||
// GLES20.glGetBoolean(pname, params);
|
||||
throw new UnsupportedOperationException("Today is not a good day for this");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetError() {
|
||||
return GLES20.glGetError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGetInteger(int pname, IntBuffer params) {
|
||||
checkLimit(params);
|
||||
GLES20.glGetIntegerv(pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGetProgram(int program, int pname, IntBuffer params) {
|
||||
checkLimit(params);
|
||||
GLES20.glGetProgramiv(program, pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetProgramInfoLog(int program, int maxLength) {
|
||||
return GLES20.glGetProgramInfoLog(program);
|
||||
}
|
||||
@ -303,51 +349,63 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
return buff.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGetShader(int shader, int pname, IntBuffer params) {
|
||||
checkLimit(params);
|
||||
GLES20.glGetShaderiv(shader, pname, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetShaderInfoLog(int shader, int maxLength) {
|
||||
return GLES20.glGetShaderInfoLog(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetString(int name) {
|
||||
return GLES20.glGetString(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetUniformLocation(int program, String name) {
|
||||
return GLES20.glGetUniformLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean glIsEnabled(int cap) {
|
||||
return GLES20.glIsEnabled(cap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glLineWidth(float width) {
|
||||
GLES20.glLineWidth(width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glLinkProgram(int program) {
|
||||
GLES20.glLinkProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPixelStorei(int pname, int param) {
|
||||
GLES20.glPixelStorei(pname, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPolygonOffset(float factor, float units) {
|
||||
GLES20.glPolygonOffset(factor, units);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data) {
|
||||
GLES20.glReadPixels(x, y, width, height, format, type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glScissor(int x, int y, int width, int height) {
|
||||
GLES20.glScissor(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glShaderSource(int shader, String[] string, IntBuffer length) {
|
||||
if (string.length != 1) {
|
||||
throw new UnsupportedOperationException("Today is not a good day");
|
||||
@ -355,186 +413,231 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
GLES20.glShaderSource(shader, string[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glStencilFuncSeparate(int face, int func, int ref, int mask) {
|
||||
GLES20.glStencilFuncSeparate(face, func, ref, mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
|
||||
GLES20.glStencilOpSeparate(face, sfail, dpfail, dppass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) {
|
||||
GLES20.glTexImage2D(target, level, internalFormat, width, height, 0, format, type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glTexParameterf(int target, int pname, float param) {
|
||||
GLES20.glTexParameterf(target, pname, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glTexParameteri(int target, int pname, int param) {
|
||||
GLES20.glTexParameteri(target, pname, param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) {
|
||||
GLES20.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1(int location, FloatBuffer value) {
|
||||
GLES20.glUniform1fv(location, getLimitCount(value, 1), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1(int location, IntBuffer value) {
|
||||
GLES20.glUniform1iv(location, getLimitCount(value, 1), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1f(int location, float v0) {
|
||||
GLES20.glUniform1f(location, v0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int v0) {
|
||||
GLES20.glUniform1i(location, v0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform2(int location, IntBuffer value) {
|
||||
GLES20.glUniform2iv(location, getLimitCount(value, 2), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform2(int location, FloatBuffer value) {
|
||||
GLES20.glUniform2fv(location, getLimitCount(value, 2), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform2f(int location, float v0, float v1) {
|
||||
GLES20.glUniform2f(location, v0, v1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform3(int location, IntBuffer value) {
|
||||
GLES20.glUniform3iv(location, getLimitCount(value, 3), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform3(int location, FloatBuffer value) {
|
||||
GLES20.glUniform3fv(location, getLimitCount(value, 3), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform3f(int location, float v0, float v1, float v2) {
|
||||
GLES20.glUniform3f(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform4(int location, FloatBuffer value) {
|
||||
GLES20.glUniform4fv(location, getLimitCount(value, 4), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform4(int location, IntBuffer value) {
|
||||
GLES20.glUniform4iv(location, getLimitCount(value, 4), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform4f(int location, float v0, float v1, float v2, float v3) {
|
||||
GLES20.glUniform4f(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
|
||||
GLES20.glUniformMatrix3fv(location, getLimitCount(value, 3 * 3), transpose, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
|
||||
GLES20.glUniformMatrix4fv(location, getLimitCount(value, 4 * 4), transpose, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUseProgram(int program) {
|
||||
GLES20.glUseProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
|
||||
GLES20.glVertexAttribPointer(index, size, type, normalized, stride, (int)pointer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLES20.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
|
||||
GLES30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferData(int target, IntBuffer data, int usage) {
|
||||
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBufferSubData(int target, long offset, IntBuffer data) {
|
||||
GLES20.glBufferSubData(target, (int)offset, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) {
|
||||
GLES30.glDrawArraysInstanced(mode, first, count, primcount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDrawBuffers(IntBuffer bufs) {
|
||||
GLES30.glDrawBuffers(bufs.limit(), bufs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) {
|
||||
GLES30.glDrawElementsInstanced(mode, indices_count, type, (int)indices_buffer_offset, primcount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGetMultisample(int pname, int index, FloatBuffer val) {
|
||||
GLES31.glGetMultisamplefv(pname, index, val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
|
||||
GLES30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) {
|
||||
GLES31.glTexStorage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glVertexAttribDivisorARB(int index, int divisor) {
|
||||
GLES30.glVertexAttribDivisor(index, divisor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindFramebufferEXT(int param1, int param2) {
|
||||
GLES20.glBindFramebuffer(param1, param2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindRenderbufferEXT(int param1, int param2) {
|
||||
GLES20.glBindRenderbuffer(param1, param2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCheckFramebufferStatusEXT(int param1) {
|
||||
return GLES20.glCheckFramebufferStatus(param1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffersEXT(IntBuffer param1) {
|
||||
checkLimit(param1);
|
||||
GLES20.glDeleteFramebuffers(param1.limit(), param1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteRenderbuffersEXT(IntBuffer param1) {
|
||||
checkLimit(param1);
|
||||
GLES20.glDeleteRenderbuffers(param1.limit(), param1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
|
||||
GLES20.glFramebufferRenderbuffer(param1, param2, param3, param4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
|
||||
GLES20.glFramebufferTexture2D(param1, param2, param3, param4, param5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenFramebuffersEXT(IntBuffer param1) {
|
||||
checkLimit(param1);
|
||||
GLES20.glGenFramebuffers(param1.limit(), param1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenRenderbuffersEXT(IntBuffer param1) {
|
||||
checkLimit(param1);
|
||||
GLES20.glGenRenderbuffers(param1.limit(), param1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenerateMipmapEXT(int param1) {
|
||||
GLES20.glGenerateMipmap(param1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
|
||||
GLES20.glRenderbufferStorage(param1, param2, param3, param4);
|
||||
}
|
||||
@ -570,16 +673,20 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
GLES30.glFramebufferTextureLayer(target, attachment, texture, level, layer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glAlphaFunc(int func, float ref) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPointSize(float size) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPolygonMode(int face, int mode) {
|
||||
}
|
||||
|
||||
// Wrapper to DrawBuffers as there's no DrawBuffer method in GLES
|
||||
@Override
|
||||
public void glDrawBuffer(int mode) {
|
||||
tmpBuff.clear();
|
||||
tmpBuff.put(0, mode);
|
||||
@ -587,25 +694,30 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
|
||||
glDrawBuffers(tmpBuff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glReadBuffer(int mode) {
|
||||
GLES30.glReadBuffer(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompressedTexImage3D(int target, int level, int internalFormat, int width, int height, int depth,
|
||||
int border, ByteBuffer data) {
|
||||
GLES30.glCompressedTexImage3D(target, level, internalFormat, width, height, depth, border, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompressedTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width,
|
||||
int height, int depth, int format, ByteBuffer data) {
|
||||
GLES30.glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, getLimitBytes(data), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border,
|
||||
int format, int type, ByteBuffer data) {
|
||||
GLES30.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height,
|
||||
int depth, int format, int type, ByteBuffer data) {
|
||||
GLES30.glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data);
|
||||
|
@ -204,6 +204,7 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
|
||||
public void showSoftKeyboard(final boolean show) {
|
||||
view.getHandler().post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
InputMethodManager manager =
|
||||
(InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2018 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -52,13 +52,7 @@ import com.jme3.input.controls.SoftTextDialogInputListener;
|
||||
import com.jme3.input.dummy.DummyKeyInput;
|
||||
import com.jme3.input.dummy.DummyMouseInput;
|
||||
import com.jme3.renderer.android.AndroidGL;
|
||||
import com.jme3.renderer.opengl.GL;
|
||||
import com.jme3.renderer.opengl.GLES_30;
|
||||
import com.jme3.renderer.opengl.GLDebugES;
|
||||
import com.jme3.renderer.opengl.GLExt;
|
||||
import com.jme3.renderer.opengl.GLFbo;
|
||||
import com.jme3.renderer.opengl.GLRenderer;
|
||||
import com.jme3.renderer.opengl.GLTracer;
|
||||
import com.jme3.renderer.opengl.*;
|
||||
import com.jme3.system.*;
|
||||
import com.jme3.util.AndroidBufferAllocator;
|
||||
import com.jme3.util.BufferAllocatorFactory;
|
||||
@ -202,20 +196,21 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
|
||||
|
||||
// Setup unhandled Exception Handler
|
||||
Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable thrown) {
|
||||
listener.handleError("Exception thrown in " + thread.toString(), thrown);
|
||||
}
|
||||
});
|
||||
|
||||
timer = new NanoTimer();
|
||||
Object gl = new AndroidGL();
|
||||
GL gl = new AndroidGL();
|
||||
if (settings.getBoolean("GraphicsDebug")) {
|
||||
gl = new GLDebugES((GL) gl, (GLExt) gl, (GLFbo) gl);
|
||||
gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GLES_30.class, GLFbo.class, GLExt.class);
|
||||
}
|
||||
if (settings.getBoolean("GraphicsTrace")) {
|
||||
gl = GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class);
|
||||
gl = (GL)GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class);
|
||||
}
|
||||
renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl);
|
||||
renderer = new GLRenderer(gl, (GLExt)gl, (GLFbo)gl);
|
||||
renderer.initialize();
|
||||
|
||||
JmeSystem.setSoftTextDialogInput(this);
|
||||
@ -254,7 +249,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
|
||||
}
|
||||
|
||||
if (settings.getFrameRate() > 0) {
|
||||
minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms
|
||||
minFrameDuration = (long)(1000d / settings.getFrameRate()); // ms
|
||||
logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration);
|
||||
} else {
|
||||
minFrameDuration = 0;
|
||||
@ -414,6 +409,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) {
|
||||
logger.log(Level.FINE, "requestDialog: title: {0}, initialValue: {1}",
|
||||
new Object[]{title, initialValue});
|
||||
@ -457,6 +453,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
|
||||
|
||||
AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext()).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK",
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
/* User clicked OK, send COMPLETE action
|
||||
* and text */
|
||||
@ -464,6 +461,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
|
||||
}
|
||||
}).setNegativeButton("Cancel",
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int whichButton) {
|
||||
/* User clicked CANCEL, send CANCEL action
|
||||
* and text */
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2014 jMonkeyEngine
|
||||
* Copyright (c) 2009-2020 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -67,6 +67,7 @@ public class AndroidBufferImageLoader implements AssetLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object load(AssetInfo assetInfo) throws IOException {
|
||||
Bitmap bitmap = null;
|
||||
Image.Format format;
|
||||
|
@ -25,12 +25,13 @@ public class AndroidNativeImageLoader implements AssetLoader {
|
||||
|
||||
private static native Image load(InputStream in, boolean flipY, byte[] tmpArray) throws IOException;
|
||||
|
||||
@Override
|
||||
public Image load(AssetInfo info) throws IOException {
|
||||
boolean flip = ((TextureKey) info.getKey()).isFlipY();
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = info.openStream();
|
||||
return load(info.openStream(), flip, tmpArray);
|
||||
return load(in, flip, tmpArray);
|
||||
} finally {
|
||||
if (in != null){
|
||||
in.close();
|
||||
|
@ -49,6 +49,7 @@ public class RingBuffer<T> implements Iterable<T> {
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new RingBufferIterator();
|
||||
}
|
||||
@ -58,14 +59,17 @@ public class RingBuffer<T> implements Iterable<T> {
|
||||
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
|
@ -1,12 +0,0 @@
|
||||
if (!hasProperty('mainClass')) {
|
||||
ext.mainClass = ''
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':jme3-core')
|
||||
compile project(':jme3-desktop')
|
||||
compile project(':jme3-effects')
|
||||
compile ('org.ejml:core:0.27')
|
||||
compile ('org.ejml:dense64:0.27')
|
||||
compile ('org.ejml:simple:0.27')
|
||||
}
|
@ -1,733 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2018 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.asset;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState.FaceCullMode;
|
||||
|
||||
/**
|
||||
* Blender key. Contains path of the blender file and its loading properties.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class BlenderKey extends ModelKey {
|
||||
protected static final int DEFAULT_FPS = 25;
|
||||
/**
|
||||
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
|
||||
* between the frames.
|
||||
*/
|
||||
protected int fps = DEFAULT_FPS;
|
||||
/**
|
||||
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
|
||||
*/
|
||||
protected int featuresToLoad = FeaturesToLoad.ALL;
|
||||
/** The variable that tells if content of the file (along with data unlinked to any feature on the scene) should be stored as 'user data' in the result spatial. */
|
||||
protected boolean loadUnlinkedAssets;
|
||||
/** The root path for all the assets. */
|
||||
protected String assetRootPath;
|
||||
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
|
||||
protected boolean fixUpAxis = true;
|
||||
/** Generated textures resolution (PPU - Pixels Per Unit). */
|
||||
protected int generatedTexturePPU = 128;
|
||||
/**
|
||||
* The name of world settings that the importer will use. If not set or specified name does not occur in the file
|
||||
* then the first world settings in the file will be used.
|
||||
*/
|
||||
protected String usedWorld;
|
||||
/**
|
||||
* User's default material that is set for objects that have no material definition in blender. The default value is
|
||||
* null. If the value is null the importer will use its own default material (gray color - like in blender).
|
||||
*/
|
||||
protected Material defaultMaterial;
|
||||
/** Face cull mode. By default it is disabled. */
|
||||
protected FaceCullMode faceCullMode = FaceCullMode.Back;
|
||||
/**
|
||||
* Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
|
||||
* If set to -1 then the current layer will be loaded.
|
||||
*/
|
||||
protected int layersToLoad = -1;
|
||||
/** A variable that toggles the object custom properties loading. */
|
||||
protected boolean loadObjectProperties = true;
|
||||
/**
|
||||
* Maximum texture size. Might be dependant on the graphic card.
|
||||
* This value is taken from <b>org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE</b>.
|
||||
*/
|
||||
protected int maxTextureSize = 8192;
|
||||
/** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */
|
||||
protected boolean loadGeneratedTextures;
|
||||
/** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
|
||||
protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
|
||||
/**
|
||||
* If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
|
||||
* textures will get their proper size.
|
||||
*/
|
||||
protected int skyGeneratedTextureSize = 1000;
|
||||
/** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */
|
||||
protected float skyGeneratedTextureRadius = 1;
|
||||
/** The shape against which the generated texture for the sky will be created. */
|
||||
protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE;
|
||||
/**
|
||||
* This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together
|
||||
* and textures that in the final result will never be visible - will be discarded.
|
||||
*/
|
||||
protected boolean optimiseTextures;
|
||||
/** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */
|
||||
protected AnimationMatchMethod animationMatchMethod = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH;
|
||||
/** The size of points that are loaded and do not belong to any edge of the mesh. */
|
||||
protected float pointsSize = 1;
|
||||
/** The width of edges that are loaded from the mesh and do not belong to any face. */
|
||||
protected float linesWidth = 1;
|
||||
|
||||
/**
|
||||
* Constructor used by serialization mechanisms.
|
||||
*/
|
||||
public BlenderKey() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Creates a key for the given file name.
|
||||
* @param name
|
||||
* the name (path) of a file
|
||||
*/
|
||||
public BlenderKey(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25.
|
||||
* @return the frames per second amount
|
||||
*/
|
||||
public int getFps() {
|
||||
return fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets frames per second amount.
|
||||
* @param fps
|
||||
* the frames per second amount
|
||||
*/
|
||||
public void setFps(int fps) {
|
||||
this.fps = fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the face cull mode.
|
||||
* @return the face cull mode
|
||||
*/
|
||||
public FaceCullMode getFaceCullMode() {
|
||||
return faceCullMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the face cull mode.
|
||||
* @param faceCullMode
|
||||
* the face cull mode
|
||||
*/
|
||||
public void setFaceCullMode(FaceCullMode faceCullMode) {
|
||||
this.faceCullMode = faceCullMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets layers to be loaded.
|
||||
* @param layersToLoad
|
||||
* layers to be loaded
|
||||
*/
|
||||
public void setLayersToLoad(int layersToLoad) {
|
||||
this.layersToLoad = layersToLoad;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns layers to be loaded.
|
||||
* @return layers to be loaded
|
||||
*/
|
||||
public int getLayersToLoad() {
|
||||
return layersToLoad;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the properies loading policy.
|
||||
* By default the value is true.
|
||||
* @param loadObjectProperties
|
||||
* true to load properties and false to suspend their loading
|
||||
*/
|
||||
public void setLoadObjectProperties(boolean loadObjectProperties) {
|
||||
this.loadObjectProperties = loadObjectProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current properties loading properties
|
||||
*/
|
||||
public boolean isLoadObjectProperties() {
|
||||
return loadObjectProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default value for this parameter is the same as defined by: org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE.
|
||||
* If by any means this is too large for user's hardware configuration use the 'setMaxTextureSize' method to change that.
|
||||
* @return maximum texture size (width/height)
|
||||
*/
|
||||
public int getMaxTextureSize() {
|
||||
return maxTextureSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the maximum texture size.
|
||||
* @param maxTextureSize
|
||||
* the maximum texture size
|
||||
*/
|
||||
public void setMaxTextureSize(int maxTextureSize) {
|
||||
this.maxTextureSize = maxTextureSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the flag that toggles the generated textures loading.
|
||||
* @param loadGeneratedTextures
|
||||
* <b>true</b> if generated textures should be loaded and <b>false</b> otherwise
|
||||
*/
|
||||
public void setLoadGeneratedTextures(boolean loadGeneratedTextures) {
|
||||
this.loadGeneratedTextures = loadGeneratedTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return tells if the generated textures should be loaded (<b>false</b> is the default value)
|
||||
*/
|
||||
public boolean isLoadGeneratedTextures() {
|
||||
return loadGeneratedTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used any more.
|
||||
* This method sets the asset root path.
|
||||
* @param assetRootPath
|
||||
* the assets root path
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAssetRootPath(String assetRootPath) {
|
||||
this.assetRootPath = assetRootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used any more.
|
||||
* This method returns the asset root path.
|
||||
* @return the asset root path
|
||||
*/
|
||||
@Deprecated
|
||||
public String getAssetRootPath() {
|
||||
return assetRootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds features to be loaded.
|
||||
* @param featuresToLoad
|
||||
* bitwise flag of FeaturesToLoad interface values
|
||||
*/
|
||||
@Deprecated
|
||||
public void includeInLoading(int featuresToLoad) {
|
||||
this.featuresToLoad |= featuresToLoad;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method removes features from being loaded.
|
||||
* @param featuresNotToLoad
|
||||
* bitwise flag of FeaturesToLoad interface values
|
||||
*/
|
||||
@Deprecated
|
||||
public void excludeFromLoading(int featuresNotToLoad) {
|
||||
featuresToLoad &= ~featuresNotToLoad;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public boolean shouldLoad(int featureToLoad) {
|
||||
return (featuresToLoad & featureToLoad) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by
|
||||
* the blender file loader.
|
||||
* @return features that will be loaded by the blender file loader
|
||||
*/
|
||||
@Deprecated
|
||||
public int getFeaturesToLoad() {
|
||||
return featuresToLoad;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method determines if unlinked assets should be loaded.
|
||||
* If not then only objects on selected layers will be loaded and their assets if required.
|
||||
* If yes then all assets will be loaded even if they are on inactive layers or are not linked
|
||||
* to anything.
|
||||
* @return <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isLoadUnlinkedAssets() {
|
||||
return loadUnlinkedAssets;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets if unlinked assets should be loaded.
|
||||
* If not then only objects on selected layers will be loaded and their assets if required.
|
||||
* If yes then all assets will be loaded even if they are on inactive layers or are not linked
|
||||
* to anything.
|
||||
* @param loadUnlinkedAssets
|
||||
* <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise
|
||||
*/
|
||||
public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) {
|
||||
this.loadUnlinkedAssets = loadUnlinkedAssets;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y
|
||||
* is up axis.
|
||||
* @param fixUpAxis
|
||||
* the up axis state variable
|
||||
*/
|
||||
public void setFixUpAxis(boolean fixUpAxis) {
|
||||
this.fixUpAxis = fixUpAxis;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By
|
||||
* default Y is up axis.
|
||||
* @return the up axis state variable
|
||||
*/
|
||||
public boolean isFixUpAxis() {
|
||||
return fixUpAxis;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the generated textures resolution.
|
||||
* @param generatedTexturePPU
|
||||
* the generated textures resolution
|
||||
*/
|
||||
public void setGeneratedTexturePPU(int generatedTexturePPU) {
|
||||
this.generatedTexturePPU = generatedTexturePPU;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the generated textures resolution
|
||||
*/
|
||||
public int getGeneratedTexturePPU() {
|
||||
return generatedTexturePPU;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mipmaps generation method
|
||||
*/
|
||||
public MipmapGenerationMethod getMipmapGenerationMethod() {
|
||||
return mipmapGenerationMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mipmapGenerationMethod
|
||||
* mipmaps generation method
|
||||
*/
|
||||
public void setMipmapGenerationMethod(MipmapGenerationMethod mipmapGenerationMethod) {
|
||||
this.mipmapGenerationMethod = mipmapGenerationMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of the generated textures for the sky (used if no flat textures are applied)
|
||||
*/
|
||||
public int getSkyGeneratedTextureSize() {
|
||||
return skyGeneratedTextureSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param skyGeneratedTextureSize
|
||||
* the size of the generated textures for the sky (used if no flat textures are applied)
|
||||
*/
|
||||
public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) {
|
||||
if (skyGeneratedTextureSize <= 0) {
|
||||
throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!");
|
||||
}
|
||||
this.skyGeneratedTextureSize = skyGeneratedTextureSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen
|
||||
*/
|
||||
public float getSkyGeneratedTextureRadius() {
|
||||
return skyGeneratedTextureRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param skyGeneratedTextureRadius
|
||||
* the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen
|
||||
*/
|
||||
public void setSkyGeneratedTextureRadius(float skyGeneratedTextureRadius) {
|
||||
this.skyGeneratedTextureRadius = skyGeneratedTextureRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the shape against which the generated texture for the sky will be created (by default it is a sphere).
|
||||
*/
|
||||
public SkyGeneratedTextureShape getSkyGeneratedTextureShape() {
|
||||
return skyGeneratedTextureShape;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param skyGeneratedTextureShape
|
||||
* the shape against which the generated texture for the sky will be created
|
||||
*/
|
||||
public void setSkyGeneratedTextureShape(SkyGeneratedTextureShape skyGeneratedTextureShape) {
|
||||
if (skyGeneratedTextureShape == null) {
|
||||
throw new IllegalArgumentException("The sky generated shape type cannot be null!");
|
||||
}
|
||||
this.skyGeneratedTextureShape = skyGeneratedTextureShape;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to true, then textures of the same mapping type will be merged together
|
||||
* and textures that in the final result will never be visible - will be discarded.
|
||||
* @param optimiseTextures
|
||||
* the variable that tells if the textures should be optimised or not
|
||||
*/
|
||||
public void setOptimiseTextures(boolean optimiseTextures) {
|
||||
this.optimiseTextures = optimiseTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the variable that tells if the textures should be optimised or not (by default the optimisation is disabled)
|
||||
*/
|
||||
public boolean isOptimiseTextures() {
|
||||
return optimiseTextures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the way the animations will be matched with skeletons.
|
||||
*
|
||||
* @param animationMatchMethod
|
||||
* the way the animations will be matched with skeletons
|
||||
*/
|
||||
public void setAnimationMatchMethod(AnimationMatchMethod animationMatchMethod) {
|
||||
this.animationMatchMethod = animationMatchMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the way the animations will be matched with skeletons
|
||||
*/
|
||||
public AnimationMatchMethod getAnimationMatchMethod() {
|
||||
return animationMatchMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of points that are loaded and do not belong to any edge of the mesh
|
||||
*/
|
||||
public float getPointsSize() {
|
||||
return pointsSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of points that are loaded and do not belong to any edge of the mesh.
|
||||
* @param pointsSize
|
||||
* The size of points that are loaded and do not belong to any edge of the mesh
|
||||
*/
|
||||
public void setPointsSize(float pointsSize) {
|
||||
this.pointsSize = pointsSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the width of edges that are loaded from the mesh and do not belong to any face
|
||||
*/
|
||||
public float getLinesWidth() {
|
||||
return linesWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the width of edges that are loaded from the mesh and do not belong to any face.
|
||||
* @param linesWidth
|
||||
* the width of edges that are loaded from the mesh and do not belong to any face
|
||||
*/
|
||||
public void setLinesWidth(float linesWidth) {
|
||||
this.linesWidth = linesWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the name of the WORLD data block that should be used during file loading. By default the name is
|
||||
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
|
||||
* during loading (assuming any exists in the file).
|
||||
* @param usedWorld
|
||||
* the name of the WORLD block used during loading
|
||||
*/
|
||||
public void setUsedWorld(String usedWorld) {
|
||||
this.usedWorld = usedWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the name of the WORLD data block that should be used during file loading.
|
||||
* @return the name of the WORLD block used during loading
|
||||
*/
|
||||
public String getUsedWorld() {
|
||||
return usedWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the default material for objects.
|
||||
* @param defaultMaterial
|
||||
* the default material
|
||||
*/
|
||||
public void setDefaultMaterial(Material defaultMaterial) {
|
||||
this.defaultMaterial = defaultMaterial;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the default material.
|
||||
* @return the default material
|
||||
*/
|
||||
public Material getDefaultMaterial() {
|
||||
return defaultMaterial;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
super.write(e);
|
||||
OutputCapsule oc = e.getCapsule(this);
|
||||
oc.write(fps, "fps", DEFAULT_FPS);
|
||||
oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL);
|
||||
oc.write(loadUnlinkedAssets, "load-unlinked-assets", false);
|
||||
oc.write(assetRootPath, "asset-root-path", null);
|
||||
oc.write(fixUpAxis, "fix-up-axis", true);
|
||||
oc.write(generatedTexturePPU, "generated-texture-ppu", 128);
|
||||
oc.write(usedWorld, "used-world", null);
|
||||
oc.write(defaultMaterial, "default-material", null);
|
||||
oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off);
|
||||
oc.write(layersToLoad, "layers-to-load", -1);
|
||||
oc.write(mipmapGenerationMethod, "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED);
|
||||
oc.write(skyGeneratedTextureSize, "sky-generated-texture-size", 1000);
|
||||
oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
|
||||
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
|
||||
oc.write(optimiseTextures, "optimise-textures", false);
|
||||
oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
|
||||
oc.write(pointsSize, "points-size", 1);
|
||||
oc.write(linesWidth, "lines-width", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter e) throws IOException {
|
||||
super.read(e);
|
||||
InputCapsule ic = e.getCapsule(this);
|
||||
fps = ic.readInt("fps", DEFAULT_FPS);
|
||||
featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL);
|
||||
loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false);
|
||||
assetRootPath = ic.readString("asset-root-path", null);
|
||||
fixUpAxis = ic.readBoolean("fix-up-axis", true);
|
||||
generatedTexturePPU = ic.readInt("generated-texture-ppu", 128);
|
||||
usedWorld = ic.readString("used-world", null);
|
||||
defaultMaterial = (Material) ic.readSavable("default-material", null);
|
||||
faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off);
|
||||
layersToLoad = ic.readInt("layers-to=load", -1);
|
||||
mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED);
|
||||
skyGeneratedTextureSize = ic.readInt("sky-generated-texture-size", 1000);
|
||||
skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
|
||||
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
|
||||
optimiseTextures = ic.readBoolean("optimise-textures", false);
|
||||
animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
|
||||
pointsSize = ic.readFloat("points-size", 1);
|
||||
linesWidth = ic.readFloat("lines-width", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + (animationMatchMethod == null ? 0 : animationMatchMethod.hashCode());
|
||||
result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
|
||||
result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
|
||||
result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
|
||||
result = prime * result + featuresToLoad;
|
||||
result = prime * result + (fixUpAxis ? 1231 : 1237);
|
||||
result = prime * result + fps;
|
||||
result = prime * result + generatedTexturePPU;
|
||||
result = prime * result + layersToLoad;
|
||||
result = prime * result + (loadGeneratedTextures ? 1231 : 1237);
|
||||
result = prime * result + (loadObjectProperties ? 1231 : 1237);
|
||||
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
|
||||
result = prime * result + maxTextureSize;
|
||||
result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode());
|
||||
result = prime * result + (optimiseTextures ? 1231 : 1237);
|
||||
result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
|
||||
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
|
||||
result = prime * result + skyGeneratedTextureSize;
|
||||
result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
|
||||
result = prime * result + (int) pointsSize;
|
||||
result = prime * result + (int) linesWidth;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof BlenderKey) {
|
||||
return false;
|
||||
}
|
||||
BlenderKey other = (BlenderKey) obj;
|
||||
if (animationMatchMethod != other.animationMatchMethod) {
|
||||
return false;
|
||||
}
|
||||
if (assetRootPath == null) {
|
||||
if (other.assetRootPath != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!assetRootPath.equals(other.assetRootPath)) {
|
||||
return false;
|
||||
}
|
||||
if (defaultMaterial == null) {
|
||||
if (other.defaultMaterial != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!defaultMaterial.equals(other.defaultMaterial)) {
|
||||
return false;
|
||||
}
|
||||
if (faceCullMode != other.faceCullMode) {
|
||||
return false;
|
||||
}
|
||||
if (featuresToLoad != other.featuresToLoad) {
|
||||
return false;
|
||||
}
|
||||
if (fixUpAxis != other.fixUpAxis) {
|
||||
return false;
|
||||
}
|
||||
if (fps != other.fps) {
|
||||
return false;
|
||||
}
|
||||
if (generatedTexturePPU != other.generatedTexturePPU) {
|
||||
return false;
|
||||
}
|
||||
if (layersToLoad != other.layersToLoad) {
|
||||
return false;
|
||||
}
|
||||
if (loadGeneratedTextures != other.loadGeneratedTextures) {
|
||||
return false;
|
||||
}
|
||||
if (loadObjectProperties != other.loadObjectProperties) {
|
||||
return false;
|
||||
}
|
||||
if (loadUnlinkedAssets != other.loadUnlinkedAssets) {
|
||||
return false;
|
||||
}
|
||||
if (maxTextureSize != other.maxTextureSize) {
|
||||
return false;
|
||||
}
|
||||
if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
|
||||
return false;
|
||||
}
|
||||
if (optimiseTextures != other.optimiseTextures) {
|
||||
return false;
|
||||
}
|
||||
if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) {
|
||||
return false;
|
||||
}
|
||||
if (skyGeneratedTextureShape != other.skyGeneratedTextureShape) {
|
||||
return false;
|
||||
}
|
||||
if (skyGeneratedTextureSize != other.skyGeneratedTextureSize) {
|
||||
return false;
|
||||
}
|
||||
if (usedWorld == null) {
|
||||
if (other.usedWorld != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!usedWorld.equals(other.usedWorld)) {
|
||||
return false;
|
||||
}
|
||||
if (pointsSize != other.pointsSize) {
|
||||
return false;
|
||||
}
|
||||
if (linesWidth != other.linesWidth) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum tells the importer if the mipmaps for textures will be generated by jme. <li>NEVER_GENERATE and ALWAYS_GENERATE are quite understandable <li>GENERATE_WHEN_NEEDED is an option that checks if the texture had 'Generate mipmaps' option set in blender, mipmaps are generated only when the option is set
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static enum MipmapGenerationMethod {
|
||||
NEVER_GENERATE, ALWAYS_GENERATE, GENERATE_WHEN_NEEDED;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface describes the features of the scene that are to be loaded.
|
||||
* @deprecated this interface is deprecated and is not used anymore; to ensure the loading models consistency
|
||||
* everything must be loaded because in blender one feature might depend on another
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
@Deprecated
|
||||
public static interface FeaturesToLoad {
|
||||
|
||||
int SCENES = 0x0000FFFF;
|
||||
int OBJECTS = 0x0000000B;
|
||||
int ANIMATIONS = 0x00000004;
|
||||
int MATERIALS = 0x00000003;
|
||||
int TEXTURES = 0x00000001;
|
||||
int CAMERAS = 0x00000020;
|
||||
int LIGHTS = 0x00000010;
|
||||
int WORLD = 0x00000040;
|
||||
int ALL = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape againts which the sky generated texture will be created.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static enum SkyGeneratedTextureShape {
|
||||
CUBE, SPHERE;
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum describes which animations should be attached to which armature.
|
||||
* Blender does not store the mapping between action and armature. That is why the importer
|
||||
* will try to match those by comparing bone name of the armature with the channel names
|
||||
* int the actions.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static enum AnimationMatchMethod {
|
||||
/**
|
||||
* Animation is matched with skeleton when at leas one bone name matches the name of the action channel.
|
||||
* All the bones that do not have their corresponding channel in the animation will not get the proper tracks for
|
||||
* this particulat animation.
|
||||
* Also the channel will not be used for the animation if it does not find the proper bone name.
|
||||
*/
|
||||
AT_LEAST_ONE_NAME_MATCH,
|
||||
/**
|
||||
* Animation is matched when all action names are covered by the target names (bone names or the name of the
|
||||
* animated spatial.
|
||||
*/
|
||||
ALL_NAMES_MATCH;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jme3.asset;
|
||||
|
||||
/**
|
||||
* This key is mostly used to distinguish between textures that are loaded from
|
||||
* the given assets and those being generated automatically. Every generated
|
||||
* texture will have this kind of key attached.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class GeneratedTextureKey extends TextureKey {
|
||||
|
||||
/**
|
||||
* Constructor. Stores the name. Extension and folder name are empty
|
||||
* strings.
|
||||
*
|
||||
* @param name
|
||||
* the name of the texture
|
||||
*/
|
||||
public GeneratedTextureKey(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExtension() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFolder() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Generated texture [" + name + "]";
|
||||
}
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.asset.AssetNotFoundException;
|
||||
import com.jme3.asset.BlenderKey;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.objects.Properties;
|
||||
|
||||
/**
|
||||
* A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can
|
||||
* hold the state of the calculations.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public abstract class AbstractBlenderHelper {
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractBlenderHelper.class.getName());
|
||||
|
||||
/** The blender context. */
|
||||
protected BlenderContext blenderContext;
|
||||
/** The version of the blend file. */
|
||||
protected final int blenderVersion;
|
||||
/** This variable indicates if the Y asxis is the UP axis or not. */
|
||||
protected boolean fixUpAxis;
|
||||
/** Quaternion used to rotate data when Y is up axis. */
|
||||
protected Quaternion upAxisRotationQuaternion;
|
||||
|
||||
/**
|
||||
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
|
||||
* versions.
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public AbstractBlenderHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
this.blenderVersion = Integer.parseInt(blenderVersion);
|
||||
this.blenderContext = blenderContext;
|
||||
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
|
||||
if (fixUpAxis) {
|
||||
upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loads the properties if they are available and defined for the structure.
|
||||
* @param structure
|
||||
* the structure we read the properties from
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return loaded properties or null if they are not available
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when the blend file is somehow corrupted
|
||||
*/
|
||||
protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
Properties properties = null;
|
||||
Structure id = (Structure) structure.getFieldValue("ID");
|
||||
if (id != null) {
|
||||
Pointer pProperties = (Pointer) id.getFieldValue("properties");
|
||||
if (pProperties.isNotNull()) {
|
||||
Structure propertiesStructure = pProperties.fetchData().get(0);
|
||||
properties = new Properties();
|
||||
properties.load(propertiesStructure, blenderContext);
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method applies properties to the given spatial. The Properties
|
||||
* instance cannot be directly applied because the end-user might not have
|
||||
* the blender plugin jar file and thus receive ClassNotFoundException. The
|
||||
* values are set by name instead.
|
||||
*
|
||||
* @param spatial
|
||||
* the spatial that is to have properties applied
|
||||
* @param properties
|
||||
* the properties to be applied
|
||||
*/
|
||||
public void applyProperties(Spatial spatial, Properties properties) {
|
||||
List<String> propertyNames = properties.getSubPropertiesNames();
|
||||
if (propertyNames != null && propertyNames.size() > 0) {
|
||||
for (String propertyName : propertyNames) {
|
||||
Object value = properties.findValue(propertyName);
|
||||
if (value instanceof Savable || value instanceof Boolean || value instanceof String || value instanceof Float || value instanceof Integer || value instanceof Long) {
|
||||
spatial.setUserData(propertyName, value);
|
||||
} else if (value instanceof Double) {
|
||||
spatial.setUserData(propertyName, ((Double) value).floatValue());
|
||||
} else if (value instanceof int[]) {
|
||||
spatial.setUserData(propertyName, Arrays.toString((int[]) value));
|
||||
} else if (value instanceof float[]) {
|
||||
spatial.setUserData(propertyName, Arrays.toString((float[]) value));
|
||||
} else if (value instanceof double[]) {
|
||||
spatial.setUserData(propertyName, Arrays.toString((double[]) value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method loads library of a given ID from linked blender file.
|
||||
* @param id
|
||||
* the ID of the linked feature (it contains its name and blender path)
|
||||
* @return loaded feature or null if none was found
|
||||
* @throws BlenderFileException
|
||||
* and exception is throw when problems with reading a blend file occur
|
||||
*/
|
||||
protected Object loadLibrary(Structure id) throws BlenderFileException {
|
||||
Pointer pLib = (Pointer) id.getFieldValue("lib");
|
||||
if (pLib.isNotNull()) {
|
||||
String fullName = id.getFieldValue("name").toString();// we need full name with the prefix
|
||||
String nameOfFeatureToLoad = id.getName();
|
||||
Structure library = pLib.fetchData().get(0);
|
||||
String path = library.getFieldValue("filepath").toString();
|
||||
|
||||
if (!blenderContext.getLinkedFeatures().keySet().contains(path)) {
|
||||
Spatial loadedAsset = null;
|
||||
BlenderKey blenderKey = new BlenderKey(path);
|
||||
blenderKey.setLoadUnlinkedAssets(true);
|
||||
try {
|
||||
loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey);
|
||||
} catch (AssetNotFoundException e) {
|
||||
LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", path);
|
||||
}
|
||||
|
||||
if (loadedAsset != null) {
|
||||
Map<String, Map<String, Object>> linkedData = loadedAsset.getUserData("linkedData");
|
||||
|
||||
for (Entry<String, Map<String, Object>> entry : linkedData.entrySet()) {
|
||||
String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey();
|
||||
blenderContext.getLinkedFeatures().put(linkedDataFilePath, entry.getValue());
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path);
|
||||
}
|
||||
}
|
||||
|
||||
Object result = blenderContext.getLinkedFeature(path, fullName);
|
||||
if (result == null) {
|
||||
LOGGER.log(Level.WARNING, "Could NOT find asset named {0} in the library of path: {1}.", new Object[] { nameOfFeatureToLoad, path });
|
||||
} else {
|
||||
blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id);
|
||||
blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
LOGGER.warning("Library link points to nothing!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,767 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EmptyStackException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Stack;
|
||||
|
||||
import com.jme3.animation.Animation;
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.asset.BlenderKey;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.post.Filter;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.animations.BlenderAction;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.Constraint;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||
import com.jme3.scene.plugins.blender.file.DnaBlockData;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialContext;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
import com.jme3.texture.Texture;
|
||||
|
||||
/**
|
||||
* The class that stores temporary data and manages it during loading the belnd
|
||||
* file. This class is intended to be used in a single loading thread. It holds
|
||||
* the state of loading operations.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class BlenderContext {
|
||||
/** The blender file version. */
|
||||
private int blenderVersion;
|
||||
/** The blender key. */
|
||||
private BlenderKey blenderKey;
|
||||
/** The header of the file block. */
|
||||
private DnaBlockData dnaBlockData;
|
||||
/** The scene structure. */
|
||||
private Structure sceneStructure;
|
||||
/** The input stream of the blend file. */
|
||||
private BlenderInputStream inputStream;
|
||||
/** The asset manager. */
|
||||
private AssetManager assetManager;
|
||||
/** The blocks read from the file. */
|
||||
protected List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>();
|
||||
/**
|
||||
* A map containing the file block headers. The key is the old memory address.
|
||||
*/
|
||||
private Map<Long, FileBlockHeader> fileBlockHeadersByOma = new HashMap<Long, FileBlockHeader>();
|
||||
/** A map containing the file block headers. The key is the block code. */
|
||||
private Map<BlockCode, List<FileBlockHeader>> fileBlockHeadersByCode = new HashMap<BlockCode, List<FileBlockHeader>>();
|
||||
/**
|
||||
* This map stores the loaded features by their old memory address. The
|
||||
* first object in the value table is the loaded structure and the second -
|
||||
* the structure already converted into proper data.
|
||||
*/
|
||||
private Map<Long, Map<LoadedDataType, Object>> loadedFeatures = new HashMap<Long, Map<LoadedDataType, Object>>();
|
||||
/** Features loaded from external blender files. The key is the file path and the value is a map between feature name and loaded feature. */
|
||||
private Map<String, Map<String, Object>> linkedFeatures = new HashMap<String, Map<String, Object>>();
|
||||
/** A stack that hold the parent structure of currently loaded feature. */
|
||||
private Stack<Structure> parentStack = new Stack<Structure>();
|
||||
/** A list of constraints for the specified object. */
|
||||
protected Map<Long, List<Constraint>> constraints = new HashMap<Long, List<Constraint>>();
|
||||
/** Animations loaded for features. */
|
||||
private Map<Long, List<Animation>> animations = new HashMap<Long, List<Animation>>();
|
||||
/** Loaded skeletons. */
|
||||
private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>();
|
||||
/** A map between skeleton and node it modifies. */
|
||||
private Map<Skeleton, Node> nodesWithSkeletons = new HashMap<Skeleton, Node>();
|
||||
/** A map of bone contexts. */
|
||||
protected Map<Long, BoneContext> boneContexts = new HashMap<Long, BoneContext>();
|
||||
/** A map og helpers that perform loading. */
|
||||
private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>();
|
||||
/** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
|
||||
private Map<String, Map<Object, Object>> markers = new HashMap<String, Map<Object, Object>>();
|
||||
/** A map of blender actions. The key is the action name and the value is the action itself. */
|
||||
private Map<String, BlenderAction> actions = new HashMap<String, BlenderAction>();
|
||||
|
||||
/**
|
||||
* This method sets the blender file version.
|
||||
*
|
||||
* @param blenderVersion
|
||||
* the blender file version
|
||||
*/
|
||||
public void setBlenderVersion(String blenderVersion) {
|
||||
this.blenderVersion = Integer.parseInt(blenderVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the blender file version
|
||||
*/
|
||||
public int getBlenderVersion() {
|
||||
return blenderVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the blender key.
|
||||
*
|
||||
* @param blenderKey
|
||||
* the blender key
|
||||
*/
|
||||
public void setBlenderKey(BlenderKey blenderKey) {
|
||||
this.blenderKey = blenderKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the blender key.
|
||||
*
|
||||
* @return the blender key
|
||||
*/
|
||||
public BlenderKey getBlenderKey() {
|
||||
return blenderKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the dna block data.
|
||||
*
|
||||
* @param dnaBlockData
|
||||
* the dna block data
|
||||
*/
|
||||
public void setBlockData(DnaBlockData dnaBlockData) {
|
||||
this.dnaBlockData = dnaBlockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the dna block data.
|
||||
*
|
||||
* @return the dna block data
|
||||
*/
|
||||
public DnaBlockData getDnaBlockData() {
|
||||
return dnaBlockData;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the scene structure data.
|
||||
*
|
||||
* @param sceneStructure
|
||||
* the scene structure data
|
||||
*/
|
||||
public void setSceneStructure(Structure sceneStructure) {
|
||||
this.sceneStructure = sceneStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the scene structure data.
|
||||
*
|
||||
* @return the scene structure data
|
||||
*/
|
||||
public Structure getSceneStructure() {
|
||||
return sceneStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the asset manager.
|
||||
*
|
||||
* @return the asset manager
|
||||
*/
|
||||
public AssetManager getAssetManager() {
|
||||
return assetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the asset manager.
|
||||
*
|
||||
* @param assetManager
|
||||
* the asset manager
|
||||
*/
|
||||
public void setAssetManager(AssetManager assetManager) {
|
||||
this.assetManager = assetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the input stream of the blend file.
|
||||
*
|
||||
* @return the input stream of the blend file
|
||||
*/
|
||||
public BlenderInputStream getInputStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the input stream of the blend file.
|
||||
*
|
||||
* @param inputStream
|
||||
* the input stream of the blend file
|
||||
*/
|
||||
public void setInputStream(BlenderInputStream inputStream) {
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a file block header to the map. Its old memory address
|
||||
* is the key.
|
||||
*
|
||||
* @param oldMemoryAddress
|
||||
* the address of the block header
|
||||
* @param fileBlockHeader
|
||||
* the block header to store
|
||||
*/
|
||||
public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) {
|
||||
blocks.add(fileBlockHeader);
|
||||
fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader);
|
||||
List<FileBlockHeader> headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode());
|
||||
if (headers == null) {
|
||||
headers = new ArrayList<FileBlockHeader>();
|
||||
fileBlockHeadersByCode.put(fileBlockHeader.getCode(), headers);
|
||||
}
|
||||
headers.add(fileBlockHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the block headers
|
||||
*/
|
||||
public List<FileBlockHeader> getBlocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the block header of a given memory address. If the
|
||||
* header is not present then null is returned.
|
||||
*
|
||||
* @param oldMemoryAddress
|
||||
* the address of the block header
|
||||
* @return loaded header or null if it was not yet loaded
|
||||
*/
|
||||
public FileBlockHeader getFileBlock(Long oldMemoryAddress) {
|
||||
return fileBlockHeadersByOma.get(oldMemoryAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of file blocks' headers of a specified code.
|
||||
*
|
||||
* @param code
|
||||
* the code of file blocks
|
||||
* @return a list of file blocks' headers of a specified code
|
||||
*/
|
||||
public List<FileBlockHeader> getFileBlocks(BlockCode code) {
|
||||
return fileBlockHeadersByCode.get(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a helper instance to the helpers' map.
|
||||
*
|
||||
* @param <T>
|
||||
* the type of the helper
|
||||
* @param clazz
|
||||
* helper's class definition
|
||||
* @param helper
|
||||
* the helper instance
|
||||
*/
|
||||
public <T> void putHelper(Class<T> clazz, AbstractBlenderHelper helper) {
|
||||
helpers.put(clazz.getSimpleName(), helper);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getHelper(Class<?> clazz) {
|
||||
return (T) helpers.get(clazz.getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a loaded feature to the map. The key is its unique old
|
||||
* memory address.
|
||||
*
|
||||
* @param oldMemoryAddress
|
||||
* the address of the feature
|
||||
* @param featureDataType
|
||||
* @param feature
|
||||
* the feature we want to store
|
||||
*/
|
||||
public void addLoadedFeatures(Long oldMemoryAddress, LoadedDataType featureDataType, Object feature) {
|
||||
if (oldMemoryAddress == null || featureDataType == null || feature == null) {
|
||||
throw new IllegalArgumentException("One of the given arguments is null!");
|
||||
}
|
||||
Map<LoadedDataType, Object> map = loadedFeatures.get(oldMemoryAddress);
|
||||
if (map == null) {
|
||||
map = new HashMap<BlenderContext.LoadedDataType, Object>();
|
||||
loadedFeatures.put(oldMemoryAddress, map);
|
||||
}
|
||||
map.put(featureDataType, feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the feature of a given memory address. If the feature
|
||||
* is not yet loaded then null is returned.
|
||||
*
|
||||
* @param oldMemoryAddress
|
||||
* the address of the feature
|
||||
* @param loadedFeatureDataType
|
||||
* the type of data we want to retrieve it can be either filled
|
||||
* structure or already converted feature
|
||||
* @return loaded feature or null if it was not yet loaded
|
||||
*/
|
||||
public Object getLoadedFeature(Long oldMemoryAddress, LoadedDataType loadedFeatureDataType) {
|
||||
Map<LoadedDataType, Object> result = loadedFeatures.get(oldMemoryAddress);
|
||||
if (result != null) {
|
||||
return result.get(loadedFeatureDataType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method adds linked content to the blender context.
|
||||
* @param blenderFilePath
|
||||
* the path of linked blender file
|
||||
* @param featureGroup
|
||||
* the linked feature group (ie. scenes, materials, meshes, etc.)
|
||||
* @param feature
|
||||
* the linked feature
|
||||
*/
|
||||
@Deprecated
|
||||
public void addLinkedFeature(String blenderFilePath, String featureGroup, Object feature) {
|
||||
// the method is deprecated and empty at the moment
|
||||
}
|
||||
|
||||
/**
|
||||
* The method returns linked feature of a given name from the specified blender path.
|
||||
* @param blenderFilePath
|
||||
* the blender file path
|
||||
* @param featureName
|
||||
* the feature name we want to get
|
||||
* @return linked feature or null if none was found
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getLinkedFeature(String blenderFilePath, String featureName) {
|
||||
Map<String, Object> linkedFeatures = this.linkedFeatures.get(blenderFilePath);
|
||||
if(linkedFeatures != null) {
|
||||
String namePrefix = (featureName.charAt(0) + "" + featureName.charAt(1)).toUpperCase();
|
||||
featureName = featureName.substring(2);
|
||||
|
||||
if("SC".equals(namePrefix)) {
|
||||
List<Node> scenes = (List<Node>) linkedFeatures.get("scenes");
|
||||
if(scenes != null) {
|
||||
for(Node scene : scenes) {
|
||||
if(featureName.equals(scene.getName())) {
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("OB".equals(namePrefix)) {
|
||||
List<Node> features = (List<Node>) linkedFeatures.get("objects");
|
||||
if(features != null) {
|
||||
for(Node feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("ME".equals(namePrefix)) {
|
||||
List<TemporalMesh> temporalMeshes = (List<TemporalMesh>) linkedFeatures.get("meshes");
|
||||
if(temporalMeshes != null) {
|
||||
for(TemporalMesh temporalMesh : temporalMeshes) {
|
||||
if(featureName.equals(temporalMesh.getName())) {
|
||||
return temporalMesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("MA".equals(namePrefix)) {
|
||||
List<MaterialContext> features = (List<MaterialContext>) linkedFeatures.get("materials");
|
||||
if(features != null) {
|
||||
for(MaterialContext feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("TX".equals(namePrefix)) {
|
||||
List<Texture> features = (List<Texture>) linkedFeatures.get("textures");
|
||||
if(features != null) {
|
||||
for(Texture feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("IM".equals(namePrefix)) {
|
||||
List<Texture> features = (List<Texture>) linkedFeatures.get("images");
|
||||
if(features != null) {
|
||||
for(Texture feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("AC".equals(namePrefix)) {
|
||||
List<Animation> features = (List<Animation>) linkedFeatures.get("animations");
|
||||
if(features != null) {
|
||||
for(Animation feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("CA".equals(namePrefix)) {
|
||||
List<Camera> features = (List<Camera>) linkedFeatures.get("cameras");
|
||||
if(features != null) {
|
||||
for(Camera feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("LA".equals(namePrefix)) {
|
||||
List<Light> features = (List<Light>) linkedFeatures.get("lights");
|
||||
if(features != null) {
|
||||
for(Light feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if("FI".equals(featureName)) {
|
||||
List<Filter> features = (List<Filter>) linkedFeatures.get("lights");
|
||||
if(features != null) {
|
||||
for(Filter feature : features) {
|
||||
if(featureName.equals(feature.getName())) {
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all linked features for the current blend file
|
||||
*/
|
||||
public Map<String, Map<String, Object>> getLinkedFeatures() {
|
||||
return linkedFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds the structure to the parent stack.
|
||||
*
|
||||
* @param parent
|
||||
* the structure to be added to the stack
|
||||
*/
|
||||
public void pushParent(Structure parent) {
|
||||
parentStack.push(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method removes the structure from the top of the parent's stack.
|
||||
*
|
||||
* @return the structure that was removed from the stack
|
||||
*/
|
||||
public Structure popParent() {
|
||||
try {
|
||||
return parentStack.pop();
|
||||
} catch (EmptyStackException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retrieves the structure at the top of the parent's stack but
|
||||
* does not remove it.
|
||||
*
|
||||
* @return the structure from the top of the stack
|
||||
*/
|
||||
public Structure peekParent() {
|
||||
try {
|
||||
return parentStack.peek();
|
||||
} catch (EmptyStackException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds a new modifier to the list.
|
||||
*
|
||||
* @param ownerOMA
|
||||
* the owner's old memory address
|
||||
* @param constraints
|
||||
* the object's constraints
|
||||
*/
|
||||
public void addConstraints(Long ownerOMA, List<Constraint> constraints) {
|
||||
List<Constraint> objectConstraints = this.constraints.get(ownerOMA);
|
||||
if (objectConstraints == null) {
|
||||
objectConstraints = new ArrayList<Constraint>();
|
||||
this.constraints.put(ownerOMA, objectConstraints);
|
||||
}
|
||||
objectConstraints.addAll(constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns constraints applied to the feature of the given OMA.
|
||||
* @param ownerOMA
|
||||
* the constraints' owner OMA
|
||||
* @return a list of constraints or <b>null</b> if no constraints are applied to the feature
|
||||
*/
|
||||
public List<Constraint> getConstraints(Long ownerOMA) {
|
||||
return constraints.get(ownerOMA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all available constraints
|
||||
*/
|
||||
public List<Constraint> getAllConstraints() {
|
||||
List<Constraint> result = new ArrayList<Constraint>();
|
||||
for (Entry<Long, List<Constraint>> entry : constraints.entrySet()) {
|
||||
result.addAll(entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method adds the animation for the specified OMA of its owner.
|
||||
*
|
||||
* @param ownerOMA
|
||||
* the owner's old memory address
|
||||
* @param animation
|
||||
* the animation for the feature specified by ownerOMA
|
||||
*/
|
||||
public void addAnimation(Long ownerOMA, Animation animation) {
|
||||
List<Animation> animList = animations.get(ownerOMA);
|
||||
if (animList == null) {
|
||||
animList = new ArrayList<Animation>();
|
||||
animations.put(ownerOMA, animList);
|
||||
}
|
||||
animList.add(animation);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the animation data for the specified owner.
|
||||
*
|
||||
* @param ownerOMA
|
||||
* the old memory address of the animation data owner
|
||||
* @return the animation or null if none exists
|
||||
*/
|
||||
public List<Animation> getAnimations(Long ownerOMA) {
|
||||
return animations.get(ownerOMA);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the skeleton for the specified OMA of its owner.
|
||||
*
|
||||
* @param skeletonOMA
|
||||
* the skeleton's old memory address
|
||||
* @param skeleton
|
||||
* the skeleton specified by the given OMA
|
||||
*/
|
||||
public void setSkeleton(Long skeletonOMA, Skeleton skeleton) {
|
||||
skeletons.put(skeletonOMA, skeleton);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method stores a binding between the skeleton and the proper armature
|
||||
* node.
|
||||
*
|
||||
* @param skeleton
|
||||
* the skeleton
|
||||
* @param node
|
||||
* the armature node
|
||||
*/
|
||||
public void setNodeForSkeleton(Skeleton skeleton, Node node) {
|
||||
nodesWithSkeletons.put(skeleton, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the armature node that is defined for the skeleton.
|
||||
*
|
||||
* @param skeleton
|
||||
* the skeleton
|
||||
* @return the armature node that defines the skeleton in blender
|
||||
*/
|
||||
public Node getControlledNode(Skeleton skeleton) {
|
||||
return nodesWithSkeletons.get(skeleton);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the skeleton for the specified OMA of its owner.
|
||||
*
|
||||
* @param skeletonOMA
|
||||
* the skeleton's old memory address
|
||||
* @return the skeleton specified by the given OMA
|
||||
*/
|
||||
public Skeleton getSkeleton(Long skeletonOMA) {
|
||||
return skeletons.get(skeletonOMA);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the bone context for the given bone old memory address.
|
||||
* If the context is already set it will be replaced.
|
||||
*
|
||||
* @param boneOMA
|
||||
* the bone's old memory address
|
||||
* @param boneContext
|
||||
* the bones's context
|
||||
*/
|
||||
public void setBoneContext(Long boneOMA, BoneContext boneContext) {
|
||||
boneContexts.put(boneOMA, boneContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the bone context for the given bone old memory
|
||||
* address. If no context exists then <b>null</b> is returned.
|
||||
*
|
||||
* @param boneOMA
|
||||
* the bone's old memory address
|
||||
* @return bone's context
|
||||
*/
|
||||
public BoneContext getBoneContext(Long boneOMA) {
|
||||
return boneContexts.get(boneOMA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bone by given name.
|
||||
*
|
||||
* @param skeletonOMA
|
||||
* the OMA of the skeleton where the bone will be searched
|
||||
* @param name
|
||||
* the name of the bone
|
||||
* @return found bone or null if none bone of a given name exists
|
||||
*/
|
||||
public BoneContext getBoneByName(Long skeletonOMA, String name) {
|
||||
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
|
||||
if (entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) {
|
||||
Bone bone = entry.getValue().getBone();
|
||||
if (bone != null && name.equals(bone.getName())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bone context for the given bone.
|
||||
*
|
||||
* @param bone
|
||||
* the bone
|
||||
* @return the bone's bone context
|
||||
*/
|
||||
public BoneContext getBoneContext(Bone bone) {
|
||||
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
|
||||
if (entry.getValue().getBone().getName().equals(bone.getName())) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Cannot find context for bone: " + bone);
|
||||
}
|
||||
|
||||
/**
|
||||
* This metod returns the default material.
|
||||
*
|
||||
* @return the default material
|
||||
*/
|
||||
public synchronized Material getDefaultMaterial() {
|
||||
if (blenderKey.getDefaultMaterial() == null) {
|
||||
Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
defaultMaterial.setColor("Color", ColorRGBA.DarkGray);
|
||||
blenderKey.setDefaultMaterial(defaultMaterial);
|
||||
}
|
||||
return blenderKey.getDefaultMaterial();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom marker for scene's feature.
|
||||
*
|
||||
* @param marker
|
||||
* the marker name
|
||||
* @param feature
|
||||
* te scene's feature (can be node, material or texture or
|
||||
* anything else)
|
||||
* @param markerValue
|
||||
* the marker value
|
||||
*/
|
||||
public void addMarker(String marker, Object feature, Object markerValue) {
|
||||
if (markerValue == null) {
|
||||
throw new IllegalArgumentException("The marker's value cannot be null.");
|
||||
}
|
||||
Map<Object, Object> markersMap = markers.get(marker);
|
||||
if (markersMap == null) {
|
||||
markersMap = new HashMap<Object, Object>();
|
||||
markers.put(marker, markersMap);
|
||||
}
|
||||
markersMap.put(feature, markerValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the marker value. The returned value is null if no marker was
|
||||
* defined for the given feature.
|
||||
*
|
||||
* @param marker
|
||||
* the marker name
|
||||
* @param feature
|
||||
* the scene's feature
|
||||
* @return marker value or null if it was not defined
|
||||
*/
|
||||
public Object getMarkerValue(String marker, Object feature) {
|
||||
Map<Object, Object> markersMap = markers.get(marker);
|
||||
return markersMap == null ? null : markersMap.get(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds blender action to the context.
|
||||
* @param action
|
||||
* the action loaded from the blend file
|
||||
*/
|
||||
public void addAction(BlenderAction action) {
|
||||
actions.put(action.getName(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a map of blender actions; the key is the action name and the value is action itself
|
||||
*/
|
||||
public Map<String, BlenderAction> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum defines what loaded data type user wants to retrieve. It can be
|
||||
* either filled structure or already converted data.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static enum LoadedDataType {
|
||||
STRUCTURE, FEATURE, TEMPORAL_MESH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return blenderKey == null ? "BlenderContext [key = null]" : "BlenderContext [ key = " + blenderKey.toString() + " ]";
|
||||
}
|
||||
}
|
@ -1,419 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.animation.Animation;
|
||||
import com.jme3.asset.AssetInfo;
|
||||
import com.jme3.asset.AssetKey;
|
||||
import com.jme3.asset.AssetLoader;
|
||||
import com.jme3.asset.AssetLocator;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.asset.BlenderKey;
|
||||
import com.jme3.asset.ModelKey;
|
||||
import com.jme3.asset.StreamAssetInfo;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.post.Filter;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.CameraNode;
|
||||
import com.jme3.scene.LightNode;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
||||
import com.jme3.scene.plugins.blender.cameras.CameraHelper;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||
import com.jme3.scene.plugins.blender.curves.CurvesHelper;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.landscape.LandscapeHelper;
|
||||
import com.jme3.scene.plugins.blender.lights.LightHelper;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialContext;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
||||
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
import com.jme3.scene.plugins.blender.particles.ParticlesHelper;
|
||||
import com.jme3.scene.plugins.blender.textures.TextureHelper;
|
||||
import com.jme3.texture.Texture;
|
||||
|
||||
/**
|
||||
* This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class BlenderLoader implements AssetLoader {
|
||||
private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName());
|
||||
|
||||
@Override
|
||||
public Spatial load(AssetInfo assetInfo) throws IOException {
|
||||
try {
|
||||
BlenderContext blenderContext = this.setup(assetInfo);
|
||||
|
||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||
animationHelper.loadAnimations();
|
||||
|
||||
BlenderKey blenderKey = blenderContext.getBlenderKey();
|
||||
LoadedFeatures loadedFeatures = new LoadedFeatures();
|
||||
for (FileBlockHeader block : blenderContext.getBlocks()) {
|
||||
switch (block.getCode()) {
|
||||
case BLOCK_OB00:
|
||||
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
||||
Node object = (Node) objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
|
||||
if (LOGGER.isLoggable(Level.FINE)) {
|
||||
LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { object.getName(), object.getLocalTranslation().toString(), object.getParent() == null ? "null" : object.getParent().getName() });
|
||||
}
|
||||
if (object.getParent() == null) {
|
||||
loadedFeatures.objects.add(object);
|
||||
}
|
||||
if (object instanceof LightNode && ((LightNode) object).getLight() != null) {
|
||||
loadedFeatures.lights.add(((LightNode) object).getLight());
|
||||
} else if (object instanceof CameraNode && ((CameraNode) object).getCamera() != null) {
|
||||
loadedFeatures.cameras.add(((CameraNode) object).getCamera());
|
||||
}
|
||||
break;
|
||||
case BLOCK_SC00:// Scene
|
||||
loadedFeatures.sceneBlocks.add(block);
|
||||
break;
|
||||
case BLOCK_MA00:// Material
|
||||
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
|
||||
MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext);
|
||||
loadedFeatures.materials.add(materialContext);
|
||||
break;
|
||||
case BLOCK_ME00:// Mesh
|
||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
|
||||
TemporalMesh temporalMesh = meshHelper.toTemporalMesh(block.getStructure(blenderContext), blenderContext);
|
||||
loadedFeatures.meshes.add(temporalMesh);
|
||||
break;
|
||||
case BLOCK_IM00:// Image
|
||||
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
|
||||
Texture image = textureHelper.loadImageAsTexture(block.getStructure(blenderContext), 0, blenderContext);
|
||||
if (image != null && image.getImage() != null) {// render results are stored as images but are not being loaded
|
||||
loadedFeatures.images.add(image);
|
||||
}
|
||||
break;
|
||||
case BLOCK_TE00:
|
||||
Structure textureStructure = block.getStructure(blenderContext);
|
||||
int type = ((Number) textureStructure.getFieldValue("type")).intValue();
|
||||
if (type == TextureHelper.TEX_IMAGE) {
|
||||
TextureHelper texHelper = blenderContext.getHelper(TextureHelper.class);
|
||||
Texture texture = texHelper.getTexture(textureStructure, null, blenderContext);
|
||||
if (texture != null) {// null is returned when texture has no image
|
||||
loadedFeatures.textures.add(texture);
|
||||
}
|
||||
} else {
|
||||
LOGGER.fine("Only image textures can be loaded as unlinked assets. Generated textures will be applied to an existing object.");
|
||||
}
|
||||
break;
|
||||
case BLOCK_WO00:// World
|
||||
LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
|
||||
Structure worldStructure = block.getStructure(blenderContext);
|
||||
|
||||
String worldName = worldStructure.getName();
|
||||
if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
|
||||
|
||||
Light ambientLight = landscapeHelper.toAmbientLight(worldStructure);
|
||||
if (ambientLight != null) {
|
||||
loadedFeatures.objects.add(new LightNode(null, ambientLight));
|
||||
loadedFeatures.lights.add(ambientLight);
|
||||
}
|
||||
loadedFeatures.sky = landscapeHelper.toSky(worldStructure);
|
||||
loadedFeatures.backgroundColor = landscapeHelper.toBackgroundColor(worldStructure);
|
||||
|
||||
Filter fogFilter = landscapeHelper.toFog(worldStructure);
|
||||
if (fogFilter != null) {
|
||||
loadedFeatures.filters.add(landscapeHelper.toFog(worldStructure));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BLOCK_AC00:
|
||||
LOGGER.fine("Loading unlinked animations is not yet supported!");
|
||||
break;
|
||||
default:
|
||||
LOGGER.log(Level.FINEST, "Ommiting the block: {0}.", block.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.fine("Baking constraints after every feature is loaded.");
|
||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||
constraintHelper.bakeConstraints(blenderContext);
|
||||
|
||||
LOGGER.fine("Loading scenes and attaching them to the root object.");
|
||||
for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) {
|
||||
loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext), blenderContext));
|
||||
}
|
||||
|
||||
LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it.");
|
||||
Node modelRoot = new Node(blenderKey.getName());
|
||||
for (Node scene : loadedFeatures.scenes) {
|
||||
modelRoot.attachChild(scene);
|
||||
}
|
||||
|
||||
if (blenderKey.isLoadUnlinkedAssets()) {
|
||||
LOGGER.fine("Setting loaded content as user data in resulting sptaial.");
|
||||
Map<String, Map<String, Object>> linkedData = new HashMap<String, Map<String, Object>>();
|
||||
|
||||
Map<String, Object> thisFileData = new HashMap<String, Object>();
|
||||
thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList<Object>() : loadedFeatures.scenes);
|
||||
thisFileData.put("objects", loadedFeatures.objects == null ? new ArrayList<Object>() : loadedFeatures.objects);
|
||||
thisFileData.put("meshes", loadedFeatures.meshes == null ? new ArrayList<Object>() : loadedFeatures.meshes);
|
||||
thisFileData.put("materials", loadedFeatures.materials == null ? new ArrayList<Object>() : loadedFeatures.materials);
|
||||
thisFileData.put("textures", loadedFeatures.textures == null ? new ArrayList<Object>() : loadedFeatures.textures);
|
||||
thisFileData.put("images", loadedFeatures.images == null ? new ArrayList<Object>() : loadedFeatures.images);
|
||||
thisFileData.put("animations", loadedFeatures.animations == null ? new ArrayList<Object>() : loadedFeatures.animations);
|
||||
thisFileData.put("cameras", loadedFeatures.cameras == null ? new ArrayList<Object>() : loadedFeatures.cameras);
|
||||
thisFileData.put("lights", loadedFeatures.lights == null ? new ArrayList<Object>() : loadedFeatures.lights);
|
||||
thisFileData.put("filters", loadedFeatures.filters == null ? new ArrayList<Object>() : loadedFeatures.filters);
|
||||
thisFileData.put("backgroundColor", loadedFeatures.backgroundColor);
|
||||
thisFileData.put("sky", loadedFeatures.sky);
|
||||
|
||||
linkedData.put("this", thisFileData);
|
||||
linkedData.putAll(blenderContext.getLinkedFeatures());
|
||||
|
||||
modelRoot.setUserData("linkedData", linkedData);
|
||||
}
|
||||
|
||||
return modelRoot;
|
||||
} catch (BlenderFileException e) {
|
||||
throw new IOException(e.getLocalizedMessage(), e);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unexpected importer exception occurred: " + e.getLocalizedMessage(), e);
|
||||
} finally {
|
||||
this.clear(assetInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the given structure to a scene node.
|
||||
* @param structure
|
||||
* structure of a scene
|
||||
* @param blenderContext the blender context
|
||||
* @return scene's node
|
||||
* @throws BlenderFileException
|
||||
* an exception throw when problems with blender file occur
|
||||
*/
|
||||
private Node toScene(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
||||
Node result = new Node(structure.getName());
|
||||
List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase();
|
||||
for (Structure b : base) {
|
||||
Pointer pObject = (Pointer) b.getFieldValue("object");
|
||||
if (pObject.isNotNull()) {
|
||||
Structure objectStructure = pObject.fetchData().get(0);
|
||||
|
||||
Object object = objectHelper.toObject(objectStructure, blenderContext);
|
||||
if (object instanceof Node) {
|
||||
if (LOGGER.isLoggable(Level.FINE)) {
|
||||
LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
|
||||
}
|
||||
|
||||
if (((Node) object).getParent() == null) {
|
||||
result.attachChild((Spatial) object);
|
||||
}
|
||||
|
||||
if(object instanceof LightNode) {
|
||||
result.addLight(((LightNode) object).getLight());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets up the loader.
|
||||
* @param assetInfo
|
||||
* the asset info
|
||||
* @throws BlenderFileException
|
||||
* an exception is throw when something wrong happens with blender file
|
||||
*/
|
||||
protected BlenderContext setup(AssetInfo assetInfo) throws BlenderFileException {
|
||||
// registering loaders
|
||||
ModelKey modelKey = (ModelKey) assetInfo.getKey();
|
||||
BlenderKey blenderKey;
|
||||
if (modelKey instanceof BlenderKey) {
|
||||
blenderKey = (BlenderKey) modelKey;
|
||||
} else {
|
||||
blenderKey = new BlenderKey(modelKey.getName());
|
||||
}
|
||||
|
||||
// opening stream
|
||||
BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream());
|
||||
|
||||
// reading blocks
|
||||
List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>();
|
||||
FileBlockHeader fileBlock;
|
||||
BlenderContext blenderContext = new BlenderContext();
|
||||
blenderContext.setBlenderVersion(inputStream.getVersionNumber());
|
||||
blenderContext.setAssetManager(assetInfo.getManager());
|
||||
blenderContext.setInputStream(inputStream);
|
||||
blenderContext.setBlenderKey(blenderKey);
|
||||
|
||||
// creating helpers
|
||||
blenderContext.putHelper(AnimationHelper.class, new AnimationHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
|
||||
|
||||
// reading the blocks (dna block is automatically saved in the blender context when found)
|
||||
FileBlockHeader sceneFileBlock = null;
|
||||
do {
|
||||
fileBlock = new FileBlockHeader(inputStream, blenderContext);
|
||||
if (!fileBlock.isDnaBlock()) {
|
||||
blocks.add(fileBlock);
|
||||
// save the scene's file block
|
||||
if (fileBlock.getCode() == BlockCode.BLOCK_SC00) {
|
||||
sceneFileBlock = fileBlock;
|
||||
}
|
||||
}
|
||||
} while (!fileBlock.isLastBlock());
|
||||
if (sceneFileBlock != null) {
|
||||
blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext));
|
||||
}
|
||||
|
||||
// adding locator for linked content
|
||||
assetInfo.getManager().registerLocator(assetInfo.getKey().getName(), LinkedContentLocator.class);
|
||||
|
||||
return blenderContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* The internal data is only needed during loading so make it unreachable so that the GC can release
|
||||
* that memory (which can be quite large amount).
|
||||
*/
|
||||
protected void clear(AssetInfo assetInfo) {
|
||||
assetInfo.getManager().unregisterLocator(assetInfo.getKey().getName(), LinkedContentLocator.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class holds the loading results according to the given loading flag.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
private static class LoadedFeatures {
|
||||
private List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
|
||||
/** The scenes from the file. */
|
||||
private List<Node> scenes = new ArrayList<Node>();
|
||||
/** Objects from all scenes. */
|
||||
private List<Node> objects = new ArrayList<Node>();
|
||||
/** All meshes. */
|
||||
private List<TemporalMesh> meshes = new ArrayList<TemporalMesh>();
|
||||
/** Materials from all objects. */
|
||||
private List<MaterialContext> materials = new ArrayList<MaterialContext>();
|
||||
/** Textures from all objects. */
|
||||
private List<Texture> textures = new ArrayList<Texture>();
|
||||
/** The images stored in the blender file. */
|
||||
private List<Texture> images = new ArrayList<Texture>();
|
||||
/** Animations of all objects. */
|
||||
private List<Animation> animations = new ArrayList<Animation>();
|
||||
/** All cameras from the file. */
|
||||
private List<Camera> cameras = new ArrayList<Camera>();
|
||||
/** All lights from the file. */
|
||||
private List<Light> lights = new ArrayList<Light>();
|
||||
/** Loaded sky. */
|
||||
private Spatial sky;
|
||||
/** Scene filters (ie. FOG). */
|
||||
private List<Filter> filters = new ArrayList<Filter>();
|
||||
/**
|
||||
* The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
|
||||
* is set to default (as in blender editor.
|
||||
*/
|
||||
private ColorRGBA backgroundColor = ColorRGBA.Gray;
|
||||
}
|
||||
|
||||
public static class LinkedContentLocator implements AssetLocator {
|
||||
private File rootFolder;
|
||||
|
||||
@Override
|
||||
public void setRootPath(String rootPath) {
|
||||
rootFolder = new File(rootPath);
|
||||
if(rootFolder.isFile()) {
|
||||
rootFolder = rootFolder.getParentFile();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public AssetInfo locate(AssetManager manager, AssetKey key) {
|
||||
if(key instanceof BlenderKey) {
|
||||
File linkedAbsoluteFile = new File(key.getName());
|
||||
if(linkedAbsoluteFile.exists() && linkedAbsoluteFile.isFile()) {
|
||||
try {
|
||||
return new StreamAssetInfo(manager, key, new FileInputStream(linkedAbsoluteFile));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
File linkedFileInCurrentAssetFolder = new File(rootFolder, linkedAbsoluteFile.getName());
|
||||
if(linkedFileInCurrentAssetFolder.exists() && linkedFileInCurrentAssetFolder.isFile()) {
|
||||
try {
|
||||
return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentAssetFolder));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
File linkedFileInCurrentFolder = new File(".", linkedAbsoluteFile.getName());
|
||||
if(linkedFileInCurrentFolder.exists() && linkedFileInCurrentFolder.isFile()) {
|
||||
try {
|
||||
return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentFolder));
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender;
|
||||
|
||||
/**
|
||||
* This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
|
||||
* @deprecated this class is deprecated; use BlenderLoader instead
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class BlenderModelLoader extends BlenderLoader {
|
||||
}
|
@ -1,391 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.animations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.Animation;
|
||||
import com.jme3.animation.BoneTrack;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.animation.SkeletonControl;
|
||||
import com.jme3.animation.SpatialTrack;
|
||||
import com.jme3.asset.BlenderKey.AnimationMatchMethod;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo;
|
||||
import com.jme3.scene.plugins.blender.curves.BezierCurve;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
|
||||
/**
|
||||
* The helper class that helps in animations loading.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class AnimationHelper extends AbstractBlenderHelper {
|
||||
private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName());
|
||||
|
||||
public AnimationHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all animations that are stored in the blender file. The animations are not yet applied to the scene features.
|
||||
* This should be called before objects are loaded.
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with blender file reading occur
|
||||
*/
|
||||
public void loadAnimations() throws BlenderFileException {
|
||||
LOGGER.info("Loading animations that will be later applied to scene features.");
|
||||
List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(BlockCode.BLOCK_AC00);
|
||||
if (actionHeaders != null) {
|
||||
for (FileBlockHeader header : actionHeaders) {
|
||||
Structure actionStructure = header.getStructure(blenderContext);
|
||||
LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName());
|
||||
blenderContext.addAction(this.getTracks(actionStructure, blenderContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file.
|
||||
* @param node
|
||||
* the node to whom the animations will be applied
|
||||
* @param animationMatchMethod
|
||||
* the way animation should be matched with node
|
||||
*/
|
||||
public void applyAnimations(Node node, AnimationMatchMethod animationMatchMethod) {
|
||||
List<BlenderAction> actions = this.getActions(node, animationMatchMethod);
|
||||
if (actions.size() > 0) {
|
||||
List<Animation> animations = new ArrayList<Animation>();
|
||||
for (BlenderAction action : actions) {
|
||||
SpatialTrack[] tracks = action.toTracks(node, blenderContext);
|
||||
if (tracks != null && tracks.length > 0) {
|
||||
Animation spatialAnimation = new Animation(action.getName(), action.getAnimationTime());
|
||||
spatialAnimation.setTracks(tracks);
|
||||
animations.add(spatialAnimation);
|
||||
blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
if (animations.size() > 0) {
|
||||
AnimControl control = new AnimControl();
|
||||
HashMap<String, Animation> anims = new HashMap<String, Animation>(animations.size());
|
||||
for (int i = 0; i < animations.size(); ++i) {
|
||||
Animation animation = animations.get(i);
|
||||
anims.put(animation.getName(), animation);
|
||||
}
|
||||
control.setAnimations(anims);
|
||||
node.addControl(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method applies skeleton animations to the given node.
|
||||
* @param node
|
||||
* the node where the animations will be applied
|
||||
* @param skeleton
|
||||
* the skeleton of the node
|
||||
* @param animationMatchMethod
|
||||
* the way animation should be matched with skeleton
|
||||
*/
|
||||
public void applyAnimations(Node node, Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
|
||||
node.addControl(new SkeletonControl(skeleton));
|
||||
blenderContext.setNodeForSkeleton(skeleton, node);
|
||||
List<BlenderAction> actions = this.getActions(skeleton, animationMatchMethod);
|
||||
|
||||
if (actions.size() > 0) {
|
||||
List<Animation> animations = new ArrayList<Animation>();
|
||||
for (BlenderAction action : actions) {
|
||||
BoneTrack[] tracks = action.toTracks(skeleton, blenderContext);
|
||||
if (tracks != null && tracks.length > 0) {
|
||||
Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime());
|
||||
boneAnimation.setTracks(tracks);
|
||||
animations.add(boneAnimation);
|
||||
Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
|
||||
blenderContext.addAnimation(animatedNodeOMA, boneAnimation);
|
||||
}
|
||||
}
|
||||
if (animations.size() > 0) {
|
||||
AnimControl control = new AnimControl(skeleton);
|
||||
HashMap<String, Animation> anims = new HashMap<String, Animation>(animations.size());
|
||||
for (int i = 0; i < animations.size(); ++i) {
|
||||
Animation animation = animations.get(i);
|
||||
anims.put(animation.getName(), animation);
|
||||
}
|
||||
control.setAnimations(anims);
|
||||
node.addControl(control);
|
||||
|
||||
// make sure that SkeletonControl is added AFTER the AnimControl
|
||||
SkeletonControl skeletonControl = node.getControl(SkeletonControl.class);
|
||||
if (skeletonControl != null) {
|
||||
node.removeControl(SkeletonControl.class);
|
||||
node.addControl(skeletonControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates an ipo object used for interpolation calculations.
|
||||
*
|
||||
* @param ipoStructure
|
||||
* the structure with ipo definition
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return the ipo object
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blender file is somehow
|
||||
* corrupted
|
||||
*/
|
||||
public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
Structure curvebase = (Structure) ipoStructure.getFieldValue("curve");
|
||||
|
||||
// preparing bezier curves
|
||||
Ipo result = null;
|
||||
List<Structure> curves = curvebase.evaluateListBase();// IpoCurve
|
||||
if (curves.size() > 0) {
|
||||
BezierCurve[] bezierCurves = new BezierCurve[curves.size()];
|
||||
int frame = 0;
|
||||
for (Structure curve : curves) {
|
||||
Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt");
|
||||
List<Structure> bezTriples = pBezTriple.fetchData();
|
||||
int type = ((Number) curve.getFieldValue("adrcode")).intValue();
|
||||
bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2);
|
||||
}
|
||||
curves.clear();
|
||||
result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
|
||||
Long ipoOma = ipoStructure.getOldMemoryAddress();
|
||||
blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.STRUCTURE, ipoStructure);
|
||||
blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.FEATURE, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates an ipo with only a single value. No track type is
|
||||
* specified so do not use it for calculating tracks.
|
||||
*
|
||||
* @param constValue
|
||||
* the value of this ipo
|
||||
* @return constant ipo
|
||||
*/
|
||||
public Ipo fromValue(float constValue) {
|
||||
return new ConstIpo(constValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retuns the bone tracks for animation.
|
||||
*
|
||||
* @param actionStructure
|
||||
* the structure containing the tracks
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return a list of tracks for the specified animation
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when there are problems with the blend
|
||||
* file
|
||||
*/
|
||||
private BlenderAction getTracks(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
if (blenderVersion < 250) {
|
||||
return this.getTracks249(actionStructure, blenderContext);
|
||||
} else {
|
||||
return this.getTracks250(actionStructure, blenderContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retuns the bone tracks for animation for blender version 2.50
|
||||
* and higher.
|
||||
*
|
||||
* @param actionStructure
|
||||
* the structure containing the tracks
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return a list of tracks for the specified animation
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when there are problems with the blend
|
||||
* file
|
||||
*/
|
||||
private BlenderAction getTracks250(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Getting tracks!");
|
||||
Structure groups = (Structure) actionStructure.getFieldValue("groups");
|
||||
List<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
|
||||
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
|
||||
int lastFrame = 1;
|
||||
for (Structure actionGroup : actionGroups) {
|
||||
String name = actionGroup.getFieldValue("name").toString();
|
||||
List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase();
|
||||
BezierCurve[] bezierCurves = new BezierCurve[channels.size()];
|
||||
int channelCounter = 0;
|
||||
for (Structure c : channels) {
|
||||
int type = this.getCurveType(c, blenderContext);
|
||||
Pointer pBezTriple = (Pointer) c.getFieldValue("bezt");
|
||||
List<Structure> bezTriples = pBezTriple.fetchData();
|
||||
bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);
|
||||
}
|
||||
|
||||
Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
|
||||
lastFrame = Math.max(lastFrame, ipo.getLastFrame());
|
||||
blenderAction.featuresTracks.put(name, ipo);
|
||||
}
|
||||
blenderAction.stopFrame = lastFrame;
|
||||
return blenderAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method retuns the bone tracks for animation for blender version 2.49
|
||||
* (and probably several lower versions too).
|
||||
*
|
||||
* @param actionStructure
|
||||
* the structure containing the tracks
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return a list of tracks for the specified animation
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when there are problems with the blend
|
||||
* file
|
||||
*/
|
||||
private BlenderAction getTracks249(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Getting tracks!");
|
||||
Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
|
||||
List<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
|
||||
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
|
||||
int lastFrame = 1;
|
||||
for (Structure bActionChannel : actionChannels) {
|
||||
String animatedFeatureName = bActionChannel.getFieldValue("name").toString();
|
||||
Pointer p = (Pointer) bActionChannel.getFieldValue("ipo");
|
||||
if (!p.isNull()) {
|
||||
Structure ipoStructure = p.fetchData().get(0);
|
||||
Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext);
|
||||
if (ipo != null) {// this can happen when ipo with no curves appear in blender file
|
||||
lastFrame = Math.max(lastFrame, ipo.getLastFrame());
|
||||
blenderAction.featuresTracks.put(animatedFeatureName, ipo);
|
||||
}
|
||||
}
|
||||
}
|
||||
blenderAction.stopFrame = lastFrame;
|
||||
return blenderAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the type of the ipo curve.
|
||||
*
|
||||
* @param structure
|
||||
* the structure must contain the 'rna_path' field and
|
||||
* 'array_index' field (the type is not important here)
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return the type of the curve
|
||||
*/
|
||||
public int getCurveType(Structure structure, BlenderContext blenderContext) {
|
||||
// reading rna path first
|
||||
BlenderInputStream bis = blenderContext.getInputStream();
|
||||
int currentPosition = bis.getPosition();
|
||||
Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path");
|
||||
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress());
|
||||
bis.setPosition(dataFileBlock.getBlockPosition());
|
||||
String rnaPath = bis.readString();
|
||||
bis.setPosition(currentPosition);
|
||||
int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue();
|
||||
|
||||
// determining the curve type
|
||||
if (rnaPath.endsWith("location")) {
|
||||
return Ipo.AC_LOC_X + arrayIndex;
|
||||
}
|
||||
if (rnaPath.endsWith("rotation_quaternion")) {
|
||||
return Ipo.AC_QUAT_W + arrayIndex;
|
||||
}
|
||||
if (rnaPath.endsWith("scale")) {
|
||||
return Ipo.AC_SIZE_X + arrayIndex;
|
||||
}
|
||||
if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) {
|
||||
return Ipo.OB_ROT_X + arrayIndex;
|
||||
}
|
||||
LOGGER.log(Level.WARNING, "Unknown curve rna path: {0}", rnaPath);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method returns the actions for the given skeleton. The actions represent armature animation in blender.
|
||||
* @param skeleton
|
||||
* the skeleton we fetch the actions for
|
||||
* @param animationMatchMethod
|
||||
* the method of animation matching
|
||||
* @return a list of animations for the specified skeleton
|
||||
*/
|
||||
private List<BlenderAction> getActions(Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
|
||||
List<BlenderAction> result = new ArrayList<BlenderAction>();
|
||||
|
||||
// first get a set of bone names
|
||||
Set<String> boneNames = new HashSet<String>();
|
||||
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
|
||||
String boneName = skeleton.getBone(i).getName();
|
||||
if (boneName != null && boneName.length() > 0) {
|
||||
boneNames.add(skeleton.getBone(i).getName());
|
||||
}
|
||||
}
|
||||
|
||||
// finding matches
|
||||
Set<String> matchingNames = new HashSet<String>();
|
||||
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
|
||||
// compute how many action tracks match the skeleton bones' names
|
||||
for (String boneName : boneNames) {
|
||||
if (actionEntry.getValue().hasTrackName(boneName)) {
|
||||
matchingNames.add(boneName);
|
||||
}
|
||||
}
|
||||
|
||||
BlenderAction action = null;
|
||||
if (animationMatchMethod == AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH && matchingNames.size() > 0) {
|
||||
action = actionEntry.getValue();
|
||||
} else if (matchingNames.size() == actionEntry.getValue().getTracksCount()) {
|
||||
action = actionEntry.getValue();
|
||||
}
|
||||
|
||||
if (action != null) {
|
||||
// remove the tracks that do not match the bone names if the matching method is different from ALL_NAMES_MATCH
|
||||
if (animationMatchMethod != AnimationMatchMethod.ALL_NAMES_MATCH) {
|
||||
action = action.clone();
|
||||
action.removeTracksThatAreNotInTheCollection(matchingNames);
|
||||
}
|
||||
result.add(action);
|
||||
}
|
||||
|
||||
matchingNames.clear();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method returns the actions for the given node. The actions represent object animation in blender.
|
||||
* @param node
|
||||
* the node we fetch the actions for
|
||||
* @param animationMatchMethod
|
||||
* the method of animation matching
|
||||
* @return a list of animations for the specified node
|
||||
*/
|
||||
private List<BlenderAction> getActions(Node node, AnimationMatchMethod animationMatchMethod) {
|
||||
List<BlenderAction> result = new ArrayList<BlenderAction>();
|
||||
|
||||
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
|
||||
if (actionEntry.getValue().hasTrackName(node.getName())) {
|
||||
result.add(actionEntry.getValue());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.animations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.jme3.animation.BoneTrack;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.animation.SpatialTrack;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
|
||||
/**
|
||||
* An abstract representation of animation. The data stored here is mainly a
|
||||
* raw action data loaded from blender. It can later be transformed into
|
||||
* bone or spatial animation and applied to the specified node.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class BlenderAction implements Cloneable {
|
||||
/** The action name. */
|
||||
/* package */final String name;
|
||||
/** Animation speed - frames per second. */
|
||||
/* package */int fps;
|
||||
/**
|
||||
* The last frame of the animation (the last ipo curve node position is
|
||||
* used as a last frame).
|
||||
*/
|
||||
/* package */int stopFrame;
|
||||
/**
|
||||
* Tracks of the features. In case of bone animation the keys are the
|
||||
* names of the bones. In case of spatial animation - the node's name is
|
||||
* used. A single ipo contains all tracks for location, rotation and
|
||||
* scales.
|
||||
*/
|
||||
/* package */Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
|
||||
|
||||
public BlenderAction(String name, int fps) {
|
||||
this.name = name;
|
||||
this.fps = fps;
|
||||
}
|
||||
|
||||
public void removeTracksThatAreNotInTheCollection(Collection<String> trackNames) {
|
||||
Map<String, Ipo> newTracks = new HashMap<String, Ipo>();
|
||||
for (String trackName : trackNames) {
|
||||
if (featuresTracks.containsKey(trackName)) {
|
||||
newTracks.put(trackName, featuresTracks.get(trackName));
|
||||
}
|
||||
}
|
||||
featuresTracks = newTracks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlenderAction clone() {
|
||||
BlenderAction result = new BlenderAction(name, fps);
|
||||
result.stopFrame = stopFrame;
|
||||
result.featuresTracks = new HashMap<String, Ipo>(featuresTracks);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the action into JME spatial animation tracks.
|
||||
*
|
||||
* @param node
|
||||
* the node that will be animated
|
||||
* @return the spatial tracks for the node
|
||||
*/
|
||||
public SpatialTrack[] toTracks(Node node, BlenderContext blenderContext) {
|
||||
List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
|
||||
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
|
||||
tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, null, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
|
||||
}
|
||||
return tracks.toArray(new SpatialTrack[tracks.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the action into JME bone animation tracks.
|
||||
*
|
||||
* @param skeleton
|
||||
* the skeleton that will be animated
|
||||
* @return the bone tracks for the node
|
||||
*/
|
||||
public BoneTrack[] toTracks(Skeleton skeleton, BlenderContext blenderContext) {
|
||||
List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
|
||||
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
|
||||
int boneIndex = skeleton.getBoneIndex(entry.getKey());
|
||||
BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(boneIndex));
|
||||
tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, boneContext, boneContext.getBone().getBindPosition(), boneContext.getBone().getBindRotation(), boneContext.getBone().getBindScale(), 1, stopFrame, fps, false));
|
||||
}
|
||||
return tracks.toArray(new BoneTrack[tracks.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the action
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the time of animations (in seconds)
|
||||
*/
|
||||
public float getAnimationTime() {
|
||||
return (stopFrame - 1) / (float) fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current action has a track of a given name.
|
||||
* CAUTION! The names are case sensitive.
|
||||
*
|
||||
* @param name
|
||||
* the name of the track
|
||||
* @return <B>true</b> if the track of a given name exists for the
|
||||
* action and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean hasTrackName(String name) {
|
||||
return featuresTracks.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the amount of tracks in current action
|
||||
*/
|
||||
public int getTracksCount() {
|
||||
return featuresTracks.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BlenderTrack [name = " + name + "; tracks = [" + featuresTracks.keySet() + "]]";
|
||||
}
|
||||
}
|
@ -1,400 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.animations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
|
||||
/**
|
||||
* This class holds the basic data that describes a bone.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class BoneContext {
|
||||
// the flags of the bone
|
||||
public static final int SELECTED = 0x000001;
|
||||
public static final int CONNECTED_TO_PARENT = 0x000010;
|
||||
public static final int DEFORM = 0x001000;
|
||||
public static final int NO_LOCAL_LOCATION = 0x400000;
|
||||
public static final int NO_INHERIT_SCALE = 0x008000;
|
||||
public static final int NO_INHERIT_ROTATION = 0x000200;
|
||||
|
||||
/**
|
||||
* The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us).
|
||||
* So in order to have them loaded properly we need to transform their armature matrix (which blender sees as rotated) to make sure we get identical results.
|
||||
*/
|
||||
public static final Matrix4f BONE_ARMATURE_TRANSFORMATION_MATRIX = new Matrix4f(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
|
||||
|
||||
private static final int IKFLAG_LOCK_X = 0x01;
|
||||
private static final int IKFLAG_LOCK_Y = 0x02;
|
||||
private static final int IKFLAG_LOCK_Z = 0x04;
|
||||
private static final int IKFLAG_LIMIT_X = 0x08;
|
||||
private static final int IKFLAG_LIMIT_Y = 0x10;
|
||||
private static final int IKFLAG_LIMIT_Z = 0x20;
|
||||
|
||||
private BlenderContext blenderContext;
|
||||
/** The OMA of the bone's armature object. */
|
||||
private Long armatureObjectOMA;
|
||||
/** The OMA of the model that owns the bone's skeleton. */
|
||||
private Long skeletonOwnerOma;
|
||||
/** The structure of the bone. */
|
||||
private Structure boneStructure;
|
||||
/** Bone's name. */
|
||||
private String boneName;
|
||||
/** The bone's flag. */
|
||||
private int flag;
|
||||
/** The bone's matrix in world space. */
|
||||
private Matrix4f globalBoneMatrix;
|
||||
/** The bone's matrix in the model space. */
|
||||
private Matrix4f boneMatrixInModelSpace;
|
||||
/** The parent context. */
|
||||
private BoneContext parent;
|
||||
/** The children of this context. */
|
||||
private List<BoneContext> children = new ArrayList<BoneContext>();
|
||||
/** Created bone (available after calling 'buildBone' method). */
|
||||
private Bone bone;
|
||||
/** The length of the bone. */
|
||||
private float length;
|
||||
/** The bone's deform envelope. */
|
||||
private BoneEnvelope boneEnvelope;
|
||||
|
||||
// The below data is used only for IK constraint computations.
|
||||
|
||||
/** The bone's stretch value. */
|
||||
private float ikStretch;
|
||||
/** Bone's rotation minimum values. */
|
||||
private Vector3f limitMin;
|
||||
/** Bone's rotation maximum values. */
|
||||
private Vector3f limitMax;
|
||||
/** The bone's stiffness values (how much it rotates during IK computations. */
|
||||
private Vector3f stiffness;
|
||||
/** Values that indicate if any axis' rotation should be limited by some angle. */
|
||||
private boolean[] limits;
|
||||
/** Values that indicate if any axis' rotation should be disabled during IK computations. */
|
||||
private boolean[] locks;
|
||||
|
||||
/**
|
||||
* Constructor. Creates the basic set of bone's data.
|
||||
*
|
||||
* @param armatureObjectOMA
|
||||
* the OMA of the bone's armature object
|
||||
* @param boneStructure
|
||||
* the bone's structure
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problem with blender data reading
|
||||
* occurs
|
||||
*/
|
||||
public BoneContext(Long armatureObjectOMA, Structure boneStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
this(boneStructure, armatureObjectOMA, null, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Creates the basic set of bone's data.
|
||||
*
|
||||
* @param boneStructure
|
||||
* the bone's structure
|
||||
* @param armatureObjectOMA
|
||||
* the OMA of the bone's armature object
|
||||
* @param parent
|
||||
* bone's parent (null if the bone is the root bone)
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problem with blender data reading
|
||||
* occurs
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException {
|
||||
this.parent = parent;
|
||||
this.blenderContext = blenderContext;
|
||||
this.boneStructure = boneStructure;
|
||||
this.armatureObjectOMA = armatureObjectOMA;
|
||||
boneName = boneStructure.getFieldValue("name").toString();
|
||||
flag = ((Number) boneStructure.getFieldValue("flag")).intValue();
|
||||
length = ((Number) boneStructure.getFieldValue("length")).floatValue();
|
||||
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
||||
|
||||
// first get the bone matrix in its armature space
|
||||
globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
// then make sure it is rotated in a proper way to fit the jme bone transformation conventions
|
||||
globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX);
|
||||
}
|
||||
|
||||
Structure armatureStructure = blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext);
|
||||
Spatial armature = (Spatial) objectHelper.toObject(armatureStructure, blenderContext);
|
||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||
Matrix4f armatureWorldMatrix = constraintHelper.toMatrix(armature.getWorldTransform(), new Matrix4f());
|
||||
|
||||
// and now compute the final bone matrix in world space
|
||||
globalBoneMatrix = armatureWorldMatrix.mult(globalBoneMatrix);
|
||||
|
||||
// load the bone deformation envelope if necessary
|
||||
if ((flag & DEFORM) == 0) {// if the flag is NOT set then the DEFORM is in use
|
||||
boneEnvelope = new BoneEnvelope(boneStructure, armatureWorldMatrix, blenderContext.getBlenderKey().isFixUpAxis());
|
||||
}
|
||||
|
||||
// load bone's pose channel data
|
||||
Pointer pPose = (Pointer) armatureStructure.getFieldValue("pose");
|
||||
if (pPose != null && pPose.isNotNull()) {
|
||||
List<Structure> poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase();
|
||||
for (Structure poseChannel : poseChannels) {
|
||||
Long boneOMA = ((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress();
|
||||
if (boneOMA.equals(this.boneStructure.getOldMemoryAddress())) {
|
||||
ikStretch = ((Number) poseChannel.getFieldValue("ikstretch")).floatValue();
|
||||
DynamicArray<Number> limitMin = (DynamicArray<Number>) poseChannel.getFieldValue("limitmin");
|
||||
this.limitMin = new Vector3f(limitMin.get(0).floatValue(), limitMin.get(1).floatValue(), limitMin.get(2).floatValue());
|
||||
|
||||
DynamicArray<Number> limitMax = (DynamicArray<Number>) poseChannel.getFieldValue("limitmax");
|
||||
this.limitMax = new Vector3f(limitMax.get(0).floatValue(), limitMax.get(1).floatValue(), limitMax.get(2).floatValue());
|
||||
|
||||
DynamicArray<Number> stiffness = (DynamicArray<Number>) poseChannel.getFieldValue("stiffness");
|
||||
this.stiffness = new Vector3f(stiffness.get(0).floatValue(), stiffness.get(1).floatValue(), stiffness.get(2).floatValue());
|
||||
|
||||
int ikFlag = ((Number) poseChannel.getFieldValue("ikflag")).intValue();
|
||||
locks = new boolean[] { (ikFlag & IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LOCK_Z) != 0 };
|
||||
// limits are enabled when locks are disabled, so we ween to take that into account here
|
||||
limits = new boolean[] { (ikFlag & IKFLAG_LIMIT_X & ~IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LIMIT_Y & ~IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LIMIT_Z & ~IKFLAG_LOCK_Z) != 0 };
|
||||
break;// we have found what we need, no need to search further
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create the children
|
||||
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase();
|
||||
for (Structure child : childbase) {
|
||||
children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext));
|
||||
}
|
||||
|
||||
blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method builds the bone. It recursively builds the bone's children.
|
||||
*
|
||||
* @param bones
|
||||
* a list of bones where the newly created bone will be added
|
||||
* @param skeletonOwnerOma
|
||||
* the spatial of the object that will own the skeleton
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return newly created bone
|
||||
*/
|
||||
public Bone buildBone(List<Bone> bones, Long skeletonOwnerOma, BlenderContext blenderContext) {
|
||||
this.skeletonOwnerOma = skeletonOwnerOma;
|
||||
Long boneOMA = boneStructure.getOldMemoryAddress();
|
||||
bone = new Bone(boneName);
|
||||
bones.add(bone);
|
||||
blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.STRUCTURE, boneStructure);
|
||||
blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.FEATURE, bone);
|
||||
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
||||
|
||||
Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedDataType.STRUCTURE);
|
||||
// I could load 'imat' here, but apparently in some older blenders there were bugs or unfinished functionalities that stored ZERO matrix in imat field
|
||||
// loading 'obmat' and inverting it makes us avoid errors in such cases
|
||||
Matrix4f invertedObjectOwnerGlobalMatrix = objectHelper.getMatrix(skeletonOwnerObjectStructure, "obmat", blenderContext.getBlenderKey().isFixUpAxis()).invertLocal();
|
||||
if (objectHelper.isParent(skeletonOwnerOma, armatureObjectOMA)) {
|
||||
boneMatrixInModelSpace = globalBoneMatrix.mult(invertedObjectOwnerGlobalMatrix);
|
||||
} else {
|
||||
boneMatrixInModelSpace = invertedObjectOwnerGlobalMatrix.mult(globalBoneMatrix);
|
||||
}
|
||||
|
||||
Matrix4f boneLocalMatrix = parent == null ? boneMatrixInModelSpace : parent.boneMatrixInModelSpace.invert().multLocal(boneMatrixInModelSpace);
|
||||
|
||||
Vector3f poseLocation = parent == null || !this.is(CONNECTED_TO_PARENT) ? boneLocalMatrix.toTranslationVector() : new Vector3f(0, parent.length, 0);
|
||||
Quaternion rotation = boneLocalMatrix.toRotationQuat().normalizeLocal();
|
||||
Vector3f scale = boneLocalMatrix.toScaleVector();
|
||||
|
||||
bone.setBindTransforms(poseLocation, rotation, scale);
|
||||
for (BoneContext child : children) {
|
||||
bone.addChild(child.buildBone(bones, skeletonOwnerOma, blenderContext));
|
||||
}
|
||||
|
||||
return bone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return built bone (available after calling 'buildBone' method)
|
||||
*/
|
||||
public Bone getBone() {
|
||||
return bone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the old memory address of the bone
|
||||
*/
|
||||
public Long getBoneOma() {
|
||||
return boneStructure.getOldMemoryAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* The method returns the length of the bone.
|
||||
* If you want to use it for bone debugger take model space scale into account and do
|
||||
* something like this:
|
||||
* <b>boneContext.getLength() * boneContext.getBone().getModelSpaceScale().y</b>.
|
||||
* Otherwise the bones might not look as they should in the bone debugger.
|
||||
* @return the length of the bone
|
||||
*/
|
||||
public float getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OMA of the bone's armature object
|
||||
*/
|
||||
public Long getArmatureObjectOMA() {
|
||||
return armatureObjectOMA;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OMA of the model that owns the bone's skeleton
|
||||
*/
|
||||
public Long getSkeletonOwnerOma() {
|
||||
return skeletonOwnerOma;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the skeleton the bone of this context belongs to
|
||||
*/
|
||||
public Skeleton getSkeleton() {
|
||||
return blenderContext.getSkeleton(armatureObjectOMA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the initial bone's matrix in model space
|
||||
*/
|
||||
public Matrix4f getBoneMatrixInModelSpace() {
|
||||
return boneMatrixInModelSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vertex assigning envelope of the bone
|
||||
*/
|
||||
public BoneEnvelope getBoneEnvelope() {
|
||||
return boneEnvelope;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bone's stretch factor
|
||||
*/
|
||||
public float getIkStretch() {
|
||||
return ikStretch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the X rotation should be limited
|
||||
*/
|
||||
public boolean isLimitX() {
|
||||
return limits != null ? limits[0] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the Y rotation should be limited
|
||||
*/
|
||||
public boolean isLimitY() {
|
||||
return limits != null ? limits[1] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the Z rotation should be limited
|
||||
*/
|
||||
public boolean isLimitZ() {
|
||||
return limits != null ? limits[2] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the X rotation should be disabled
|
||||
*/
|
||||
public boolean isLockX() {
|
||||
return locks != null ? locks[0] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the Y rotation should be disabled
|
||||
*/
|
||||
public boolean isLockY() {
|
||||
return locks != null ? locks[1] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the Z rotation should be disabled
|
||||
*/
|
||||
public boolean isLockZ() {
|
||||
return locks != null ? locks[2] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minimum values in rotation limitation (if limitation is enabled for specific axis).
|
||||
*/
|
||||
public Vector3f getLimitMin() {
|
||||
return limitMin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum values in rotation limitation (if limitation is enabled for specific axis).
|
||||
*/
|
||||
public Vector3f getLimitMax() {
|
||||
return limitMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stiffness of the bone
|
||||
*/
|
||||
public Vector3f getStiffness() {
|
||||
return stiffness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the bone is of specified property defined by its flag.
|
||||
* @param flagMask
|
||||
* the mask of the flag (constants defined in this class)
|
||||
* @return <b>true</b> if the bone IS of specified proeprty and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean is(int flagMask) {
|
||||
return (flag & flagMask) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the root bone context of this bone context
|
||||
*/
|
||||
public BoneContext getRoot() {
|
||||
BoneContext result = this;
|
||||
while (result.parent != null) {
|
||||
result = result.parent;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a number of bones from this bone to its root
|
||||
*/
|
||||
public int getDistanceFromRoot() {
|
||||
int result = 0;
|
||||
BoneContext boneContext = this;
|
||||
while (boneContext.parent != null) {
|
||||
boneContext = boneContext.parent;
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BoneContext: " + boneName;
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.animations;
|
||||
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* An implementation of bone envelope. Used when assigning bones to the mesh by envelopes.
|
||||
*
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class BoneEnvelope {
|
||||
/** A defined distance that will be included in the envelope space. */
|
||||
private float distance;
|
||||
/** The bone's weight. */
|
||||
private float weight;
|
||||
/** The radius of the bone's head. */
|
||||
private float boneHeadRadius;
|
||||
/** The radius of the bone's tail. */
|
||||
private float boneTailRadius;
|
||||
/** Head position in rest pose in world space. */
|
||||
private Vector3f head;
|
||||
/** Tail position in rest pose in world space. */
|
||||
private Vector3f tail;
|
||||
|
||||
/**
|
||||
* The constructor of bone envelope. It reads all the needed data. Take notice that the positions of head and tail
|
||||
* are computed in the world space and that the points' positions given for computations should be in world space as well.
|
||||
*
|
||||
* @param boneStructure
|
||||
* the blender bone structure
|
||||
* @param armatureWorldMatrix
|
||||
* the world matrix of the armature object
|
||||
* @param fixUpAxis
|
||||
* a variable that tells if we use the Y-is up axis orientation
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public BoneEnvelope(Structure boneStructure, Matrix4f armatureWorldMatrix, boolean fixUpAxis) {
|
||||
distance = ((Number) boneStructure.getFieldValue("dist")).floatValue();
|
||||
weight = ((Number) boneStructure.getFieldValue("weight")).floatValue();
|
||||
boneHeadRadius = ((Number) boneStructure.getFieldValue("rad_head")).floatValue();
|
||||
boneTailRadius = ((Number) boneStructure.getFieldValue("rad_tail")).floatValue();
|
||||
|
||||
DynamicArray<Number> headArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_head");
|
||||
head = new Vector3f(headArray.get(0).floatValue(), headArray.get(1).floatValue(), headArray.get(2).floatValue());
|
||||
if (fixUpAxis) {
|
||||
float z = head.z;
|
||||
head.z = -head.y;
|
||||
head.y = z;
|
||||
}
|
||||
armatureWorldMatrix.mult(head, head);// move the head point to global space
|
||||
|
||||
DynamicArray<Number> tailArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_tail");
|
||||
tail = new Vector3f(tailArray.get(0).floatValue(), tailArray.get(1).floatValue(), tailArray.get(2).floatValue());
|
||||
if (fixUpAxis) {
|
||||
float z = tail.z;
|
||||
tail.z = -tail.y;
|
||||
tail.y = z;
|
||||
}
|
||||
armatureWorldMatrix.mult(tail, tail);// move the tail point to global space
|
||||
}
|
||||
|
||||
/**
|
||||
* The method verifies if the given point is inside the envelope.
|
||||
* @param point
|
||||
* the point in 3D space (MUST be in a world coordinate space)
|
||||
* @return <b>true</b> if the point is inside the envelope and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isInEnvelope(Vector3f point) {
|
||||
Vector3f v = tail.subtract(head);
|
||||
float boneLength = v.length();
|
||||
v.normalizeLocal();
|
||||
|
||||
// computing a plane that contains 'point' and v is its normal vector
|
||||
// the plane's equation is: Ax + By + Cz + D = 0, where v = [A, B, C]
|
||||
float D = -v.dot(point);
|
||||
|
||||
// computing a point where a line that contains head and tail crosses the plane
|
||||
float temp = -(v.dot(head) + D) / v.dot(v);
|
||||
Vector3f p = head.add(v.x * temp, v.y * temp, v.z * temp);
|
||||
|
||||
// determining if the point p is on the same or other side of head than the tail point
|
||||
Vector3f headToPointOnLineVector = p.subtract(head);
|
||||
float headToPointLength = headToPointOnLineVector.length();
|
||||
float cosinus = headToPointOnLineVector.dot(v) / headToPointLength;// the length of v is already = 1; cosinus should be either 1, 0 or -1
|
||||
if (cosinus < 0 && headToPointLength > boneHeadRadius || headToPointLength > boneLength + boneTailRadius) {
|
||||
return false;// the point is outside the anvelope
|
||||
}
|
||||
|
||||
// now check if the point is inside and envelope
|
||||
float pointDistanceFromLine = point.subtract(p).length(), maximumDistance = 0;
|
||||
if (cosinus < 0) {
|
||||
// checking if the distance from p to point is inside the half sphere defined by head envelope
|
||||
// compute the distance from the line to the half sphere border
|
||||
maximumDistance = boneHeadRadius;
|
||||
} else if (headToPointLength < boneLength) {
|
||||
// compute the maximum available distance
|
||||
if (boneTailRadius > boneHeadRadius) {
|
||||
// compute the distance from head to p
|
||||
float headToPDistance = p.subtract(head).length();
|
||||
// from tangens function we have
|
||||
float x = headToPDistance * ((boneTailRadius - boneHeadRadius) / boneLength);
|
||||
maximumDistance = x + boneHeadRadius;
|
||||
} else if (boneTailRadius < boneHeadRadius) {
|
||||
// compute the distance from head to p
|
||||
float tailToPDistance = p.subtract(tail).length();
|
||||
// from tangens function we have
|
||||
float x = tailToPDistance * ((boneHeadRadius - boneTailRadius) / boneLength);
|
||||
maximumDistance = x + boneTailRadius;
|
||||
} else {
|
||||
maximumDistance = boneTailRadius;
|
||||
}
|
||||
} else {
|
||||
// checking if the distance from p to point is inside the half sphere defined by tail envelope
|
||||
maximumDistance = boneTailRadius;
|
||||
}
|
||||
|
||||
return pointDistanceFromLine <= maximumDistance + distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the weight of the bone
|
||||
*/
|
||||
public float getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BoneEnvelope [d=" + distance + ", w=" + weight + ", hr=" + boneHeadRadius + ", tr=" + boneTailRadius + ", (" + head + ") -> (" + tail + ")]";
|
||||
}
|
||||
}
|
@ -1,317 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.animations;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.animation.BoneTrack;
|
||||
import com.jme3.animation.SpatialTrack;
|
||||
import com.jme3.animation.Track;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.curves.BezierCurve;
|
||||
|
||||
/**
|
||||
* This class is used to calculate bezier curves value for the given frames. The
|
||||
* Ipo (interpolation object) consists of several b-spline curves (connected 3rd
|
||||
* degree bezier curves) of a different type.
|
||||
*
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class Ipo {
|
||||
private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName());
|
||||
|
||||
public static final int AC_LOC_X = 1;
|
||||
public static final int AC_LOC_Y = 2;
|
||||
public static final int AC_LOC_Z = 3;
|
||||
public static final int OB_ROT_X = 7;
|
||||
public static final int OB_ROT_Y = 8;
|
||||
public static final int OB_ROT_Z = 9;
|
||||
public static final int AC_SIZE_X = 13;
|
||||
public static final int AC_SIZE_Y = 14;
|
||||
public static final int AC_SIZE_Z = 15;
|
||||
public static final int AC_QUAT_W = 25;
|
||||
public static final int AC_QUAT_X = 26;
|
||||
public static final int AC_QUAT_Y = 27;
|
||||
public static final int AC_QUAT_Z = 28;
|
||||
|
||||
/** A list of bezier curves for this interpolation object. */
|
||||
private BezierCurve[] bezierCurves;
|
||||
/** Each ipo contains one bone track. */
|
||||
private Track calculatedTrack;
|
||||
/** This variable indicates if the Y asxis is the UP axis or not. */
|
||||
protected boolean fixUpAxis;
|
||||
/**
|
||||
* Depending on the blender version rotations are stored in degrees or
|
||||
* radians so we need to know the version that is used.
|
||||
*/
|
||||
protected final int blenderVersion;
|
||||
|
||||
/**
|
||||
* Constructor. Stores the bezier curves.
|
||||
*
|
||||
* @param bezierCurves
|
||||
* a table of bezier curves
|
||||
* @param fixUpAxis
|
||||
* indicates if the Y is the up axis or not
|
||||
* @param blenderVersion
|
||||
* the blender version that is currently used
|
||||
*/
|
||||
public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) {
|
||||
this.bezierCurves = bezierCurves;
|
||||
this.fixUpAxis = fixUpAxis;
|
||||
this.blenderVersion = blenderVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the ipo value for the first curve.
|
||||
*
|
||||
* @param frame
|
||||
* the frame for which the value is calculated
|
||||
* @return calculated ipo value
|
||||
*/
|
||||
public double calculateValue(int frame) {
|
||||
return this.calculateValue(frame, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the ipo value for the curve of the specified
|
||||
* index. Make sure you do not exceed the curves amount. Alway chech the
|
||||
* amount of curves before calling this method.
|
||||
*
|
||||
* @param frame
|
||||
* the frame for which the value is calculated
|
||||
* @param curveIndex
|
||||
* the index of the curve
|
||||
* @return calculated ipo value
|
||||
*/
|
||||
public double calculateValue(int frame, int curveIndex) {
|
||||
return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the frame where last bezier triple center point of
|
||||
* the specified bezier curve is located.
|
||||
*
|
||||
* @return the frame number of the last defined bezier triple point for the
|
||||
* specified ipo
|
||||
*/
|
||||
public int getLastFrame() {
|
||||
int result = 1;
|
||||
for (int i = 0; i < bezierCurves.length; ++i) {
|
||||
int tempResult = bezierCurves[i].getLastFrame();
|
||||
if (tempResult > result) {
|
||||
result = tempResult;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method calculates the value of the curves as a bone track between
|
||||
* the specified frames.
|
||||
*
|
||||
* @param targetIndex
|
||||
* the index of the target for which the method calculates the
|
||||
* tracks IMPORTANT! Aet to -1 (or any negative number) if you
|
||||
* want to load spatial animation.
|
||||
* @param localTranslation
|
||||
* the local translation of the object/bone that will be animated by
|
||||
* the track
|
||||
* @param localRotation
|
||||
* the local rotation of the object/bone that will be animated by
|
||||
* the track
|
||||
* @param localScale
|
||||
* the local scale of the object/bone that will be animated by
|
||||
* the track
|
||||
* @param startFrame
|
||||
* the first frame of tracks (inclusive)
|
||||
* @param stopFrame
|
||||
* the last frame of the tracks (inclusive)
|
||||
* @param fps
|
||||
* frame rate (frames per second)
|
||||
* @param spatialTrack
|
||||
* this flag indicates if the track belongs to a spatial or to a
|
||||
* bone; the difference is important because it appears that bones
|
||||
* in blender have the same type of coordinate system (Y as UP)
|
||||
* as jme while other features have different one (Z is UP)
|
||||
* @return bone track for the specified bone
|
||||
*/
|
||||
public Track calculateTrack(int targetIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
|
||||
if (calculatedTrack == null) {
|
||||
// preparing data for track
|
||||
int framesAmount = stopFrame - startFrame;
|
||||
float timeBetweenFrames = 1.0f / fps;
|
||||
|
||||
float[] times = new float[framesAmount + 1];
|
||||
Vector3f[] translations = new Vector3f[framesAmount + 1];
|
||||
float[] translation = new float[3];
|
||||
Quaternion[] rotations = new Quaternion[framesAmount + 1];
|
||||
float[] quaternionRotation = new float[] { localRotation.getX(), localRotation.getY(), localRotation.getZ(), localRotation.getW(), };
|
||||
float[] eulerRotation = localRotation.toAngles(null);
|
||||
Vector3f[] scales = new Vector3f[framesAmount + 1];
|
||||
float[] scale = new float[] { localScale.x, localScale.y, localScale.z };
|
||||
float degreeToRadiansFactor = 1;
|
||||
if (blenderVersion < 250) {// in blender earlier than 2.50 the values are stored in degrees
|
||||
degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here
|
||||
}
|
||||
int yIndex = 1, zIndex = 2;
|
||||
boolean swapAxes = spatialTrack && fixUpAxis;
|
||||
if (swapAxes) {
|
||||
yIndex = 2;
|
||||
zIndex = 1;
|
||||
}
|
||||
boolean eulerRotationUsed = false, queternionRotationUsed = false;
|
||||
|
||||
// calculating track data
|
||||
for (int frame = startFrame; frame <= stopFrame; ++frame) {
|
||||
boolean translationSet = false;
|
||||
translation[0] = translation[1] = translation[2] = 0;
|
||||
int index = frame - startFrame;
|
||||
times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames;
|
||||
for (int j = 0; j < bezierCurves.length; ++j) {
|
||||
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
|
||||
switch (bezierCurves[j].getType()) {
|
||||
// LOCATION
|
||||
case AC_LOC_X:
|
||||
translation[0] = (float) value;
|
||||
translationSet = true;
|
||||
break;
|
||||
case AC_LOC_Y:
|
||||
if (swapAxes && value != 0) {
|
||||
value = -value;
|
||||
}
|
||||
translation[yIndex] = (float) value;
|
||||
translationSet = true;
|
||||
break;
|
||||
case AC_LOC_Z:
|
||||
translation[zIndex] = (float) value;
|
||||
translationSet = true;
|
||||
break;
|
||||
|
||||
// EULER ROTATION
|
||||
case OB_ROT_X:
|
||||
eulerRotationUsed = true;
|
||||
eulerRotation[0] = (float) value * degreeToRadiansFactor;
|
||||
break;
|
||||
case OB_ROT_Y:
|
||||
eulerRotationUsed = true;
|
||||
if (swapAxes && value != 0) {
|
||||
value = -value;
|
||||
}
|
||||
eulerRotation[yIndex] = (float) value * degreeToRadiansFactor;
|
||||
break;
|
||||
case OB_ROT_Z:
|
||||
eulerRotationUsed = true;
|
||||
eulerRotation[zIndex] = (float) value * degreeToRadiansFactor;
|
||||
break;
|
||||
|
||||
// SIZE
|
||||
case AC_SIZE_X:
|
||||
scale[0] = (float) value;
|
||||
break;
|
||||
case AC_SIZE_Y:
|
||||
scale[yIndex] = (float) value;
|
||||
break;
|
||||
case AC_SIZE_Z:
|
||||
scale[zIndex] = (float) value;
|
||||
break;
|
||||
|
||||
// QUATERNION ROTATION (used with bone animation)
|
||||
case AC_QUAT_W:
|
||||
queternionRotationUsed = true;
|
||||
quaternionRotation[3] = (float) value;
|
||||
break;
|
||||
case AC_QUAT_X:
|
||||
queternionRotationUsed = true;
|
||||
quaternionRotation[0] = (float) value;
|
||||
break;
|
||||
case AC_QUAT_Y:
|
||||
queternionRotationUsed = true;
|
||||
if (swapAxes && value != 0) {
|
||||
value = -value;
|
||||
}
|
||||
quaternionRotation[yIndex] = (float) value;
|
||||
break;
|
||||
case AC_QUAT_Z:
|
||||
quaternionRotation[zIndex] = (float) value;
|
||||
break;
|
||||
default:
|
||||
LOGGER.log(Level.WARNING, "Unknown ipo curve type: {0}.", bezierCurves[j].getType());
|
||||
}
|
||||
}
|
||||
if(translationSet) {
|
||||
translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
|
||||
} else {
|
||||
translations[index] = new Vector3f();
|
||||
}
|
||||
|
||||
if(boneContext != null) {
|
||||
if(boneContext.getBone().getParent() == null && boneContext.is(BoneContext.NO_LOCAL_LOCATION)) {
|
||||
float temp = translations[index].z;
|
||||
translations[index].z = -translations[index].y;
|
||||
translations[index].y = temp;
|
||||
}
|
||||
}
|
||||
|
||||
if (queternionRotationUsed) {
|
||||
rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
|
||||
} else {
|
||||
rotations[index] = new Quaternion().fromAngles(eulerRotation);
|
||||
}
|
||||
|
||||
scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
|
||||
}
|
||||
if (spatialTrack) {
|
||||
calculatedTrack = new SpatialTrack(times, translations, rotations, scales);
|
||||
} else {
|
||||
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
|
||||
}
|
||||
|
||||
if (queternionRotationUsed && eulerRotationUsed) {
|
||||
LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!");
|
||||
}
|
||||
}
|
||||
|
||||
return calculatedTrack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ipo constant curve. This is a curve with only one value and no specified
|
||||
* type. This type of ipo cannot be used to calculate tracks. It should only
|
||||
* be used to calculate single value for a given frame.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */static class ConstIpo extends Ipo {
|
||||
|
||||
/** The constant value of this ipo. */
|
||||
private float constValue;
|
||||
|
||||
/**
|
||||
* Constructor. Stores the constant value of this ipo.
|
||||
*
|
||||
* @param constValue
|
||||
* the constant value of this ipo
|
||||
*/
|
||||
public ConstIpo(float constValue) {
|
||||
super(null, false, 0);// the version is not important here
|
||||
this.constValue = constValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double calculateValue(int frame) {
|
||||
return constValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double calculateValue(int frame, int curveIndex) {
|
||||
return constValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoneTrack calculateTrack(int boneIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
|
||||
throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.cameras;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* A class that is used to load cameras into the scene.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class CameraHelper extends AbstractBlenderHelper {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName());
|
||||
protected static final int DEFAULT_CAM_WIDTH = 640;
|
||||
protected static final int DEFAULT_CAM_HEIGHT = 480;
|
||||
|
||||
/**
|
||||
* This constructor parses the given blender version and stores the result. Some functionalities may differ in
|
||||
* different blender versions.
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public CameraHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the given structure to jme camera.
|
||||
*
|
||||
* @param structure
|
||||
* camera structure
|
||||
* @return jme camera object
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when there are problems with the
|
||||
* blender file
|
||||
*/
|
||||
public Camera toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
if (blenderVersion >= 250) {
|
||||
return this.toCamera250(structure, blenderContext.getSceneStructure());
|
||||
} else {
|
||||
return this.toCamera249(structure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the given structure to jme camera. Should be used form blender 2.5+.
|
||||
*
|
||||
* @param structure
|
||||
* camera structure
|
||||
* @param sceneStructure
|
||||
* scene structure
|
||||
* @return jme camera object
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when there are problems with the
|
||||
* blender file
|
||||
*/
|
||||
private Camera toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
|
||||
int width = DEFAULT_CAM_WIDTH;
|
||||
int height = DEFAULT_CAM_HEIGHT;
|
||||
if (sceneStructure != null) {
|
||||
Structure renderData = (Structure) sceneStructure.getFieldValue("r");
|
||||
width = ((Number) renderData.getFieldValue("xsch")).shortValue();
|
||||
height = ((Number) renderData.getFieldValue("ysch")).shortValue();
|
||||
}
|
||||
Camera camera = new Camera(width, height);
|
||||
int type = ((Number) structure.getFieldValue("type")).intValue();
|
||||
if (type != 0 && type != 1) {
|
||||
LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);
|
||||
type = 0;
|
||||
}
|
||||
// type==0 - perspective; type==1 - orthographic; perspective is used as default
|
||||
camera.setParallelProjection(type == 1);
|
||||
float aspect = width / (float) height;
|
||||
float fovY; // Vertical field of view in degrees
|
||||
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
|
||||
float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();
|
||||
if (type == 0) {
|
||||
// Convert lens MM to vertical degrees in fovY, see Blender rna_Camera_angle_get()
|
||||
// Default sensor size prior to 2.60 was 32.
|
||||
float sensor = 32.0f;
|
||||
boolean sensorVertical = false;
|
||||
Number sensorFit = (Number) structure.getFieldValue("sensor_fit");
|
||||
if (sensorFit != null) {
|
||||
// If sensor_fit is vert (2), then sensor_y is used
|
||||
sensorVertical = sensorFit.byteValue() == 2;
|
||||
String sensorName = "sensor_x";
|
||||
if (sensorVertical) {
|
||||
sensorName = "sensor_y";
|
||||
}
|
||||
sensor = ((Number) structure.getFieldValue(sensorName)).floatValue();
|
||||
}
|
||||
float focalLength = ((Number) structure.getFieldValue("lens")).floatValue();
|
||||
float fov = 2.0f * FastMath.atan(sensor / 2.0f / focalLength);
|
||||
if (sensorVertical) {
|
||||
fovY = fov * FastMath.RAD_TO_DEG;
|
||||
} else {
|
||||
// Convert fov from horizontal to vertical
|
||||
fovY = 2.0f * FastMath.atan(FastMath.tan(fov / 2.0f) / aspect) * FastMath.RAD_TO_DEG;
|
||||
}
|
||||
} else {
|
||||
// This probably is not correct.
|
||||
fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
|
||||
}
|
||||
camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);
|
||||
camera.setName(structure.getName());
|
||||
return camera;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the given structure to jme camera. Should be used form blender 2.49.
|
||||
*
|
||||
* @param structure
|
||||
* camera structure
|
||||
* @return jme camera object
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when there are problems with the
|
||||
* blender file
|
||||
*/
|
||||
private Camera toCamera249(Structure structure) throws BlenderFileException {
|
||||
Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
|
||||
int type = ((Number) structure.getFieldValue("type")).intValue();
|
||||
if (type != 0 && type != 1) {
|
||||
LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);
|
||||
type = 0;
|
||||
}
|
||||
// type==0 - perspective; type==1 - orthographic; perspective is used as default
|
||||
camera.setParallelProjection(type == 1);
|
||||
float aspect = 0;
|
||||
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
|
||||
float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();
|
||||
if (type == 0) {
|
||||
aspect = ((Number) structure.getFieldValue("lens")).floatValue();
|
||||
} else {
|
||||
aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
|
||||
}
|
||||
camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend);
|
||||
camera.setName(structure.getName());
|
||||
return camera;
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.animations.Ipo;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
|
||||
/**
|
||||
* Constraint applied on the bone.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class BoneConstraint extends Constraint {
|
||||
private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName());
|
||||
|
||||
/**
|
||||
* The bone constraint constructor.
|
||||
*
|
||||
* @param constraintStructure
|
||||
* the constraint's structure
|
||||
* @param ownerOMA
|
||||
* the OMA of the bone that owns the constraint
|
||||
* @param influenceIpo
|
||||
* the influence interpolation curve
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* exception thrown when problems with blender file occur
|
||||
*/
|
||||
public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
|
||||
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate() {
|
||||
if (targetOMA != null) {
|
||||
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
|
||||
if (nodeTarget == null) {
|
||||
LOGGER.log(Level.WARNING, "Cannot find target for constraint: {0}.", name);
|
||||
return false;
|
||||
}
|
||||
// the second part of the if expression verifies if the found node
|
||||
// (if any) is an armature node
|
||||
if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) {
|
||||
if (subtargetName.trim().isEmpty()) {
|
||||
LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name);
|
||||
return false;
|
||||
}
|
||||
// if the target is not an object node then it is an Armature,
|
||||
// so make sure the bone is in the current skeleton
|
||||
BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
|
||||
if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) {
|
||||
LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(int frame) {
|
||||
super.apply(frame);
|
||||
blenderContext.getBoneContext(ownerOMA).getBone().updateModelTransforms();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTargetOMA() {
|
||||
if(targetOMA != null && subtargetName != null && !subtargetName.trim().isEmpty()) {
|
||||
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
|
||||
if(nodeTarget != null) {
|
||||
if(blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) {
|
||||
BoneContext boneContext = blenderContext.getBoneByName(targetOMA, subtargetName);
|
||||
return boneContext != null ? boneContext.getBoneOma() : 0L;
|
||||
}
|
||||
return targetOMA;
|
||||
}
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.Ipo;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinition;
|
||||
import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinitionFactory;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* The implementation of a constraint.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public abstract class Constraint {
|
||||
private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName());
|
||||
|
||||
/** The name of this constraint. */
|
||||
protected final String name;
|
||||
/** Indicates if the constraint is already baked or not. */
|
||||
protected boolean baked;
|
||||
|
||||
protected Space ownerSpace;
|
||||
protected final ConstraintDefinition constraintDefinition;
|
||||
protected Long ownerOMA;
|
||||
|
||||
protected Long targetOMA;
|
||||
protected Space targetSpace;
|
||||
protected String subtargetName;
|
||||
|
||||
/** The ipo object defining influence. */
|
||||
protected final Ipo ipo;
|
||||
/** The blender context. */
|
||||
protected final BlenderContext blenderContext;
|
||||
protected final ConstraintHelper constraintHelper;
|
||||
|
||||
/**
|
||||
* This constructor creates the constraint instance.
|
||||
*
|
||||
* @param constraintStructure
|
||||
* the constraint's structure (bConstraint clss in blender 2.49).
|
||||
* @param ownerOMA
|
||||
* the old memory address of the constraint owner
|
||||
* @param influenceIpo
|
||||
* the ipo curve of the influence factor
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blender file is somehow
|
||||
* corrupted
|
||||
*/
|
||||
public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
|
||||
this.blenderContext = blenderContext;
|
||||
name = constraintStructure.getFieldValue("name").toString();
|
||||
Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
|
||||
if (pData.isNotNull()) {
|
||||
Structure data = pData.fetchData().get(0);
|
||||
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, name, ownerOMA, blenderContext);
|
||||
Pointer pTar = (Pointer) data.getFieldValue("tar");
|
||||
if (pTar != null && pTar.isNotNull()) {
|
||||
targetOMA = pTar.getOldMemoryAddress();
|
||||
targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());
|
||||
Object subtargetValue = data.getFieldValue("subtarget");
|
||||
if (subtargetValue != null) {// not all constraint data have the
|
||||
// subtarget field
|
||||
subtargetName = subtargetValue.toString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Null constraint has no data, so create it here
|
||||
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, name, null, blenderContext);
|
||||
}
|
||||
ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
|
||||
ipo = influenceIpo;
|
||||
this.ownerOMA = ownerOMA;
|
||||
constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||
LOGGER.log(Level.INFO, "Created constraint: {0} with definition: {1}", new Object[] { name, constraintDefinition });
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if the constraint is implemented and <b>false</b>
|
||||
* otherwise
|
||||
*/
|
||||
public boolean isImplemented() {
|
||||
return constraintDefinition == null ? true : constraintDefinition.isImplemented();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the constraint type, similar to the constraint name
|
||||
* used in Blender
|
||||
*/
|
||||
public String getConstraintTypeName() {
|
||||
return constraintDefinition.getConstraintTypeName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OMAs of the features whose transform had been altered beside the constraint owner
|
||||
*/
|
||||
public Set<Long> getAlteredOmas() {
|
||||
return constraintDefinition.getAlteredOmas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs validation before baking. Checks factors that can prevent
|
||||
* constraint from baking that could not be checked during constraint
|
||||
* loading.
|
||||
*/
|
||||
public abstract boolean validate();
|
||||
|
||||
/**
|
||||
* @return the OMA of the target or 0 if no target is specified for the constraint
|
||||
*/
|
||||
public abstract Long getTargetOMA();
|
||||
|
||||
/**
|
||||
* Applies the constraint to owner (and in some cases can alter other bones of the skeleton).
|
||||
* @param frame
|
||||
* the frame of the animation
|
||||
*/
|
||||
public void apply(int frame) {
|
||||
if (LOGGER.isLoggable(Level.FINEST)) {
|
||||
LOGGER.log(Level.FINEST, "Applying constraint: {0} for frame {1}", new Object[] { name, frame });
|
||||
}
|
||||
Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
|
||||
constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, (float) ipo.calculateValue(frame));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return determines if the definition of the constraint will change the bone in any way; in most cases
|
||||
* it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
|
||||
* computing to improve the computation speed and lower the computations complexity
|
||||
*/
|
||||
public boolean isTrackToBeChanged() {
|
||||
return constraintDefinition == null ? false : constraintDefinition.isTrackToBeChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (name == null ? 0 : name.hashCode());
|
||||
result = prime * result + (ownerOMA == null ? 0 : ownerOMA.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Constraint other = (Constraint) obj;
|
||||
if (name == null) {
|
||||
if (other.name != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
if (ownerOMA == null) {
|
||||
if (other.ownerOMA != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!ownerOMA.equals(other.ownerOMA)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Constraint(name = " + name + ", def = " + constraintDefinition + ")";
|
||||
}
|
||||
}
|
@ -1,476 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.animations.Ipo;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
/**
|
||||
* This class should be used for constraint calculations.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class ConstraintHelper extends AbstractBlenderHelper {
|
||||
private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName());
|
||||
|
||||
/**
|
||||
* Helper constructor.
|
||||
*
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public ConstraintHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads constraints for for the given structure. The
|
||||
* constraints are loaded only once for object/bone.
|
||||
*
|
||||
* @param objectStructure
|
||||
* the structure we read constraint's for
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
*/
|
||||
public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
LOGGER.fine("Loading constraints.");
|
||||
// reading influence ipos for the constraints
|
||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||
Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
|
||||
Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
|
||||
if (pActions.isNotNull()) {
|
||||
List<Structure> actions = pActions.fetchData();
|
||||
for (Structure action : actions) {
|
||||
Structure chanbase = (Structure) action.getFieldValue("chanbase");
|
||||
List<Structure> actionChannels = chanbase.evaluateListBase();
|
||||
for (Structure actionChannel : actionChannels) {
|
||||
Map<String, Ipo> ipos = new HashMap<String, Ipo>();
|
||||
Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels");
|
||||
List<Structure> constraintChannels = constChannels.evaluateListBase();
|
||||
for (Structure constraintChannel : constraintChannels) {
|
||||
Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
|
||||
if (pIpo.isNotNull()) {
|
||||
String constraintName = constraintChannel.getFieldValue("name").toString();
|
||||
Ipo ipo = animationHelper.fromIpoStructure(pIpo.fetchData().get(0), blenderContext);
|
||||
ipos.put(constraintName, ipo);
|
||||
}
|
||||
}
|
||||
String actionName = actionChannel.getFieldValue("name").toString();
|
||||
constraintsIpos.put(actionName, ipos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loading constraints connected with the object's bones
|
||||
Pointer pPose = (Pointer) objectStructure.getFieldValue("pose");
|
||||
if (pPose.isNotNull()) {
|
||||
List<Structure> poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase();
|
||||
for (Structure poseChannel : poseChannels) {
|
||||
List<Constraint> constraintsList = new ArrayList<Constraint>();
|
||||
Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress());
|
||||
|
||||
// the name is read directly from structure because bone might
|
||||
// not yet be loaded
|
||||
String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString();
|
||||
List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase();
|
||||
for (Structure constraint : constraints) {
|
||||
String constraintName = constraint.getFieldValue("name").toString();
|
||||
Map<String, Ipo> ipoMap = constraintsIpos.get(name);
|
||||
Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName);
|
||||
if (ipo == null) {
|
||||
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
|
||||
ipo = animationHelper.fromValue(enforce);
|
||||
}
|
||||
constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext));
|
||||
}
|
||||
blenderContext.addConstraints(boneOMA, constraintsList);
|
||||
}
|
||||
}
|
||||
|
||||
// loading constraints connected with the object itself
|
||||
List<Structure> constraints = ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase();
|
||||
if (constraints != null && constraints.size() > 0) {
|
||||
Pointer pData = (Pointer) objectStructure.getFieldValue("data");
|
||||
String dataType = pData.isNotNull() ? pData.fetchData().get(0).getType() : null;
|
||||
List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size());
|
||||
|
||||
for (Structure constraint : constraints) {
|
||||
String constraintName = constraint.getFieldValue("name").toString();
|
||||
String objectName = objectStructure.getName();
|
||||
|
||||
Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName);
|
||||
Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null;
|
||||
if (ipo == null) {
|
||||
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
|
||||
ipo = animationHelper.fromValue(enforce);
|
||||
}
|
||||
|
||||
constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
|
||||
}
|
||||
blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a proper constraint object depending on the object's
|
||||
* data type. Supported data types: <li>Mesh <li>Armature <li>Camera <li>
|
||||
* Lamp Bone constraints are created in a different place.
|
||||
*
|
||||
* @param dataType
|
||||
* the type of the object's data
|
||||
* @param constraintStructure
|
||||
* the constraint structure
|
||||
* @param ownerOMA
|
||||
* the owner OMA
|
||||
* @param influenceIpo
|
||||
* the influence interpolation curve
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return constraint object for the required type
|
||||
* @throws BlenderFileException
|
||||
* thrown when problems with blender file occurred
|
||||
*/
|
||||
private Constraint createConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
|
||||
if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) {
|
||||
return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
|
||||
} else if ("Armature".equalsIgnoreCase(dataType)) {
|
||||
return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method bakes all available and valid constraints.
|
||||
*
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public void bakeConstraints(BlenderContext blenderContext) {
|
||||
Set<Long> owners = new HashSet<Long>();
|
||||
for (Constraint constraint : blenderContext.getAllConstraints()) {
|
||||
if(constraint instanceof BoneConstraint) {
|
||||
BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
|
||||
owners.add(boneContext.getArmatureObjectOMA());
|
||||
} else {
|
||||
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedDataType.FEATURE);
|
||||
while (spatial.getParent() != null) {
|
||||
spatial = spatial.getParent();
|
||||
}
|
||||
owners.add((Long)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, spatial));
|
||||
}
|
||||
}
|
||||
|
||||
List<SimulationNode> simulationRootNodes = new ArrayList<SimulationNode>(owners.size());
|
||||
for(Long ownerOMA : owners) {
|
||||
simulationRootNodes.add(new SimulationNode(ownerOMA, blenderContext));
|
||||
}
|
||||
|
||||
for (SimulationNode node : simulationRootNodes) {
|
||||
node.simulate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method retrieves the transform from a feature in a given space.
|
||||
*
|
||||
* @param oma
|
||||
* the OMA of the feature (spatial or armature node)
|
||||
* @param subtargetName
|
||||
* the feature's subtarget (bone in a case of armature's node)
|
||||
* @param space
|
||||
* the space the transform is evaluated to
|
||||
* @return the transform of a feature in a given space
|
||||
*/
|
||||
public Transform getTransform(Long oma, String subtargetName, Space space) {
|
||||
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
|
||||
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
|
||||
if (isArmature) {
|
||||
blenderContext.getSkeleton(oma).updateWorldVectors();
|
||||
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
|
||||
Bone bone = targetBoneContext.getBone();
|
||||
|
||||
if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) {
|
||||
space = Space.CONSTRAINT_SPACE_POSE;
|
||||
}
|
||||
|
||||
TempVars tempVars = TempVars.get();// use readable names of the matrices so that the code is more clear
|
||||
Transform result;
|
||||
switch (space) {
|
||||
case CONSTRAINT_SPACE_WORLD:
|
||||
Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedDataType.FEATURE);
|
||||
Matrix4f boneModelMatrix = this.toMatrix(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale(), tempVars.tempMat4);
|
||||
Matrix4f modelWorldMatrix = this.toMatrix(model.getWorldTransform(), tempVars.tempMat42);
|
||||
Matrix4f boneMatrixInWorldSpace = modelWorldMatrix.multLocal(boneModelMatrix);
|
||||
result = new Transform(boneMatrixInWorldSpace.toTranslationVector(), boneMatrixInWorldSpace.toRotationQuat(), boneMatrixInWorldSpace.toScaleVector());
|
||||
break;
|
||||
case CONSTRAINT_SPACE_LOCAL:
|
||||
assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!";
|
||||
result = new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale());
|
||||
break;
|
||||
case CONSTRAINT_SPACE_POSE: {
|
||||
Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4);
|
||||
Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal();
|
||||
Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix);
|
||||
result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector());
|
||||
break;
|
||||
}
|
||||
case CONSTRAINT_SPACE_PARLOCAL: {
|
||||
Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4);
|
||||
Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal();
|
||||
Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix);
|
||||
result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector());
|
||||
Bone parent = bone.getParent();
|
||||
if(parent != null) {
|
||||
BoneContext parentContext = blenderContext.getBoneContext(parent);
|
||||
Vector3f head = parent.getModelSpacePosition();
|
||||
Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(parentContext.getLength())));
|
||||
result.getTranslation().subtractLocal(tail);
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("Unknown space type: " + space);
|
||||
}
|
||||
tempVars.release();
|
||||
return result;
|
||||
} else {
|
||||
switch (space) {
|
||||
case CONSTRAINT_SPACE_LOCAL:
|
||||
return feature.getLocalTransform();
|
||||
case CONSTRAINT_SPACE_WORLD:
|
||||
return feature.getWorldTransform();
|
||||
case CONSTRAINT_SPACE_PARLOCAL:
|
||||
case CONSTRAINT_SPACE_POSE:
|
||||
throw new IllegalStateException("Nodes can have only Local and World spaces applied!");
|
||||
default:
|
||||
throw new IllegalStateException("Unknown space type: " + space);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies transform to a feature (bone or spatial). Computations transform
|
||||
* the given transformation from the given space to the feature's local
|
||||
* space.
|
||||
*
|
||||
* @param oma
|
||||
* the OMA of the feature we apply transformation to
|
||||
* @param subtargetName
|
||||
* the name of the feature's subtarget (bone in case of armature)
|
||||
* @param space
|
||||
* the space in which the given transform is to be applied
|
||||
* @param transform
|
||||
* the transform we apply
|
||||
*/
|
||||
public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
|
||||
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
|
||||
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
|
||||
if (isArmature) {
|
||||
Skeleton skeleton = blenderContext.getSkeleton(oma);
|
||||
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
|
||||
Bone bone = targetBoneContext.getBone();
|
||||
|
||||
if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) {
|
||||
space = Space.CONSTRAINT_SPACE_POSE;
|
||||
}
|
||||
|
||||
TempVars tempVars = TempVars.get();
|
||||
switch (space) {
|
||||
case CONSTRAINT_SPACE_LOCAL:
|
||||
assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!";
|
||||
bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
|
||||
break;
|
||||
case CONSTRAINT_SPACE_WORLD: {
|
||||
Matrix4f boneMatrixInWorldSpace = this.toMatrix(transform, tempVars.tempMat4);
|
||||
Matrix4f modelWorldMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42);
|
||||
Matrix4f boneMatrixInModelSpace = modelWorldMatrix.invertLocal().multLocal(boneMatrixInWorldSpace);
|
||||
Bone parent = bone.getParent();
|
||||
if (parent != null) {
|
||||
Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
|
||||
boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace);
|
||||
}
|
||||
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
|
||||
break;
|
||||
}
|
||||
case CONSTRAINT_SPACE_POSE: {
|
||||
Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4);
|
||||
Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42));
|
||||
Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal();
|
||||
Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace);
|
||||
Bone parent = bone.getParent();
|
||||
if (parent != null) {
|
||||
Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
|
||||
boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace);
|
||||
}
|
||||
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
|
||||
break;
|
||||
}
|
||||
case CONSTRAINT_SPACE_PARLOCAL:
|
||||
Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4);
|
||||
Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42));
|
||||
Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal();
|
||||
Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace);
|
||||
Bone parent = bone.getParent();
|
||||
if (parent != null) {
|
||||
//first add the initial parent matrix to the bone's model matrix
|
||||
BoneContext parentContext = blenderContext.getBoneContext(parent);
|
||||
|
||||
Matrix4f initialParentMatrixInModelSpace = parentContext.getBoneMatrixInModelSpace();
|
||||
Matrix4f currentParentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
|
||||
//the bone will now move with its parent in model space
|
||||
|
||||
//now we need to subtract the difference between current parent's model matrix and its initial model matrix
|
||||
boneMatrixInModelSpace = initialParentMatrixInModelSpace.mult(boneMatrixInModelSpace);
|
||||
|
||||
Matrix4f diffMatrix = initialParentMatrixInModelSpace.mult(currentParentMatrixInModelSpace.invert());
|
||||
boneMatrixInModelSpace.multLocal(diffMatrix);
|
||||
//now the bone will have its position in model space with initial parent's model matrix added
|
||||
}
|
||||
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
|
||||
break;
|
||||
default:
|
||||
tempVars.release();
|
||||
throw new IllegalStateException("Invalid space type for target object: " + space.toString());
|
||||
}
|
||||
tempVars.release();
|
||||
skeleton.updateWorldVectors();
|
||||
} else {
|
||||
switch (space) {
|
||||
case CONSTRAINT_SPACE_LOCAL:
|
||||
feature.getLocalTransform().set(transform);
|
||||
break;
|
||||
case CONSTRAINT_SPACE_WORLD:
|
||||
if (feature.getParent() == null) {
|
||||
feature.setLocalTransform(transform);
|
||||
} else {
|
||||
Transform parentWorldTransform = feature.getParent().getWorldTransform();
|
||||
|
||||
TempVars tempVars = TempVars.get();
|
||||
Matrix4f parentInverseMatrix = this.toMatrix(parentWorldTransform, tempVars.tempMat4).invertLocal();
|
||||
Matrix4f m = this.toMatrix(transform, tempVars.tempMat42);
|
||||
m = m.multLocal(parentInverseMatrix);
|
||||
tempVars.release();
|
||||
|
||||
transform.setTranslation(m.toTranslationVector());
|
||||
transform.setRotation(m.toRotationQuat());
|
||||
transform.setScale(m.toScaleVector());
|
||||
|
||||
feature.setLocalTransform(transform);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid space type for spatial object: " + space.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts given transform to the matrix.
|
||||
*
|
||||
* @param transform
|
||||
* the transform to be converted
|
||||
* @param store
|
||||
* the matrix where the result will be stored
|
||||
* @return the store matrix
|
||||
*/
|
||||
public Matrix4f toMatrix(Transform transform, Matrix4f store) {
|
||||
if (transform != null) {
|
||||
return this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale(), store);
|
||||
}
|
||||
store.loadIdentity();
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts given transformation parameters into the matrix.
|
||||
*
|
||||
* @param position
|
||||
* the position of the feature
|
||||
* @param rotation
|
||||
* the rotation of the feature
|
||||
* @param scale
|
||||
* the scale of the feature
|
||||
* @param store
|
||||
* the matrix where the result will be stored
|
||||
* @return the store matrix
|
||||
*/
|
||||
private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale, Matrix4f store) {
|
||||
store.loadIdentity();
|
||||
store.setTranslation(position);
|
||||
store.setRotationQuaternion(rotation);
|
||||
store.setScale(scale);
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* The space of target or owner transformation.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static enum Space {
|
||||
/** A transformation of the bone or spatial in the world space. */
|
||||
CONSTRAINT_SPACE_WORLD,
|
||||
/**
|
||||
* For spatial it is the transformation in its parent space or in WORLD space if it has no parent.
|
||||
* For bone it is a transformation in its bone parent space or in armature space if it has no parent.
|
||||
*/
|
||||
CONSTRAINT_SPACE_LOCAL,
|
||||
/**
|
||||
* This space IS NOT applicable for spatials.
|
||||
* For bone it is a transformation in the blender's armature object space.
|
||||
*/
|
||||
CONSTRAINT_SPACE_POSE,
|
||||
|
||||
CONSTRAINT_SPACE_PARLOCAL;
|
||||
|
||||
/**
|
||||
* This method returns the enum instance when given the appropriate
|
||||
* value from the blend file.
|
||||
*
|
||||
* @param c
|
||||
* the blender's value of the space modifier
|
||||
* @return the scape enum instance
|
||||
*/
|
||||
public static Space valueOf(byte c) {
|
||||
switch (c) {
|
||||
case 0:
|
||||
return CONSTRAINT_SPACE_WORLD;
|
||||
case 1:
|
||||
return CONSTRAINT_SPACE_LOCAL;
|
||||
case 2:
|
||||
return CONSTRAINT_SPACE_POSE;
|
||||
case 3:
|
||||
return CONSTRAINT_SPACE_PARLOCAL;
|
||||
default:
|
||||
throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,397 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.animation.AnimChannel;
|
||||
import com.jme3.animation.AnimControl;
|
||||
import com.jme3.animation.Animation;
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.BoneTrack;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.animation.SpatialTrack;
|
||||
import com.jme3.animation.Track;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
/**
|
||||
* A node that represents either spatial or bone in constraint simulation. The
|
||||
* node is applied its translation, rotation and scale for each frame of its
|
||||
* animation. Then the constraints are applied that will eventually alter it.
|
||||
* After that the feature's transformation is stored in VirtualTrack which is
|
||||
* converted to new bone or spatial track at the very end.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class SimulationNode {
|
||||
private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName());
|
||||
|
||||
private Long featureOMA;
|
||||
/** The blender context. */
|
||||
private BlenderContext blenderContext;
|
||||
/** The name of the node (for debugging purposes). */
|
||||
private String name;
|
||||
/** A list of children for the node (either bones or child spatials). */
|
||||
private List<SimulationNode> children = new ArrayList<SimulationNode>();
|
||||
/** A list of node's animations. */
|
||||
private List<Animation> animations;
|
||||
|
||||
/** The nodes spatial (if null then the boneContext should be set). */
|
||||
private Spatial spatial;
|
||||
/** The skeleton of the bone (not null if the node simulated the bone). */
|
||||
private Skeleton skeleton;
|
||||
/** Animation controller for the node's feature. */
|
||||
private AnimControl animControl;
|
||||
|
||||
/**
|
||||
* The star transform of a spatial. Needed to properly reset the spatial to
|
||||
* its start position.
|
||||
*/
|
||||
private Transform spatialStartTransform;
|
||||
/** Star transformations for bones. Needed to properly reset the bones. */
|
||||
private Map<Bone, Transform> boneStartTransforms;
|
||||
|
||||
/**
|
||||
* Builds the nodes tree for the given feature. The feature (bone or
|
||||
* spatial) is found by its OMA. The feature must be a root bone or a root
|
||||
* spatial.
|
||||
*
|
||||
* @param featureOMA
|
||||
* the OMA of either bone or spatial
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public SimulationNode(Long featureOMA, BlenderContext blenderContext) {
|
||||
this(featureOMA, blenderContext, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the node for the feature.
|
||||
*
|
||||
* @param featureOMA
|
||||
* the OMA of either bone or spatial
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @param rootNode
|
||||
* indicates if the feature is a root bone or root spatial or not
|
||||
*/
|
||||
private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
|
||||
this.featureOMA = featureOMA;
|
||||
this.blenderContext = blenderContext;
|
||||
Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedDataType.FEATURE);
|
||||
if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, spatial) != null) {
|
||||
skeleton = blenderContext.getSkeleton(featureOMA);
|
||||
|
||||
Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton);
|
||||
animControl = nodeWithAnimationControl.getControl(AnimControl.class);
|
||||
|
||||
boneStartTransforms = new HashMap<Bone, Transform>();
|
||||
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
|
||||
Bone bone = skeleton.getBone(i);
|
||||
boneStartTransforms.put(bone, new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale()));
|
||||
}
|
||||
} else {
|
||||
if (rootNode && spatial.getParent() != null) {
|
||||
throw new IllegalStateException("Given spatial must be a root node!");
|
||||
}
|
||||
this.spatial = spatial;
|
||||
spatialStartTransform = spatial.getLocalTransform().clone();
|
||||
}
|
||||
|
||||
name = '>' + spatial.getName() + '<';
|
||||
|
||||
// add children nodes
|
||||
if (skeleton != null) {
|
||||
Node node = blenderContext.getControlledNode(skeleton);
|
||||
Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
|
||||
animations = blenderContext.getAnimations(animatedNodeOMA);
|
||||
} else {
|
||||
animations = blenderContext.getAnimations(featureOMA);
|
||||
for (Spatial child : spatial.getChildren()) {
|
||||
if (child instanceof Node) {
|
||||
children.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, child), blenderContext, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the node's feature to its starting transformation.
|
||||
*/
|
||||
private void reset() {
|
||||
if (spatial != null) {
|
||||
spatial.setLocalTransform(spatialStartTransform);
|
||||
for (SimulationNode child : children) {
|
||||
child.reset();
|
||||
}
|
||||
} else if (skeleton != null) {
|
||||
for (Entry<Bone, Transform> entry : boneStartTransforms.entrySet()) {
|
||||
Transform t = entry.getValue();
|
||||
entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
|
||||
entry.getKey().updateModelTransforms();
|
||||
}
|
||||
skeleton.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the spatial node.
|
||||
*/
|
||||
private void simulateSpatial() {
|
||||
List<Constraint> constraints = blenderContext.getConstraints(featureOMA);
|
||||
if (constraints != null && constraints.size() > 0) {
|
||||
LOGGER.fine("Simulating spatial.");
|
||||
boolean applyStaticConstraints = true;
|
||||
if (animations != null) {
|
||||
for (Animation animation : animations) {
|
||||
float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation);
|
||||
int maxFrame = (int) animationTimeBoundaries[0];
|
||||
float maxTime = animationTimeBoundaries[1];
|
||||
|
||||
VirtualTrack vTrack = new VirtualTrack(spatial.getName(), maxFrame, maxTime);
|
||||
for (Track track : animation.getTracks()) {
|
||||
for (int frame = 0; frame < maxFrame; ++frame) {
|
||||
spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]);
|
||||
spatial.setLocalRotation(((SpatialTrack) track).getRotations()[frame]);
|
||||
spatial.setLocalScale(((SpatialTrack) track).getScales()[frame]);
|
||||
|
||||
for (Constraint constraint : constraints) {
|
||||
constraint.apply(frame);
|
||||
vTrack.setTransform(frame, spatial.getLocalTransform());
|
||||
}
|
||||
}
|
||||
Track newTrack = vTrack.getAsSpatialTrack();
|
||||
if (newTrack != null) {
|
||||
animation.removeTrack(track);
|
||||
animation.addTrack(newTrack);
|
||||
}
|
||||
applyStaticConstraints = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no animations then just constraint the static
|
||||
// object's transformation
|
||||
if (applyStaticConstraints) {
|
||||
for (Constraint constraint : constraints) {
|
||||
constraint.apply(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (SimulationNode child : children) {
|
||||
child.simulate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the bone node.
|
||||
*/
|
||||
private void simulateSkeleton() {
|
||||
LOGGER.fine("Simulating skeleton.");
|
||||
Set<Long> alteredOmas = new HashSet<Long>();
|
||||
|
||||
if (animations != null) {
|
||||
TempVars vars = TempVars.get();
|
||||
AnimChannel animChannel = animControl.createChannel();
|
||||
|
||||
for (Animation animation : animations) {
|
||||
float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation);
|
||||
int maxFrame = (int) animationTimeBoundaries[0];
|
||||
float maxTime = animationTimeBoundaries[1];
|
||||
|
||||
Map<Integer, VirtualTrack> tracks = new HashMap<Integer, VirtualTrack>();
|
||||
for (int frame = 0; frame < maxFrame; ++frame) {
|
||||
// this MUST be done here, otherwise setting next frame of animation will
|
||||
// lead to possible errors
|
||||
this.reset();
|
||||
|
||||
// first set proper time for all bones in all the tracks ...
|
||||
for (Track track : animation.getTracks()) {
|
||||
float time = ((BoneTrack) track).getTimes()[frame];
|
||||
track.setTime(time, 1, animControl, animChannel, vars);
|
||||
skeleton.updateWorldVectors();
|
||||
}
|
||||
|
||||
// ... and then apply constraints from the root bone to the last child ...
|
||||
Set<Long> applied = new HashSet<Long>();
|
||||
for (Bone rootBone : skeleton.getRoots()) {
|
||||
// ignore the 0-indexed bone
|
||||
if (skeleton.getBoneIndex(rootBone) > 0) {
|
||||
this.applyConstraints(rootBone, alteredOmas, applied, frame, new Stack<Bone>());
|
||||
}
|
||||
}
|
||||
|
||||
// ... add virtual tracks if necessary, for bones that were altered but had no tracks before ...
|
||||
for (Long boneOMA : alteredOmas) {
|
||||
BoneContext boneContext = blenderContext.getBoneContext(boneOMA);
|
||||
int boneIndex = skeleton.getBoneIndex(boneContext.getBone());
|
||||
if (!tracks.containsKey(boneIndex)) {
|
||||
tracks.put(boneIndex, new VirtualTrack(boneContext.getBone().getName(), maxFrame, maxTime));
|
||||
}
|
||||
}
|
||||
alteredOmas.clear();
|
||||
|
||||
// ... and fill in another frame in the result track
|
||||
for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
|
||||
Bone bone = skeleton.getBone(trackEntry.getKey());
|
||||
Transform startTransform = boneStartTransforms.get(bone);
|
||||
|
||||
// track contains differences between the frame position and bind positions of bones/spatials
|
||||
Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation());
|
||||
Quaternion boneRotationDifference = startTransform.getRotation().inverse().mult(bone.getLocalRotation()).normalizeLocal();
|
||||
Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale());
|
||||
|
||||
trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference));
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
|
||||
Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey());
|
||||
if (newTrack != null) {
|
||||
boolean trackReplaced = false;
|
||||
for (Track track : animation.getTracks()) {
|
||||
if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) {
|
||||
animation.removeTrack(track);
|
||||
animation.addTrack(newTrack);
|
||||
trackReplaced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!trackReplaced) {
|
||||
animation.addTrack(newTrack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vars.release();
|
||||
animControl.clearChannels();
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies constraints to the given bone and its children.
|
||||
* The goal is to apply constraint from root bone to the last child.
|
||||
* @param bone
|
||||
* the bone whose constraints will be applied
|
||||
* @param alteredOmas
|
||||
* the set of OMAS of the altered bones (is populated if necessary)
|
||||
* @param frame
|
||||
* the current frame of the animation
|
||||
* @param bonesStack
|
||||
* the stack of bones used to avoid infinite loops while applying constraints
|
||||
*/
|
||||
private void applyConstraints(Bone bone, Set<Long> alteredOmas, Set<Long> applied, int frame, Stack<Bone> bonesStack) {
|
||||
if (!bonesStack.contains(bone)) {
|
||||
bonesStack.push(bone);
|
||||
BoneContext boneContext = blenderContext.getBoneContext(bone);
|
||||
if (!applied.contains(boneContext.getBoneOma())) {
|
||||
List<Constraint> constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
|
||||
if (constraints != null && constraints.size() > 0) {
|
||||
for (Constraint constraint : constraints) {
|
||||
if (constraint.getTargetOMA() != null && constraint.getTargetOMA() > 0L) {
|
||||
// first apply constraints of the target bone
|
||||
BoneContext targetBone = blenderContext.getBoneContext(constraint.getTargetOMA());
|
||||
this.applyConstraints(targetBone.getBone(), alteredOmas, applied, frame, bonesStack);
|
||||
}
|
||||
constraint.apply(frame);
|
||||
if (constraint.getAlteredOmas() != null) {
|
||||
alteredOmas.addAll(constraint.getAlteredOmas());
|
||||
}
|
||||
alteredOmas.add(boneContext.getBoneOma());
|
||||
}
|
||||
}
|
||||
applied.add(boneContext.getBoneOma());
|
||||
}
|
||||
|
||||
List<Bone> children = bone.getChildren();
|
||||
if (children != null && children.size() > 0) {
|
||||
for (Bone child : bone.getChildren()) {
|
||||
this.applyConstraints(child, alteredOmas, applied, frame, bonesStack);
|
||||
}
|
||||
}
|
||||
bonesStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates the node.
|
||||
*/
|
||||
public void simulate() {
|
||||
this.reset();
|
||||
if (spatial != null) {
|
||||
this.simulateSpatial();
|
||||
} else {
|
||||
this.simulateSkeleton();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the maximum frame and time for the animation. Different tracks
|
||||
* can have different lengths so here the maximum one is being found.
|
||||
*
|
||||
* @param animation
|
||||
* the animation
|
||||
* @return maximum frame and time of the animation
|
||||
*/
|
||||
private float[] computeAnimationTimeBoundaries(Animation animation) {
|
||||
int maxFrame = Integer.MIN_VALUE;
|
||||
float maxTime = -Float.MAX_VALUE;
|
||||
for (Track track : animation.getTracks()) {
|
||||
if (track instanceof BoneTrack) {
|
||||
maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length);
|
||||
maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]);
|
||||
} else if (track instanceof SpatialTrack) {
|
||||
maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length);
|
||||
maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]);
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported track type for simuation: " + track);
|
||||
}
|
||||
}
|
||||
return new float[] { maxFrame, maxTime };
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds constraints for the node's features.
|
||||
*
|
||||
* @param ownerOMA
|
||||
* the feature's OMA
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return a list of feature's constraints or empty list if none were found
|
||||
*/
|
||||
private List<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) {
|
||||
List<Constraint> result = new ArrayList<Constraint>();
|
||||
List<Constraint> constraints = blenderContext.getConstraints(ownerOMA);
|
||||
if (constraints != null) {
|
||||
for (Constraint constraint : constraints) {
|
||||
if (constraint.isImplemented() && constraint.validate() && constraint.isTrackToBeChanged()) {
|
||||
result.add(constraint);
|
||||
}
|
||||
// TODO: add proper warnings to some map or set so that they are not logged on every frame
|
||||
}
|
||||
}
|
||||
return result.size() > 0 ? result : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.Ipo;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* Constraint applied on the skeleton. This constraint is here only to make the
|
||||
* application not crash when loads constraints applied to armature. But
|
||||
* skeleton movement is not supported by jme so the constraint will never be
|
||||
* applied.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class SkeletonConstraint extends Constraint {
|
||||
private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName());
|
||||
|
||||
public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
|
||||
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate() {
|
||||
LOGGER.warning("Constraints for skeleton are not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(int frame) {
|
||||
LOGGER.warning("Applying constraints to skeleton is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTargetOMA() {
|
||||
LOGGER.warning("Constraints for skeleton are not supported.");
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.animations.Ipo;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* Constraint applied on the spatial objects. This includes: nodes, cameras
|
||||
* nodes and light nodes.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class SpatialConstraint extends Constraint {
|
||||
public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
|
||||
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate() {
|
||||
if (targetOMA != null) {
|
||||
return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null;
|
||||
}
|
||||
return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTargetOMA() {
|
||||
return targetOMA;
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.jme3.animation.BoneTrack;
|
||||
import com.jme3.animation.SpatialTrack;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
/**
|
||||
* A virtual track that stores computed frames after constraints are applied.
|
||||
* Not all the frames need to be inserted. If there are lacks then the class
|
||||
* will fill the gaps.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class VirtualTrack {
|
||||
/** The name of the track (for debugging purposes). */
|
||||
private String name;
|
||||
/** The last frame for the track. */
|
||||
public int maxFrame;
|
||||
/** The max time for the track. */
|
||||
public float maxTime;
|
||||
/** Translations of the track. */
|
||||
public ArrayList<Vector3f> translations;
|
||||
/** Rotations of the track. */
|
||||
public ArrayList<Quaternion> rotations;
|
||||
/** Scales of the track. */
|
||||
public ArrayList<Vector3f> scales;
|
||||
|
||||
/**
|
||||
* Constructs the object storing the maximum frame and time.
|
||||
*
|
||||
* @param maxFrame
|
||||
* the last frame for the track
|
||||
* @param maxTime
|
||||
* the max time for the track
|
||||
*/
|
||||
public VirtualTrack(String name, int maxFrame, float maxTime) {
|
||||
this.name = name;
|
||||
this.maxFrame = maxFrame;
|
||||
this.maxTime = maxTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the transform for the given frame.
|
||||
*
|
||||
* @param frameIndex
|
||||
* the frame for which the transform will be set
|
||||
* @param transform
|
||||
* the transformation to be set
|
||||
*/
|
||||
public void setTransform(int frameIndex, Transform transform) {
|
||||
if (translations == null) {
|
||||
translations = this.createList(Vector3f.ZERO, frameIndex);
|
||||
}
|
||||
this.append(translations, Vector3f.ZERO, frameIndex - translations.size());
|
||||
translations.add(transform.getTranslation().clone());
|
||||
|
||||
if (rotations == null) {
|
||||
rotations = this.createList(Quaternion.IDENTITY, frameIndex);
|
||||
}
|
||||
this.append(rotations, Quaternion.IDENTITY, frameIndex - rotations.size());
|
||||
rotations.add(transform.getRotation().clone());
|
||||
|
||||
if (scales == null) {
|
||||
scales = this.createList(Vector3f.UNIT_XYZ, frameIndex);
|
||||
}
|
||||
this.append(scales, Vector3f.UNIT_XYZ, frameIndex - scales.size());
|
||||
scales.add(transform.getScale().clone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the track as a bone track.
|
||||
*
|
||||
* @param targetBoneIndex
|
||||
* the bone index
|
||||
* @return the bone track
|
||||
*/
|
||||
public BoneTrack getAsBoneTrack(int targetBoneIndex) {
|
||||
if (translations == null && rotations == null && scales == null) {
|
||||
return null;
|
||||
}
|
||||
return new BoneTrack(targetBoneIndex, this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the track as a spatial track.
|
||||
*
|
||||
* @return the spatial track
|
||||
*/
|
||||
public SpatialTrack getAsSpatialTrack() {
|
||||
if (translations == null && rotations == null && scales == null) {
|
||||
return null;
|
||||
}
|
||||
return new SpatialTrack(this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame]));
|
||||
}
|
||||
|
||||
/**
|
||||
* The method creates times for the track based on the given maximum values.
|
||||
*
|
||||
* @return the times for the track
|
||||
*/
|
||||
private float[] createTimes() {
|
||||
float[] times = new float[maxFrame];
|
||||
float dT = maxTime / maxFrame;
|
||||
float t = 0;
|
||||
for (int i = 0; i < maxFrame; ++i) {
|
||||
times[i] = t;
|
||||
t += dT;
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that creates a list of a given size filled with given
|
||||
* elements.
|
||||
*
|
||||
* @param element
|
||||
* the element to be put into the list
|
||||
* @param count
|
||||
* the list size
|
||||
* @return the list
|
||||
*/
|
||||
private <T> ArrayList<T> createList(T element, int count) {
|
||||
ArrayList<T> result = new ArrayList<T>(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
result.add(element);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the element to the given list.
|
||||
*
|
||||
* @param list
|
||||
* the list where the element will be appended
|
||||
* @param element
|
||||
* the element to be appended
|
||||
* @param count
|
||||
* how many times the element will be appended
|
||||
*/
|
||||
private <T> void append(ArrayList<T> list, T element, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
list.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder(2048);
|
||||
result.append("TRACK: ").append(name).append('\n');
|
||||
if (translations != null && translations.size() > 0) {
|
||||
result.append("TRANSLATIONS: ").append(translations.toString()).append('\n');
|
||||
}
|
||||
if (rotations != null && rotations.size() > 0) {
|
||||
result.append("ROTATIONS: ").append(rotations.toString()).append('\n');
|
||||
}
|
||||
if (scales != null && scales.size() > 0) {
|
||||
result.append("SCALES: ").append(scales.toString()).append('\n');
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* A base class for all constraint definitions.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public abstract class ConstraintDefinition {
|
||||
protected ConstraintHelper constraintHelper;
|
||||
/** Constraints flag. Used to load user's options applied to the constraint. */
|
||||
protected int flag;
|
||||
/** The constraint's owner. Loaded during runtime. */
|
||||
private Object owner;
|
||||
/** The blender context. */
|
||||
protected BlenderContext blenderContext;
|
||||
/** The constraint's owner OMA. */
|
||||
protected Long ownerOMA;
|
||||
/** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */
|
||||
protected Set<Long> alteredOmas;
|
||||
/** The variable that determines if the constraint will alter the track in any way. */
|
||||
protected boolean trackToBeChanged = true;
|
||||
/** The name of the constraint. */
|
||||
protected String constraintName;
|
||||
|
||||
/**
|
||||
* Loads a constraint definition based on the constraint definition
|
||||
* structure.
|
||||
*
|
||||
* @param constraintData
|
||||
* the constraint definition structure
|
||||
* @param ownerOMA
|
||||
* the constraint's owner OMA
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public ConstraintDefinition(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
if (constraintData != null) {// Null constraint has no data
|
||||
Number flag = (Number) constraintData.getFieldValue("flag");
|
||||
if (flag != null) {
|
||||
this.flag = flag.intValue();
|
||||
}
|
||||
}
|
||||
this.blenderContext = blenderContext;
|
||||
constraintHelper = (ConstraintHelper) (blenderContext == null ? null : blenderContext.getHelper(ConstraintHelper.class));
|
||||
this.ownerOMA = ownerOMA;
|
||||
}
|
||||
|
||||
public void setConstraintName(String constraintName) {
|
||||
this.constraintName = constraintName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return determines if the definition of the constraint will change the bone in any way; in most cases
|
||||
* it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
|
||||
* computing to improve the computation speed and lower the computations complexity
|
||||
*/
|
||||
public boolean isTrackToBeChanged() {
|
||||
return trackToBeChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return determines if this constraint definition requires a defined target or not
|
||||
*/
|
||||
public abstract boolean isTargetRequired();
|
||||
|
||||
/**
|
||||
* This method is here because we have no guarantee that the owner is loaded
|
||||
* when constraint is being created. So use it to get the owner when it is
|
||||
* needed for computations.
|
||||
*
|
||||
* @return the owner of the constraint or null if none is set
|
||||
*/
|
||||
protected Object getOwner() {
|
||||
if (ownerOMA != null && owner == null) {
|
||||
owner = blenderContext.getLoadedFeature(ownerOMA, LoadedDataType.FEATURE);
|
||||
if (owner == null) {
|
||||
throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName());
|
||||
}
|
||||
}
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method gets the owner's transformation. The owner can be either bone or spatial.
|
||||
* @param ownerSpace
|
||||
* the space in which the computed transformation is given
|
||||
* @return the constraint owner's transformation
|
||||
*/
|
||||
protected Transform getOwnerTransform(Space ownerSpace) {
|
||||
if (this.getOwner() instanceof Bone) {
|
||||
BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
|
||||
return constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace);
|
||||
}
|
||||
return constraintHelper.getTransform(ownerOMA, null, ownerSpace);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method applies the given transformation to the owner.
|
||||
* @param ownerTransform
|
||||
* the transformation to apply to the owner
|
||||
* @param ownerSpace
|
||||
* the space that defines which owner's transformation (ie. global, local, etc. will be set)
|
||||
*/
|
||||
protected void applyOwnerTransform(Transform ownerTransform, Space ownerSpace) {
|
||||
if (this.getOwner() instanceof Bone) {
|
||||
BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
|
||||
constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform);
|
||||
} else {
|
||||
constraintHelper.applyTransform(ownerOMA, null, ownerSpace, ownerTransform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if the definition is implemented and <b>false</b>
|
||||
* otherwise
|
||||
*/
|
||||
public boolean isImplemented() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a list of all OMAs of the features that the constraint had altered beside its owner
|
||||
*/
|
||||
public Set<Long> getAlteredOmas() {
|
||||
return alteredOmas;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type name of the constraint
|
||||
*/
|
||||
public abstract String getConstraintTypeName();
|
||||
|
||||
/**
|
||||
* Bakes the constraint for the current feature (bone or spatial) position.
|
||||
*
|
||||
* @param ownerSpace
|
||||
* the space where owner transform will be evaluated in
|
||||
* @param targetSpace
|
||||
* the space where target transform will be evaluated in
|
||||
* @param targetTransform
|
||||
* the target transform used by some of the constraints
|
||||
* @param influence
|
||||
* the influence of the constraint from range [0; 1]
|
||||
*/
|
||||
public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getConstraintTypeName();
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Dist limit' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition {
|
||||
private static final int LIMITDIST_INSIDE = 0;
|
||||
private static final int LIMITDIST_OUTSIDE = 1;
|
||||
private static final int LIMITDIST_ONSURFACE = 2;
|
||||
|
||||
protected int mode;
|
||||
protected float dist;
|
||||
|
||||
public ConstraintDefinitionDistLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
mode = ((Number) constraintData.getFieldValue("mode")).intValue();
|
||||
dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
|
||||
// distance limit does not work on bones who are connected to their parent
|
||||
return;
|
||||
}
|
||||
if (influence == 0 || targetTransform == null) {
|
||||
return;// no need to do anything
|
||||
}
|
||||
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
|
||||
Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
|
||||
float currentDistance = v.length();
|
||||
switch (mode) {
|
||||
case LIMITDIST_INSIDE:
|
||||
if (currentDistance >= dist) {
|
||||
v.normalizeLocal();
|
||||
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
|
||||
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
|
||||
}
|
||||
break;
|
||||
case LIMITDIST_ONSURFACE:
|
||||
if (currentDistance > dist) {
|
||||
v.normalizeLocal();
|
||||
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
|
||||
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
|
||||
} else if (currentDistance < dist) {
|
||||
v.normalizeLocal().multLocal(dist * influence);
|
||||
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
|
||||
}
|
||||
break;
|
||||
case LIMITDIST_OUTSIDE:
|
||||
if (currentDistance <= dist) {
|
||||
v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence);
|
||||
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);
|
||||
}
|
||||
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Limit distance";
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
public class ConstraintDefinitionFactory {
|
||||
private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>();
|
||||
static {
|
||||
CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class);
|
||||
CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class);
|
||||
CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class);
|
||||
CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class);
|
||||
CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class);
|
||||
CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class);
|
||||
CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class);
|
||||
CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class);
|
||||
CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionIK.class);
|
||||
CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class);// since blender 2.51
|
||||
CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionMaintainVolume.class);// since blender 2.53
|
||||
}
|
||||
|
||||
private static final Map<String, String> UNSUPPORTED_CONSTRAINTS = new HashMap<String, String>();
|
||||
static {
|
||||
UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bClampToConstraint", "Clamp to");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bFollowPathConstraint", "Follow path");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bLockTrackConstraint", "Lock track");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bMinMaxConstraint", "Min max");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bPythonConstraint", "Python/Script");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bRigidBodyJointConstraint", "Rigid body joint");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bShrinkWrapConstraint", "Shrinkwrap");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bStretchToConstraint", "Stretch to");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bTransformConstraint", "Transform");
|
||||
// Blender 2.50+
|
||||
UNSUPPORTED_CONSTRAINTS.put("bSplineIKConstraint", "Spline inverse kinematics");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bDampTrackConstraint", "Damp track");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bPivotConstraint", "Pivot");
|
||||
// Blender 2.56+
|
||||
UNSUPPORTED_CONSTRAINTS.put("bTrackToConstraint", "Track to");
|
||||
// Blender 2.62+
|
||||
UNSUPPORTED_CONSTRAINTS.put("bCameraSolverConstraint", "Camera solver");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bObjectSolverConstraint", "Object solver");
|
||||
UNSUPPORTED_CONSTRAINTS.put("bFollowTrackConstraint", "Follow track");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates the constraint instance.
|
||||
*
|
||||
* @param constraintStructure
|
||||
* the constraint's structure (bConstraint clss in blender 2.49).
|
||||
* If the value is null the NullConstraint is created.
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blender file is somehow
|
||||
* corrupted
|
||||
*/
|
||||
public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, String constraintName, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException {
|
||||
if (constraintStructure == null) {
|
||||
return new ConstraintDefinitionNull(null, ownerOMA, blenderContext);
|
||||
}
|
||||
String constraintClassName = constraintStructure.getType();
|
||||
Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
|
||||
if (constraintDefinitionClass != null) {
|
||||
try {
|
||||
ConstraintDefinition def = (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext);
|
||||
def.setConstraintName(constraintName);
|
||||
return def;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BlenderFileException(e.getLocalizedMessage(), e);
|
||||
} catch (SecurityException e) {
|
||||
throw new BlenderFileException(e.getLocalizedMessage(), e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new BlenderFileException(e.getLocalizedMessage(), e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new BlenderFileException(e.getLocalizedMessage(), e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new BlenderFileException(e.getLocalizedMessage(), e);
|
||||
}
|
||||
} else {
|
||||
String unsupportedConstraintClassName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName);
|
||||
if (unsupportedConstraintClassName != null) {
|
||||
return new UnsupportedConstraintDefinition(unsupportedConstraintClassName);
|
||||
} else {
|
||||
throw new BlenderFileException("Unknown constraint type: " + constraintClassName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.ejml.simple.SimpleMatrix;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.math.DQuaternion;
|
||||
import com.jme3.scene.plugins.blender.math.DTransform;
|
||||
import com.jme3.scene.plugins.blender.math.Matrix;
|
||||
import com.jme3.scene.plugins.blender.math.Vector3d;
|
||||
|
||||
/**
|
||||
* A definiotion of a Inverse Kinematics constraint. This implementation uses Jacobian pseudoinverse algorithm.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class ConstraintDefinitionIK extends ConstraintDefinition {
|
||||
private static final float MIN_DISTANCE = 0.001f;
|
||||
private static final float MIN_ANGLE_CHANGE = 0.001f;
|
||||
private static final int FLAG_USE_TAIL = 0x01;
|
||||
private static final int FLAG_POSITION = 0x20;
|
||||
|
||||
private BonesChain bones;
|
||||
/** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */
|
||||
private int bonesAffected;
|
||||
/** Indicates if the tail of the bone should be used or not. */
|
||||
private boolean useTail;
|
||||
/** The amount of iterations of the algorithm. */
|
||||
private int iterations;
|
||||
/** The count of bones' chain. */
|
||||
private int bonesCount = -1;
|
||||
|
||||
public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
bonesAffected = ((Number) constraintData.getFieldValue("rootbone")).intValue();
|
||||
iterations = ((Number) constraintData.getFieldValue("iterations")).intValue();
|
||||
useTail = (flag & FLAG_USE_TAIL) != 0;
|
||||
|
||||
if ((flag & FLAG_POSITION) == 0) {
|
||||
trackToBeChanged = false;
|
||||
}
|
||||
|
||||
if (trackToBeChanged) {
|
||||
alteredOmas = new HashSet<Long>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Below are the variables that only need to be allocated once for IK constraint instance.
|
||||
*/
|
||||
/** Temporal quaternion. */
|
||||
private DQuaternion tempDQuaternion = new DQuaternion();
|
||||
/** Temporal matrix column. */
|
||||
private Vector3d col = new Vector3d();
|
||||
/** Effector's position change. */
|
||||
private Matrix deltaP = new Matrix(3, 1);
|
||||
/** The current target position. */
|
||||
private Vector3d target = new Vector3d();
|
||||
/** Rotation vectors for each joint (allocated when we know the size of a bones' chain. */
|
||||
private Vector3d[] rotationVectors;
|
||||
/** The Jacobian matrix. Allocated when the bones' chain size is known. */
|
||||
private Matrix J;
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || !trackToBeChanged || targetTransform == null || bonesCount == 0) {
|
||||
return;// no need to do anything
|
||||
}
|
||||
|
||||
if (bones == null) {
|
||||
bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, alteredOmas, blenderContext);
|
||||
}
|
||||
if (bones.size() == 0) {
|
||||
bonesCount = 0;
|
||||
return;// no need to do anything
|
||||
}
|
||||
double distanceFromTarget = Double.MAX_VALUE;
|
||||
target.set(targetTransform.getTranslation().x, targetTransform.getTranslation().y, targetTransform.getTranslation().z);
|
||||
|
||||
if (bonesCount < 0) {
|
||||
bonesCount = bones.size();
|
||||
rotationVectors = new Vector3d[bonesCount];
|
||||
for (int i = 0; i < bonesCount; ++i) {
|
||||
rotationVectors[i] = new Vector3d();
|
||||
}
|
||||
J = new Matrix(3, bonesCount);
|
||||
}
|
||||
|
||||
BoneContext topBone = bones.get(0);
|
||||
for (int i = 0; i < iterations; ++i) {
|
||||
DTransform topBoneTransform = bones.getWorldTransform(topBone);
|
||||
Vector3d e = topBoneTransform.getTranslation().add(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
|
||||
distanceFromTarget = e.distance(target);
|
||||
if (distanceFromTarget <= MIN_DISTANCE) {
|
||||
break;
|
||||
}
|
||||
|
||||
deltaP.setColumn(0, 0, target.x - e.x, target.y - e.y, target.z - e.z);
|
||||
int column = 0;
|
||||
for (BoneContext boneContext : bones) {
|
||||
DTransform boneWorldTransform = bones.getWorldTransform(boneContext);
|
||||
Vector3d j = boneWorldTransform.getTranslation(); // current join position
|
||||
Vector3d vectorFromJointToEffector = e.subtract(j);
|
||||
vectorFromJointToEffector.cross(target.subtract(j), rotationVectors[column]).normalizeLocal();
|
||||
rotationVectors[column].cross(vectorFromJointToEffector, col);
|
||||
J.setColumn(col, column++);
|
||||
}
|
||||
Matrix J_1 = J.pseudoinverse();
|
||||
|
||||
SimpleMatrix deltaThetas = J_1.mult(deltaP);
|
||||
if (deltaThetas.elementMaxAbs() < MIN_ANGLE_CHANGE) {
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < deltaThetas.numRows(); ++j) {
|
||||
double angle = deltaThetas.get(j, 0);
|
||||
Vector3d rotationVector = rotationVectors[j];
|
||||
|
||||
tempDQuaternion.fromAngleAxis(angle, rotationVector);
|
||||
BoneContext boneContext = bones.get(j);
|
||||
Bone bone = boneContext.getBone();
|
||||
if (bone.equals(this.getOwner())) {
|
||||
if (boneContext.isLockX()) {
|
||||
tempDQuaternion.set(0, tempDQuaternion.getY(), tempDQuaternion.getZ(), tempDQuaternion.getW());
|
||||
}
|
||||
if (boneContext.isLockY()) {
|
||||
tempDQuaternion.set(tempDQuaternion.getX(), 0, tempDQuaternion.getZ(), tempDQuaternion.getW());
|
||||
}
|
||||
if (boneContext.isLockZ()) {
|
||||
tempDQuaternion.set(tempDQuaternion.getX(), tempDQuaternion.getY(), 0, tempDQuaternion.getW());
|
||||
}
|
||||
}
|
||||
|
||||
DTransform boneTransform = bones.getWorldTransform(boneContext);
|
||||
boneTransform.getRotation().set(tempDQuaternion.mult(boneTransform.getRotation()));
|
||||
bones.setWorldTransform(boneContext, boneTransform);
|
||||
}
|
||||
}
|
||||
|
||||
// applying the results
|
||||
for (int i = bonesCount - 1; i >= 0; --i) {
|
||||
BoneContext boneContext = bones.get(i);
|
||||
DTransform transform = bones.getWorldTransform(boneContext);
|
||||
constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform.toTransform());
|
||||
}
|
||||
bones = null;// need to reload them again
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Inverse kinematics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loaded bones' chain. This class allows to operate on transform matrices that use double precision in computations.
|
||||
* Only the final result is being transformed to single precision numbers.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
private static class BonesChain extends ArrayList<BoneContext> {
|
||||
private static final long serialVersionUID = -1850524345643600718L;
|
||||
|
||||
private List<Matrix> localBonesMatrices = new ArrayList<Matrix>();
|
||||
|
||||
public BonesChain(Bone bone, boolean useTail, int bonesAffected, Collection<Long> alteredOmas, BlenderContext blenderContext) {
|
||||
if (bone != null) {
|
||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||
if (!useTail) {
|
||||
bone = bone.getParent();
|
||||
}
|
||||
while (bone != null && (bonesAffected <= 0 || this.size() < bonesAffected)) {
|
||||
BoneContext boneContext = blenderContext.getBoneContext(bone);
|
||||
this.add(boneContext);
|
||||
alteredOmas.add(boneContext.getBoneOma());
|
||||
|
||||
Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
|
||||
localBonesMatrices.add(new DTransform(transform).toMatrix());
|
||||
|
||||
bone = bone.getParent();
|
||||
}
|
||||
|
||||
if(localBonesMatrices.size() > 0) {
|
||||
// making the matrices describe the local transformation
|
||||
Matrix parentWorldMatrix = localBonesMatrices.get(localBonesMatrices.size() - 1);
|
||||
for(int i=localBonesMatrices.size() - 2;i>=0;--i) {
|
||||
SimpleMatrix m = parentWorldMatrix.invert().mult(localBonesMatrices.get(i));
|
||||
parentWorldMatrix = localBonesMatrices.get(i);
|
||||
localBonesMatrices.set(i, new Matrix(m));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DTransform getWorldTransform(BoneContext bone) {
|
||||
int index = this.indexOf(bone);
|
||||
return this.getWorldMatrix(index).toTransform();
|
||||
}
|
||||
|
||||
public void setWorldTransform(BoneContext bone, DTransform transform) {
|
||||
int index = this.indexOf(bone);
|
||||
Matrix boneMatrix = transform.toMatrix();
|
||||
|
||||
if (index < this.size() - 1) {
|
||||
// computing the current bone local transform
|
||||
Matrix parentWorldMatrix = this.getWorldMatrix(index + 1);
|
||||
SimpleMatrix m = parentWorldMatrix.invert().mult(boneMatrix);
|
||||
boneMatrix = new Matrix(m);
|
||||
}
|
||||
localBonesMatrices.set(index, boneMatrix);
|
||||
}
|
||||
|
||||
public Matrix getWorldMatrix(int index) {
|
||||
if (index == this.size() - 1) {
|
||||
return new Matrix(localBonesMatrices.get(this.size() - 1));
|
||||
}
|
||||
|
||||
SimpleMatrix result = this.getWorldMatrix(index + 1);
|
||||
result = result.mult(localBonesMatrices.get(index));
|
||||
return new Matrix(result);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Loc like' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionLocLike extends ConstraintDefinition {
|
||||
private static final int LOCLIKE_X = 0x01;
|
||||
private static final int LOCLIKE_Y = 0x02;
|
||||
private static final int LOCLIKE_Z = 0x04;
|
||||
// protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in
|
||||
// blender
|
||||
private static final int LOCLIKE_X_INVERT = 0x10;
|
||||
private static final int LOCLIKE_Y_INVERT = 0x20;
|
||||
private static final int LOCLIKE_Z_INVERT = 0x40;
|
||||
private static final int LOCLIKE_OFFSET = 0x80;
|
||||
|
||||
public ConstraintDefinitionLocLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
// swapping Y and X limits flag in the bitwise flag
|
||||
int y = flag & LOCLIKE_Y;
|
||||
int invY = flag & LOCLIKE_Y_INVERT;
|
||||
int z = flag & LOCLIKE_Z;
|
||||
int invZ = flag & LOCLIKE_Z_INVERT;
|
||||
// clear the other flags to swap them
|
||||
flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;
|
||||
|
||||
flag |= y << 1;
|
||||
flag |= invY << 1;
|
||||
flag |= z >> 1;
|
||||
flag |= invZ >> 1;
|
||||
|
||||
trackToBeChanged = (flag & LOCLIKE_X) != 0 || (flag & LOCLIKE_Y) != 0 || (flag & LOCLIKE_Z) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrackToBeChanged() {
|
||||
// location copy does not work on bones who are connected to their parent
|
||||
return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || targetTransform == null || !this.isTrackToBeChanged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
|
||||
Vector3f ownerLocation = ownerTransform.getTranslation();
|
||||
Vector3f targetLocation = targetTransform.getTranslation();
|
||||
|
||||
Vector3f startLocation = ownerTransform.getTranslation().clone();
|
||||
Vector3f offset = Vector3f.ZERO;
|
||||
if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location
|
||||
offset = startLocation;
|
||||
}
|
||||
|
||||
if ((flag & LOCLIKE_X) != 0) {
|
||||
ownerLocation.x = targetLocation.x;
|
||||
if ((flag & LOCLIKE_X_INVERT) != 0) {
|
||||
ownerLocation.x = -ownerLocation.x;
|
||||
}
|
||||
}
|
||||
if ((flag & LOCLIKE_Y) != 0) {
|
||||
ownerLocation.y = targetLocation.y;
|
||||
if ((flag & LOCLIKE_Y_INVERT) != 0) {
|
||||
ownerLocation.y = -ownerLocation.y;
|
||||
}
|
||||
}
|
||||
if ((flag & LOCLIKE_Z) != 0) {
|
||||
ownerLocation.z = targetLocation.z;
|
||||
if ((flag & LOCLIKE_Z_INVERT) != 0) {
|
||||
ownerLocation.z = -ownerLocation.z;
|
||||
}
|
||||
}
|
||||
ownerLocation.addLocal(offset);
|
||||
|
||||
if (influence < 1.0f) {
|
||||
startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
|
||||
ownerLocation.addLocal(startLocation);
|
||||
}
|
||||
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Copy location";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Loc limit' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition {
|
||||
private static final int LIMIT_XMIN = 0x01;
|
||||
private static final int LIMIT_XMAX = 0x02;
|
||||
private static final int LIMIT_YMIN = 0x04;
|
||||
private static final int LIMIT_YMAX = 0x08;
|
||||
private static final int LIMIT_ZMIN = 0x10;
|
||||
private static final int LIMIT_ZMAX = 0x20;
|
||||
|
||||
protected float[][] limits = new float[3][2];
|
||||
|
||||
public ConstraintDefinitionLocLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
|
||||
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
|
||||
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
|
||||
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
|
||||
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
|
||||
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
|
||||
|
||||
// swapping Y and X limits flag in the bitwise flag
|
||||
int ymin = flag & LIMIT_YMIN;
|
||||
int ymax = flag & LIMIT_YMAX;
|
||||
int zmin = flag & LIMIT_ZMIN;
|
||||
int zmax = flag & LIMIT_ZMAX;
|
||||
flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap
|
||||
// them
|
||||
flag |= ymin << 2;
|
||||
flag |= ymax << 2;
|
||||
flag |= zmin >> 2;
|
||||
flag |= zmax >> 2;
|
||||
} else {
|
||||
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
|
||||
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
|
||||
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
|
||||
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
|
||||
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
|
||||
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
|
||||
}
|
||||
|
||||
trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrackToBeChanged() {
|
||||
// location limit does not work on bones who are connected to their parent
|
||||
return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || !this.isTrackToBeChanged()) {
|
||||
return;// no need to do anything
|
||||
}
|
||||
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
|
||||
Vector3f translation = ownerTransform.getTranslation();
|
||||
|
||||
if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
|
||||
translation.x -= (translation.x - limits[0][0]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) {
|
||||
translation.x -= (translation.x - limits[0][1]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) {
|
||||
translation.y -= (translation.y - limits[1][0]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) {
|
||||
translation.y -= (translation.y - limits[1][1]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) {
|
||||
translation.z -= (translation.z - limits[2][0]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) {
|
||||
translation.z -= (translation.z - limits[2][1]) * influence;
|
||||
}
|
||||
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Limit location";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Maintain volume' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
|
||||
private static final int FLAG_MASK_X = 0;
|
||||
private static final int FLAG_MASK_Y = 1;
|
||||
private static final int FLAG_MASK_Z = 2;
|
||||
|
||||
private float volume;
|
||||
|
||||
public ConstraintDefinitionMaintainVolume(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
volume = (float) Math.sqrt(((Number) constraintData.getFieldValue("volume")).floatValue());
|
||||
trackToBeChanged = volume != 1 && (flag & (FLAG_MASK_X | FLAG_MASK_Y | FLAG_MASK_Z)) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (trackToBeChanged && influence > 0) {
|
||||
// the maintain volume constraint is applied directly to object's scale, so no need to do it again
|
||||
// but in case of bones we need to make computations
|
||||
if (this.getOwner() instanceof Bone) {
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
switch (flag) {
|
||||
case FLAG_MASK_X:
|
||||
ownerTransform.getScale().multLocal(1, volume, volume);
|
||||
break;
|
||||
case FLAG_MASK_Y:
|
||||
ownerTransform.getScale().multLocal(volume, 1, volume);
|
||||
break;
|
||||
case FLAG_MASK_Z:
|
||||
ownerTransform.getScale().multLocal(volume, volume, 1);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown flag value: " + flag);
|
||||
}
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Maintain volume";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Null' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionNull extends ConstraintDefinition {
|
||||
|
||||
public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
trackToBeChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
// null constraint does nothing so no need to implement this one
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Rot like' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionRotLike extends ConstraintDefinition {
|
||||
private static final int ROTLIKE_X = 0x01;
|
||||
private static final int ROTLIKE_Y = 0x02;
|
||||
private static final int ROTLIKE_Z = 0x04;
|
||||
private static final int ROTLIKE_X_INVERT = 0x10;
|
||||
private static final int ROTLIKE_Y_INVERT = 0x20;
|
||||
private static final int ROTLIKE_Z_INVERT = 0x40;
|
||||
private static final int ROTLIKE_OFFSET = 0x80;
|
||||
|
||||
private transient float[] ownerAngles = new float[3];
|
||||
private transient float[] targetAngles = new float[3];
|
||||
|
||||
public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
trackToBeChanged = (flag & (ROTLIKE_X | ROTLIKE_Y | ROTLIKE_Z)) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || targetTransform == null || !trackToBeChanged) {
|
||||
return;// no need to do anything
|
||||
}
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
|
||||
Quaternion ownerRotation = ownerTransform.getRotation();
|
||||
ownerAngles = ownerRotation.toAngles(ownerAngles);
|
||||
targetAngles = targetTransform.getRotation().toAngles(targetAngles);
|
||||
|
||||
Quaternion startRotation = ownerRotation.clone();
|
||||
Quaternion offset = Quaternion.IDENTITY;
|
||||
if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to
|
||||
// the copied rotation
|
||||
offset = startRotation;
|
||||
}
|
||||
|
||||
if ((flag & ROTLIKE_X) != 0) {
|
||||
ownerAngles[0] = targetAngles[0];
|
||||
if ((flag & ROTLIKE_X_INVERT) != 0) {
|
||||
ownerAngles[0] = -ownerAngles[0];
|
||||
}
|
||||
}
|
||||
if ((flag & ROTLIKE_Y) != 0) {
|
||||
ownerAngles[1] = targetAngles[1];
|
||||
if ((flag & ROTLIKE_Y_INVERT) != 0) {
|
||||
ownerAngles[1] = -ownerAngles[1];
|
||||
}
|
||||
}
|
||||
if ((flag & ROTLIKE_Z) != 0) {
|
||||
ownerAngles[2] = targetAngles[2];
|
||||
if ((flag & ROTLIKE_Z_INVERT) != 0) {
|
||||
ownerAngles[2] = -ownerAngles[2];
|
||||
}
|
||||
}
|
||||
ownerRotation.fromAngles(ownerAngles).multLocal(offset);
|
||||
|
||||
if (influence < 1.0f) {
|
||||
// startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
|
||||
// ownerLocation.addLocal(startLocation);
|
||||
// TODO
|
||||
}
|
||||
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Copy rotation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Rot limit' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition {
|
||||
private static final int LIMIT_XROT = 0x01;
|
||||
private static final int LIMIT_YROT = 0x02;
|
||||
private static final int LIMIT_ZROT = 0x04;
|
||||
|
||||
private transient float[][] limits = new float[3][2];
|
||||
private transient float[] angles = new float[3];
|
||||
|
||||
public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
|
||||
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
|
||||
limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
|
||||
limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
|
||||
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
|
||||
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
|
||||
|
||||
// swapping Y and X limits flag in the bitwise flag
|
||||
int limitY = flag & LIMIT_YROT;
|
||||
int limitZ = flag & LIMIT_ZROT;
|
||||
flag &= LIMIT_XROT;// clear the other flags to swap them
|
||||
flag |= limitY << 1;
|
||||
flag |= limitZ >> 1;
|
||||
} else {
|
||||
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
|
||||
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
|
||||
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
|
||||
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
|
||||
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
|
||||
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
|
||||
}
|
||||
|
||||
// until blender 2.49 the rotations values were stored in degrees
|
||||
if (blenderContext.getBlenderVersion() <= 249) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
limits[i][0] *= FastMath.DEG_TO_RAD;
|
||||
limits[i][1] *= FastMath.DEG_TO_RAD;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure that the limits are always in range [0, 2PI)
|
||||
// TODO: left it here because it is essential to make sure all cases
|
||||
// work poperly
|
||||
// but will do it a little bit later ;)
|
||||
/*
|
||||
* for (int i = 0; i < 3; ++i) { for (int j = 0; j < 2; ++j) { int
|
||||
* multFactor = (int)Math.abs(limits[i][j] / FastMath.TWO_PI) ; if
|
||||
* (limits[i][j] < 0) { limits[i][j] += FastMath.TWO_PI * (multFactor +
|
||||
* 1); } else { limits[i][j] -= FastMath.TWO_PI * multFactor; } } //make
|
||||
* sure the lower limit is not greater than the upper one
|
||||
* if(limits[i][0] > limits[i][1]) { float temp = limits[i][0];
|
||||
* limits[i][0] = limits[i][1]; limits[i][1] = temp; } }
|
||||
*/
|
||||
|
||||
trackToBeChanged = (flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT)) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || !trackToBeChanged) {
|
||||
return;
|
||||
}
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
|
||||
ownerTransform.getRotation().toAngles(angles);
|
||||
// make sure that the rotations are always in range [0, 2PI)
|
||||
// TODO: same comment as in constructor
|
||||
/*
|
||||
* for (int i = 0; i < 3; ++i) { int multFactor =
|
||||
* (int)Math.abs(angles[i] / FastMath.TWO_PI) ; if(angles[i] < 0) {
|
||||
* angles[i] += FastMath.TWO_PI * (multFactor + 1); } else { angles[i]
|
||||
* -= FastMath.TWO_PI * multFactor; } }
|
||||
*/
|
||||
if ((flag & LIMIT_XROT) != 0) {
|
||||
float difference = 0.0f;
|
||||
if (angles[0] < limits[0][0]) {
|
||||
difference = (angles[0] - limits[0][0]) * influence;
|
||||
} else if (angles[0] > limits[0][1]) {
|
||||
difference = (angles[0] - limits[0][1]) * influence;
|
||||
}
|
||||
angles[0] -= difference;
|
||||
}
|
||||
if ((flag & LIMIT_YROT) != 0) {
|
||||
float difference = 0.0f;
|
||||
if (angles[1] < limits[1][0]) {
|
||||
difference = (angles[1] - limits[1][0]) * influence;
|
||||
} else if (angles[1] > limits[1][1]) {
|
||||
difference = (angles[1] - limits[1][1]) * influence;
|
||||
}
|
||||
angles[1] -= difference;
|
||||
}
|
||||
if ((flag & LIMIT_ZROT) != 0) {
|
||||
float difference = 0.0f;
|
||||
if (angles[2] < limits[2][0]) {
|
||||
difference = (angles[2] - limits[2][0]) * influence;
|
||||
} else if (angles[2] > limits[2][1]) {
|
||||
difference = (angles[2] - limits[2][1]) * influence;
|
||||
}
|
||||
angles[2] -= difference;
|
||||
}
|
||||
ownerTransform.getRotation().fromAngles(angles);
|
||||
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Limit rotation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Size like' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition {
|
||||
private static final int SIZELIKE_X = 0x01;
|
||||
private static final int SIZELIKE_Y = 0x02;
|
||||
private static final int SIZELIKE_Z = 0x04;
|
||||
private static final int LOCLIKE_OFFSET = 0x80;
|
||||
|
||||
public ConstraintDefinitionSizeLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
// swapping Y and X limits flag in the bitwise flag
|
||||
int y = flag & SIZELIKE_Y;
|
||||
int z = flag & SIZELIKE_Z;
|
||||
flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap
|
||||
// them
|
||||
flag |= y << 1;
|
||||
flag |= z >> 1;
|
||||
|
||||
trackToBeChanged = (flag & (SIZELIKE_X | SIZELIKE_Y | SIZELIKE_Z)) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || targetTransform == null || !trackToBeChanged) {
|
||||
return;// no need to do anything
|
||||
}
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
|
||||
Vector3f ownerScale = ownerTransform.getScale();
|
||||
Vector3f targetScale = targetTransform.getScale();
|
||||
|
||||
Vector3f offset = Vector3f.ZERO;
|
||||
if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the
|
||||
// copied scale
|
||||
offset = ownerScale.clone();
|
||||
}
|
||||
|
||||
if ((flag & SIZELIKE_X) != 0) {
|
||||
ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x;
|
||||
}
|
||||
if ((flag & SIZELIKE_Y) != 0) {
|
||||
ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y;
|
||||
}
|
||||
if ((flag & SIZELIKE_Z) != 0) {
|
||||
ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z;
|
||||
}
|
||||
ownerScale.addLocal(offset);
|
||||
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Copy scale";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* This class represents 'Size limit' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition {
|
||||
private static final int LIMIT_XMIN = 0x01;
|
||||
private static final int LIMIT_XMAX = 0x02;
|
||||
private static final int LIMIT_YMIN = 0x04;
|
||||
private static final int LIMIT_YMAX = 0x08;
|
||||
private static final int LIMIT_ZMIN = 0x10;
|
||||
private static final int LIMIT_ZMAX = 0x20;
|
||||
|
||||
protected transient float[][] limits = new float[3][2];
|
||||
|
||||
public ConstraintDefinitionSizeLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
|
||||
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
|
||||
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
|
||||
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
|
||||
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
|
||||
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
|
||||
|
||||
// swapping Y and X limits flag in the bitwise flag
|
||||
int ymin = flag & LIMIT_YMIN;
|
||||
int ymax = flag & LIMIT_YMAX;
|
||||
int zmin = flag & LIMIT_ZMIN;
|
||||
int zmax = flag & LIMIT_ZMAX;
|
||||
flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap
|
||||
// them
|
||||
flag |= ymin << 2;
|
||||
flag |= ymax << 2;
|
||||
flag |= zmin >> 2;
|
||||
flag |= zmax >> 2;
|
||||
} else {
|
||||
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
|
||||
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
|
||||
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
|
||||
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
|
||||
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
|
||||
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
|
||||
}
|
||||
|
||||
trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || !trackToBeChanged) {
|
||||
return;
|
||||
}
|
||||
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
|
||||
|
||||
Vector3f scale = ownerTransform.getScale();
|
||||
if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) {
|
||||
scale.x -= (scale.x - limits[0][0]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) {
|
||||
scale.x -= (scale.x - limits[0][1]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) {
|
||||
scale.y -= (scale.y - limits[1][0]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) {
|
||||
scale.y -= (scale.y - limits[1][1]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) {
|
||||
scale.z -= (scale.z - limits[2][0]) * influence;
|
||||
}
|
||||
if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) {
|
||||
scale.z -= (scale.z - limits[2][1]) * influence;
|
||||
}
|
||||
|
||||
this.applyOwnerTransform(ownerTransform, ownerSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Limit scale";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
import com.jme3.util.TempVars;
|
||||
|
||||
/**
|
||||
* This class represents 'Trans like' constraint type in blender.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class ConstraintDefinitionTransLike extends ConstraintDefinition {
|
||||
private Long targetOMA;
|
||||
private String subtargetName;
|
||||
|
||||
public ConstraintDefinitionTransLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
|
||||
super(constraintData, ownerOMA, blenderContext);
|
||||
Pointer pTarget = (Pointer) constraintData.getFieldValue("tar");
|
||||
targetOMA = pTarget.getOldMemoryAddress();
|
||||
Object subtarget = constraintData.getFieldValue("subtarget");
|
||||
if (subtarget != null) {
|
||||
subtargetName = subtarget.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
if (influence == 0 || targetTransform == null) {
|
||||
return;// no need to do anything
|
||||
}
|
||||
Object target = this.getTarget();// Bone or Node
|
||||
Object owner = this.getOwner();// Bone or Node
|
||||
if (!target.getClass().equals(owner.getClass())) {
|
||||
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
|
||||
|
||||
TempVars tempVars = TempVars.get();
|
||||
Matrix4f m = constraintHelper.toMatrix(targetTransform, tempVars.tempMat4);
|
||||
tempVars.tempMat42.set(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX);
|
||||
if (target instanceof Bone) {
|
||||
tempVars.tempMat42.invertLocal();
|
||||
}
|
||||
m = m.multLocal(tempVars.tempMat42);
|
||||
tempVars.release();
|
||||
|
||||
targetTransform = new Transform(m.toTranslationVector(), m.toRotationQuat(), m.toScaleVector());
|
||||
}
|
||||
this.applyOwnerTransform(targetTransform, ownerSpace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the target feature; it is either Node or Bone (vertex group subtarger is not yet supported)
|
||||
*/
|
||||
private Object getTarget() {
|
||||
Object target = blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
|
||||
if (subtargetName != null && blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, target) != null) {
|
||||
Skeleton skeleton = blenderContext.getSkeleton(targetOMA);
|
||||
target = skeleton.getBone(subtargetName);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return "Copy transforms";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.constraints.definitions;
|
||||
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
|
||||
|
||||
/**
|
||||
* This class represents a constraint that is defined by blender but not
|
||||
* supported by either importer ot jme. It only wirtes down a warning when
|
||||
* baking is called.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition {
|
||||
private String typeName;
|
||||
|
||||
public UnsupportedConstraintDefinition(String typeName) {
|
||||
super(null, null, null);
|
||||
this.typeName = typeName;
|
||||
trackToBeChanged = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isImplemented() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstraintTypeName() {
|
||||
return typeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTargetRequired() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.curves;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize
|
||||
* floating point operations errors.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class BezierCurve {
|
||||
private static final int IPO_CONSTANT = 0;
|
||||
private static final int IPO_LINEAR = 1;
|
||||
private static final int IPO_BEZIER = 2;
|
||||
|
||||
public static final int X_VALUE = 0;
|
||||
public static final int Y_VALUE = 1;
|
||||
public static final int Z_VALUE = 2;
|
||||
/**
|
||||
* The type of the curve. Describes the data it modifies.
|
||||
* Used in ipos calculations.
|
||||
*/
|
||||
private int type;
|
||||
/** The dimension of the curve. */
|
||||
private int dimension;
|
||||
/** A table of the bezier points. */
|
||||
private double[][][] bezierPoints;
|
||||
/** Array that stores a radius for each bezier triple. */
|
||||
private double[] radiuses;
|
||||
/** Interpolation types of the bezier triples. */
|
||||
private int[] interpolations;
|
||||
|
||||
public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) {
|
||||
this(type, bezTriples, dimension, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension, boolean fixUpAxis) {
|
||||
if (dimension != 2 && dimension != 3) {
|
||||
throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!");
|
||||
}
|
||||
this.type = type;
|
||||
this.dimension = dimension;
|
||||
// first index of the bezierPoints table has the length of triples amount
|
||||
// the second index points to a table od three points of a bezier triple (handle, point, handle)
|
||||
// the third index specifies the coordinates of the specific point in a bezier triple
|
||||
bezierPoints = new double[bezTriples.size()][3][dimension];
|
||||
radiuses = new double[bezTriples.size()];
|
||||
interpolations = new int[bezTriples.size()];
|
||||
int i = 0, j, k;
|
||||
for (Structure bezTriple : bezTriples) {
|
||||
DynamicArray<Number> vec = (DynamicArray<Number>) bezTriple.getFieldValue("vec");
|
||||
for (j = 0; j < 3; ++j) {
|
||||
for (k = 0; k < dimension; ++k) {
|
||||
bezierPoints[i][j][k] = vec.get(j, k).doubleValue();
|
||||
}
|
||||
if (fixUpAxis && dimension == 3) {
|
||||
double temp = bezierPoints[i][j][2];
|
||||
bezierPoints[i][j][2] = -bezierPoints[i][j][1];
|
||||
bezierPoints[i][j][1] = temp;
|
||||
}
|
||||
}
|
||||
radiuses[i] = ((Number) bezTriple.getFieldValue("radius")).floatValue();
|
||||
interpolations[i++] = ((Number) bezTriple.getFieldValue("ipo", IPO_BEZIER)).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method evaluates the data for the specified frame. The Y value is returned.
|
||||
* @param frame
|
||||
* the frame for which the value is being calculated
|
||||
* @param valuePart
|
||||
* this param specifies wheather we should return the X, Y or Z part of the result value; it should have
|
||||
* one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result
|
||||
* Z_VALUE - the Z factor of the result
|
||||
* @return the value of the curve
|
||||
*/
|
||||
public double evaluate(int frame, int valuePart) {
|
||||
for (int i = 0; i < bezierPoints.length - 1; ++i) {
|
||||
if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) {
|
||||
double t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]);
|
||||
switch (interpolations[i]) {
|
||||
case IPO_BEZIER:
|
||||
double oneMinusT = 1.0f - t;
|
||||
double oneMinusT2 = oneMinusT * oneMinusT;
|
||||
double t2 = t * t;
|
||||
return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t;
|
||||
case IPO_LINEAR:
|
||||
return (1f - t) * bezierPoints[i][1][valuePart] + t * bezierPoints[i + 1][1][valuePart];
|
||||
case IPO_CONSTANT:
|
||||
return bezierPoints[i][1][valuePart];
|
||||
default:
|
||||
throw new IllegalStateException("Unknown interpolation type for curve: " + interpolations[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (frame < bezierPoints[0][1][0]) {
|
||||
return bezierPoints[0][1][1];
|
||||
} else { // frame>bezierPoints[bezierPoints.length-1][1][0]
|
||||
return bezierPoints[bezierPoints.length - 1][1][1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the frame where last bezier triple center point of the bezier curve is located.
|
||||
* @return the frame number of the last defined bezier triple point for the curve
|
||||
*/
|
||||
public int getLastFrame() {
|
||||
return (int) bezierPoints[bezierPoints.length - 1][1][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the type of the bezier curve. The type describes the parameter that this curve modifies
|
||||
* (ie. LocationX or rotationW of the feature).
|
||||
* @return the type of the bezier curve
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method returns the radius for the required bezier triple.
|
||||
*
|
||||
* @param bezierTripleIndex
|
||||
* index of the bezier triple
|
||||
* @return radius of the required bezier triple
|
||||
*/
|
||||
public double getRadius(int bezierTripleIndex) {
|
||||
return radiuses[bezierTripleIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a list of control points for this curve.
|
||||
* @return a list of control points for this curve.
|
||||
*/
|
||||
public List<Vector3f> getControlPoints() {
|
||||
List<Vector3f> controlPoints = new ArrayList<Vector3f>(bezierPoints.length * 3);
|
||||
for (int i = 0; i < bezierPoints.length; ++i) {
|
||||
controlPoints.add(new Vector3f((float) bezierPoints[i][0][0], (float) bezierPoints[i][0][1], (float) bezierPoints[i][0][2]));
|
||||
controlPoints.add(new Vector3f((float) bezierPoints[i][1][0], (float) bezierPoints[i][1][1], (float) bezierPoints[i][1][2]));
|
||||
controlPoints.add(new Vector3f((float) bezierPoints[i][2][0], (float) bezierPoints[i][2][1], (float) bezierPoints[i][2][2]));
|
||||
}
|
||||
return controlPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n');
|
||||
for (int i = 0; i < bezierPoints.length; ++i) {
|
||||
sb.append(this.toStringBezTriple(i)).append('\n');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the bezier triple of a specified index into text.
|
||||
* @param tripleIndex
|
||||
* index of the triple
|
||||
* @return text representation of the triple
|
||||
*/
|
||||
private String toStringBezTriple(int tripleIndex) {
|
||||
if (dimension == 2) {
|
||||
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]";
|
||||
} else {
|
||||
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.curves;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* A class that is used in mesh calculations.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class CurvesHelper extends AbstractBlenderHelper {
|
||||
private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName());
|
||||
|
||||
/** Minimum basis U function degree for NURBS curves and surfaces. */
|
||||
protected int minimumBasisUFunctionDegree = 4;
|
||||
/** Minimum basis V function degree for NURBS curves and surfaces. */
|
||||
protected int minimumBasisVFunctionDegree = 4;
|
||||
|
||||
/**
|
||||
* This constructor parses the given blender version and stores the result. Some functionalities may differ in
|
||||
* different blender versions.
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public CurvesHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
public CurvesTemporalMesh toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
CurvesTemporalMesh result = new CurvesTemporalMesh(curveStructure, blenderContext);
|
||||
|
||||
if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
|
||||
LOGGER.fine("Reading custom properties.");
|
||||
result.setProperties(this.loadProperties(curveStructure, blenderContext));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method transforms the bevel along the curve.
|
||||
*
|
||||
* @param bevel
|
||||
* the bevel to be transformed
|
||||
* @param prevPos
|
||||
* previous curve point
|
||||
* @param currPos
|
||||
* current curve point (here the center of the new bevel will be
|
||||
* set)
|
||||
* @param nextPos
|
||||
* next curve point
|
||||
* @return points of transformed bevel
|
||||
*/
|
||||
protected Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) {
|
||||
bevel = bevel.clone();
|
||||
|
||||
// currPos and directionVector define the line in 3D space
|
||||
Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos);
|
||||
directionVector.normalizeLocal();
|
||||
|
||||
// plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz)
|
||||
Vector3f planeNormal = null;
|
||||
if (prevPos != null) {
|
||||
planeNormal = currPos.subtract(prevPos).normalizeLocal();
|
||||
if (nextPos != null) {
|
||||
planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal();
|
||||
}
|
||||
} else {
|
||||
planeNormal = nextPos.subtract(currPos).normalizeLocal();
|
||||
}
|
||||
float D = -planeNormal.dot(currPos);// D = -(Ax + By + Cz)
|
||||
|
||||
// now we need to compute paralell cast of each bevel point on the plane, the leading line is already known
|
||||
// parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t
|
||||
// where p = currPos and v = directionVector
|
||||
// using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross
|
||||
float temp = planeNormal.dot(directionVector);
|
||||
for (int i = 0; i < bevel.length; ++i) {
|
||||
float t = -(planeNormal.dot(bevel[i]) + D) / temp;
|
||||
if (fixUpAxis) {
|
||||
bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, bevel[i].y + directionVector.y * t, bevel[i].z + directionVector.z * t);
|
||||
} else {
|
||||
bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, -bevel[i].z + directionVector.z * t, bevel[i].y + directionVector.y * t);
|
||||
}
|
||||
}
|
||||
return bevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method transforms the first line of the bevel points positioning it
|
||||
* on the first point of the curve.
|
||||
*
|
||||
* @param startingLinePoints
|
||||
* the vbevel shape points
|
||||
* @param firstCurvePoint
|
||||
* the first curve's point
|
||||
* @param secondCurvePoint
|
||||
* the second curve's point
|
||||
* @return points of transformed bevel
|
||||
*/
|
||||
protected Vector3f[] transformToFirstLineOfBevelPoints(Vector3f[] startingLinePoints, Vector3f firstCurvePoint, Vector3f secondCurvePoint) {
|
||||
Vector3f planeNormal = secondCurvePoint.subtract(firstCurvePoint).normalizeLocal();
|
||||
|
||||
float angle = FastMath.acos(planeNormal.dot(Vector3f.UNIT_X));
|
||||
Vector3f rotationVector = Vector3f.UNIT_X.cross(planeNormal).normalizeLocal();
|
||||
|
||||
Matrix4f m = new Matrix4f();
|
||||
m.setRotationQuaternion(new Quaternion().fromAngleAxis(angle, rotationVector));
|
||||
m.setTranslation(firstCurvePoint);
|
||||
|
||||
Vector3f temp = new Vector3f();
|
||||
Vector3f[] verts = new Vector3f[startingLinePoints.length];
|
||||
for (int i = 0; i < verts.length; ++i) {
|
||||
verts[i] = m.mult(startingLinePoints[i], temp).clone();
|
||||
}
|
||||
return verts;
|
||||
}
|
||||
}
|
@ -1,890 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.curves;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.material.RenderState.FaceCullMode;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Spline;
|
||||
import com.jme3.math.Spline.SplineType;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.math.Vector4f;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.mesh.IndexBuffer;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialContext;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
||||
import com.jme3.scene.plugins.blender.meshes.Edge;
|
||||
import com.jme3.scene.plugins.blender.meshes.Face;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
import com.jme3.scene.shape.Curve;
|
||||
import com.jme3.scene.shape.Surface;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
/**
|
||||
* A temporal mesh for curves and surfaces. It works in similar way as TemporalMesh for meshes.
|
||||
* It prepares all necessary lines and faces and allows to apply modifiers just like in regular temporal mesh.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class CurvesTemporalMesh extends TemporalMesh {
|
||||
private static final Logger LOGGER = Logger.getLogger(CurvesTemporalMesh.class.getName());
|
||||
|
||||
private static final int TYPE_BEZIER = 0x0001;
|
||||
private static final int TYPE_NURBS = 0x0004;
|
||||
|
||||
private static final int FLAG_3D = 0x0001;
|
||||
private static final int FLAG_FRONT = 0x0002;
|
||||
private static final int FLAG_BACK = 0x0004;
|
||||
private static final int FLAG_FILL_CAPS = 0x4000;
|
||||
|
||||
private static final int FLAG_SMOOTH = 0x0001;
|
||||
|
||||
protected CurvesHelper curvesHelper;
|
||||
protected boolean is2D;
|
||||
protected boolean isFront;
|
||||
protected boolean isBack;
|
||||
protected boolean fillCaps;
|
||||
protected float bevelStart;
|
||||
protected float bevelEnd;
|
||||
protected List<BezierLine> beziers = new ArrayList<BezierLine>();
|
||||
protected CurvesTemporalMesh bevelObject;
|
||||
protected CurvesTemporalMesh taperObject;
|
||||
/** The scale that is used if the curve is a bevel or taper curve. */
|
||||
protected Vector3f scale = new Vector3f(1, 1, 1);
|
||||
|
||||
/**
|
||||
* The constructor creates an empty temporal mesh.
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this will never be thrown here
|
||||
*/
|
||||
protected CurvesTemporalMesh(BlenderContext blenderContext) throws BlenderFileException {
|
||||
super(null, blenderContext, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface.
|
||||
* @param curveStructure
|
||||
* the structure that contains the curve/surface data
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with reading occur
|
||||
*/
|
||||
public CurvesTemporalMesh(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
this(curveStructure, new Vector3f(1, 1, 1), true, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface.
|
||||
* @param curveStructure
|
||||
* the structure that contains the curve/surface data
|
||||
* @param scale
|
||||
* the scale used if the current curve is used as a bevel curve
|
||||
* @param loadBevelAndTaper indicates if bevel and taper should be loaded (this is not needed for curves that are loaded to be used as bevel and taper)
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with reading occur
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private CurvesTemporalMesh(Structure curveStructure, Vector3f scale, boolean loadBevelAndTaper, BlenderContext blenderContext) throws BlenderFileException {
|
||||
super(curveStructure, blenderContext, false);
|
||||
name = curveStructure.getName();
|
||||
curvesHelper = blenderContext.getHelper(CurvesHelper.class);
|
||||
this.scale = scale;
|
||||
|
||||
int flag = ((Number) curveStructure.getFieldValue("flag")).intValue();
|
||||
is2D = (flag & FLAG_3D) == 0;
|
||||
if (is2D) {
|
||||
// TODO: add support for 3D flag
|
||||
LOGGER.warning("2D flag not yet supported for curves!");
|
||||
}
|
||||
isFront = (flag & FLAG_FRONT) != 0;
|
||||
isBack = (flag & FLAG_BACK) != 0;
|
||||
fillCaps = (flag & FLAG_FILL_CAPS) != 0;
|
||||
bevelStart = ((Number) curveStructure.getFieldValue("bevfac1", 0)).floatValue();
|
||||
bevelEnd = ((Number) curveStructure.getFieldValue("bevfac2", 1)).floatValue();
|
||||
if (bevelStart > bevelEnd) {
|
||||
float temp = bevelStart;
|
||||
bevelStart = bevelEnd;
|
||||
bevelEnd = temp;
|
||||
}
|
||||
|
||||
LOGGER.fine("Reading nurbs (and sorting them by material).");
|
||||
Map<Number, List<Structure>> nurbs = new HashMap<Number, List<Structure>>();
|
||||
List<Structure> nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase();
|
||||
for (Structure nurb : nurbStructures) {
|
||||
Number matNumber = (Number) nurb.getFieldValue("mat_nr");
|
||||
List<Structure> nurbList = nurbs.get(matNumber);
|
||||
if (nurbList == null) {
|
||||
nurbList = new ArrayList<Structure>();
|
||||
nurbs.put(matNumber, nurbList);
|
||||
}
|
||||
nurbList.add(nurb);
|
||||
}
|
||||
|
||||
LOGGER.fine("Getting materials.");
|
||||
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
|
||||
materials = materialHelper.getMaterials(curveStructure, blenderContext);
|
||||
if (materials != null) {
|
||||
for (MaterialContext materialContext : materials) {
|
||||
materialContext.setFaceCullMode(FaceCullMode.Off);
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.fine("Getting or creating bevel object.");
|
||||
bevelObject = loadBevelAndTaper ? this.loadBevelObject(curveStructure) : null;
|
||||
|
||||
LOGGER.fine("Getting taper object.");
|
||||
Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj");
|
||||
if (bevelObject != null && pTaperObject.isNotNull()) {
|
||||
Structure taperObjectStructure = pTaperObject.fetchData().get(0);
|
||||
DynamicArray<Number> scaleArray = (DynamicArray<Number>) taperObjectStructure.getFieldValue("size");
|
||||
scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue());
|
||||
Pointer pTaperStructure = (Pointer) taperObjectStructure.getFieldValue("data");
|
||||
Structure taperStructure = pTaperStructure.fetchData().get(0);
|
||||
taperObject = new CurvesTemporalMesh(taperStructure, blenderContext);
|
||||
}
|
||||
|
||||
LOGGER.fine("Creating the result curves.");
|
||||
for (Entry<Number, List<Structure>> nurbEntry : nurbs.entrySet()) {
|
||||
for (Structure nurb : nurbEntry.getValue()) {
|
||||
int type = ((Number) nurb.getFieldValue("type")).intValue();
|
||||
if ((type & TYPE_BEZIER) != 0) {
|
||||
this.loadBezierCurve(nurb, nurbEntry.getKey().intValue());
|
||||
} else if ((type & TYPE_NURBS) != 0) {
|
||||
this.loadNurbSurface(nurb, nurbEntry.getKey().intValue());
|
||||
} else {
|
||||
throw new BlenderFileException("Unknown curve type: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bevelObject != null && beziers.size() > 0) {
|
||||
this.append(this.applyBevelAndTaper(this, bevelObject, taperObject, blenderContext));
|
||||
} else {
|
||||
for (BezierLine bezierLine : beziers) {
|
||||
int originalVerticesAmount = vertices.size();
|
||||
vertices.add(bezierLine.vertices[0]);
|
||||
Vector3f v = bezierLine.vertices[1].subtract(bezierLine.vertices[0]).normalizeLocal();
|
||||
float temp = v.x;
|
||||
v.x = -v.y;
|
||||
v.y = temp;
|
||||
v.z = 0;
|
||||
normals.add(v);// this will be smoothed in the next iteration
|
||||
|
||||
for (int i = 1; i < bezierLine.vertices.length; ++i) {
|
||||
vertices.add(bezierLine.vertices[i]);
|
||||
edges.add(new Edge(originalVerticesAmount + i - 1, originalVerticesAmount + i, 0, false, this));
|
||||
|
||||
// generating normal for vertex at 'i'
|
||||
v = bezierLine.vertices[i].subtract(bezierLine.vertices[i - 1]).normalizeLocal();
|
||||
temp = v.x;
|
||||
v.x = -v.y;
|
||||
v.y = temp;
|
||||
v.z = 0;
|
||||
|
||||
// make the previous normal smooth
|
||||
normals.get(i - 1).addLocal(v).multLocal(0.5f).normalizeLocal();
|
||||
normals.add(v);// this will be smoothed in the next iteration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method computes the value of a point at the certain relational distance from its beginning.
|
||||
* @param alongRatio
|
||||
* the relative distance along the curve; should be a value between 0 and 1 inclusive;
|
||||
* if the value exceeds the boundaries it is truncated to them
|
||||
* @return computed value along the curve
|
||||
*/
|
||||
private Vector3f getValueAlongCurve(float alongRatio) {
|
||||
alongRatio = FastMath.clamp(alongRatio, 0, 1);
|
||||
Vector3f result = new Vector3f();
|
||||
float probeLength = this.getLength() * alongRatio, length = 0;
|
||||
for (BezierLine bezier : beziers) {
|
||||
float edgeLength = bezier.getLength();
|
||||
if (length + edgeLength >= probeLength) {
|
||||
float ratioAlongEdge = (probeLength - length) / edgeLength;
|
||||
return bezier.getValueAlongCurve(ratioAlongEdge);
|
||||
}
|
||||
length += edgeLength;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of the curve
|
||||
*/
|
||||
private float getLength() {
|
||||
float result = 0;
|
||||
for (BezierLine bezier : beziers) {
|
||||
result += bezier.getLength();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The methods loads the bezier curve from the given structure.
|
||||
* @param nurbStructure
|
||||
* the structure containing a single curve definition
|
||||
* @param materialIndex
|
||||
* the index of this segment's material
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with reading occur
|
||||
*/
|
||||
private void loadBezierCurve(Structure nurbStructure, int materialIndex) throws BlenderFileException {
|
||||
Pointer pBezierTriple = (Pointer) nurbStructure.getFieldValue("bezt");
|
||||
if (pBezierTriple.isNotNull()) {
|
||||
int resolution = ((Number) nurbStructure.getFieldValue("resolu")).intValue();
|
||||
boolean cyclic = (((Number) nurbStructure.getFieldValue("flagu")).intValue() & 0x01) != 0;
|
||||
boolean smooth = (((Number) nurbStructure.getFieldValue("flag")).intValue() & FLAG_SMOOTH) != 0;
|
||||
|
||||
// creating the curve object
|
||||
BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 3, blenderContext.getBlenderKey().isFixUpAxis());
|
||||
List<Vector3f> controlPoints = bezierCurve.getControlPoints();
|
||||
|
||||
if (cyclic) {
|
||||
// copy the first three points at the end
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
controlPoints.add(controlPoints.get(i));
|
||||
}
|
||||
}
|
||||
// removing the first and last handles
|
||||
controlPoints.remove(0);
|
||||
controlPoints.remove(controlPoints.size() - 1);
|
||||
|
||||
// creating curve
|
||||
Curve curve = new Curve(new Spline(SplineType.Bezier, controlPoints, 0, false), resolution);
|
||||
|
||||
FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData();
|
||||
beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, cyclic));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loads the NURBS curve or surface.
|
||||
* @param nurb
|
||||
* the NURBS data structure
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with reading occur
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void loadNurbSurface(Structure nurb, int materialIndex) throws BlenderFileException {
|
||||
// loading the knots
|
||||
List<Float>[] knots = new List[2];
|
||||
Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") };
|
||||
for (int i = 0; i < knots.length; ++i) {
|
||||
if (pKnots[i].isNotNull()) {
|
||||
FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress());
|
||||
BlenderInputStream blenderInputStream = blenderContext.getInputStream();
|
||||
blenderInputStream.setPosition(fileBlockHeader.getBlockPosition());
|
||||
int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4;
|
||||
knots[i] = new ArrayList<Float>(knotsAmount);
|
||||
for (int j = 0; j < knotsAmount; ++j) {
|
||||
knots[i].add(Float.valueOf(blenderInputStream.readFloat()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loading the flags and orders (basis functions degrees)
|
||||
int flag = ((Number) nurb.getFieldValue("flag")).intValue();
|
||||
boolean smooth = (flag & FLAG_SMOOTH) != 0;
|
||||
int flagU = ((Number) nurb.getFieldValue("flagu")).intValue();
|
||||
int flagV = ((Number) nurb.getFieldValue("flagv")).intValue();
|
||||
int orderU = ((Number) nurb.getFieldValue("orderu")).intValue();
|
||||
int orderV = ((Number) nurb.getFieldValue("orderv")).intValue();
|
||||
|
||||
// loading control points and their weights
|
||||
int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue();
|
||||
int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue();
|
||||
List<Structure> bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData();
|
||||
List<List<Vector4f>> controlPoints = new ArrayList<List<Vector4f>>(pntsV);
|
||||
for (int i = 0; i < pntsV; ++i) {
|
||||
List<Vector4f> uControlPoints = new ArrayList<Vector4f>(pntsU);
|
||||
for (int j = 0; j < pntsU; ++j) {
|
||||
DynamicArray<Float> vec = (DynamicArray<Float>) bPoints.get(j + i * pntsU).getFieldValue("vec");
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue()));
|
||||
} else {
|
||||
uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue()));
|
||||
}
|
||||
}
|
||||
if ((flagU & 0x01) != 0) {
|
||||
for (int k = 0; k < orderU - 1; ++k) {
|
||||
uControlPoints.add(uControlPoints.get(k));
|
||||
}
|
||||
}
|
||||
controlPoints.add(uControlPoints);
|
||||
}
|
||||
if ((flagV & 0x01) != 0) {
|
||||
for (int k = 0; k < orderV - 1; ++k) {
|
||||
controlPoints.add(controlPoints.get(k));
|
||||
}
|
||||
}
|
||||
|
||||
int originalVerticesAmount = vertices.size();
|
||||
int resolu = ((Number) nurb.getFieldValue("resolu")).intValue();
|
||||
if (knots[1] == null) {// creating the NURB curve
|
||||
Curve curve = new Curve(new Spline(controlPoints.get(0), knots[0]), resolu);
|
||||
FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData();
|
||||
beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, false));
|
||||
} else {// creating the NURB surface
|
||||
int resolv = ((Number) nurb.getFieldValue("resolv")).intValue();
|
||||
int uSegments = resolu * controlPoints.get(0).size() - 1;
|
||||
int vSegments = resolv * controlPoints.size() - 1;
|
||||
Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, uSegments, vSegments, orderU, orderV, smooth);
|
||||
|
||||
FloatBuffer vertsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Position).getData();
|
||||
vertices.addAll(Arrays.asList(BufferUtils.getVector3Array(vertsBuffer)));
|
||||
FloatBuffer normalsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Normal).getData();
|
||||
normals.addAll(Arrays.asList(BufferUtils.getVector3Array(normalsBuffer)));
|
||||
|
||||
IndexBuffer indexBuffer = nurbSurface.getIndexBuffer();
|
||||
for (int i = 0; i < indexBuffer.size(); i += 3) {
|
||||
int index1 = indexBuffer.get(i) + originalVerticesAmount;
|
||||
int index2 = indexBuffer.get(i + 1) + originalVerticesAmount;
|
||||
int index3 = indexBuffer.get(i + 2) + originalVerticesAmount;
|
||||
faces.add(new Face(new Integer[] { index1, index2, index3 }, smooth, materialIndex, null, null, this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method loads the bevel object that should be applied to curve. It can either be another curve or a generated one
|
||||
* based on the bevel generating parameters in blender.
|
||||
* @param curveStructure
|
||||
* the structure with the curve's data (the curve being loaded, NOT the bevel curve)
|
||||
* @return the curve's bevel object
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with reading occur
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private CurvesTemporalMesh loadBevelObject(Structure curveStructure) throws BlenderFileException {
|
||||
CurvesTemporalMesh bevelObject = null;
|
||||
Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj");
|
||||
boolean cyclic = false;
|
||||
if (pBevelObject.isNotNull()) {
|
||||
Structure bevelObjectStructure = pBevelObject.fetchData().get(0);
|
||||
DynamicArray<Number> scaleArray = (DynamicArray<Number>) bevelObjectStructure.getFieldValue("size");
|
||||
Vector3f scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue());
|
||||
Pointer pBevelStructure = (Pointer) bevelObjectStructure.getFieldValue("data");
|
||||
Structure bevelStructure = pBevelStructure.fetchData().get(0);
|
||||
bevelObject = new CurvesTemporalMesh(bevelStructure, scale, false, blenderContext);
|
||||
|
||||
// transforming the bezier lines from plane XZ to plane YZ
|
||||
for (BezierLine bl : bevelObject.beziers) {
|
||||
for (Vector3f v : bl.vertices) {
|
||||
// casting the bezier curve orthogonally on the plane XZ (making Y = 0) and then moving the plane XZ to ZY in a way that:
|
||||
// -Z => +Y and +X => +Z and +Y => +X (but because casting would make Y = 0, then we simply set X = 0)
|
||||
v.y = -v.z;
|
||||
v.z = v.x;
|
||||
v.x = 0;
|
||||
}
|
||||
|
||||
// bevel curves should not have repeated the first vertex at the end when they are cyclic (this is handled differently)
|
||||
if (bl.isCyclic()) {
|
||||
bl.removeLastVertex();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fillCaps = false;// this option is inactive in blender when there is no bevel object applied
|
||||
int bevResol = ((Number) curveStructure.getFieldValue("bevresol")).intValue();
|
||||
float extrude = ((Number) curveStructure.getFieldValue("ext1")).floatValue();
|
||||
float bevelDepth = ((Number) curveStructure.getFieldValue("ext2")).floatValue();
|
||||
float offset = ((Number) curveStructure.getFieldValue("offset", 0)).floatValue();
|
||||
if (offset != 0) {
|
||||
// TODO: add support for offset parameter
|
||||
LOGGER.warning("Offset parameter not yet supported.");
|
||||
}
|
||||
Curve bevelCurve = null;
|
||||
if (bevelDepth > 0.0f) {
|
||||
float handlerLength = bevelDepth / 2.0f;
|
||||
cyclic = !isFront && !isBack;
|
||||
List<Vector3f> conrtolPoints = new ArrayList<Vector3f>();
|
||||
|
||||
// blenders from 2.49 to 2.52 did not pay attention to fron and back faces
|
||||
// so in order to draw the scene exactly as it is in different blender versions the blender version is checked here
|
||||
// when neither fron and back face is selected all version behave the same and draw full bevel around the curve
|
||||
if (cyclic || blenderContext.getBlenderVersion() < 253) {
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth));
|
||||
|
||||
if (extrude > 0) {
|
||||
conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth));
|
||||
}
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength));
|
||||
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0));
|
||||
|
||||
if (cyclic) {
|
||||
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, handlerLength));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, extrude + handlerLength, bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, extrude, bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, extrude - handlerLength, bevelDepth));
|
||||
|
||||
if (extrude > 0) {
|
||||
conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude, bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, bevelDepth));
|
||||
}
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, handlerLength));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0));
|
||||
}
|
||||
} else {
|
||||
if (extrude > 0) {
|
||||
if (isBack) {
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth));
|
||||
}
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth));
|
||||
|
||||
if (isFront) {
|
||||
conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength));
|
||||
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0));
|
||||
}
|
||||
} else {
|
||||
if (isFront && isBack) {
|
||||
conrtolPoints.add(new Vector3f(0, -bevelDepth, 0));
|
||||
conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, 0, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength));
|
||||
conrtolPoints.add(new Vector3f(0, bevelDepth, 0));
|
||||
} else {
|
||||
if (isBack) {
|
||||
conrtolPoints.add(new Vector3f(0, -bevelDepth, 0));
|
||||
conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, 0, -bevelDepth));
|
||||
} else {
|
||||
conrtolPoints.add(new Vector3f(0, 0, -bevelDepth));
|
||||
conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth));
|
||||
|
||||
conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength));
|
||||
conrtolPoints.add(new Vector3f(0, bevelDepth, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bevelCurve = new Curve(new Spline(SplineType.Bezier, conrtolPoints, 0, false), bevResol);
|
||||
} else if (extrude > 0.0f) {
|
||||
Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] { new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0) }, 1, false);
|
||||
bevelCurve = new Curve(bevelSpline, bevResol);
|
||||
}
|
||||
if (bevelCurve != null) {
|
||||
bevelObject = new CurvesTemporalMesh(blenderContext);
|
||||
FloatBuffer vertsBuffer = (FloatBuffer) bevelCurve.getBuffer(Type.Position).getData();
|
||||
Vector3f[] verts = BufferUtils.getVector3Array(vertsBuffer);
|
||||
if (cyclic) {// get rid of the last vertex which is identical to the first one
|
||||
verts = Arrays.copyOf(verts, verts.length - 1);
|
||||
}
|
||||
bevelObject.beziers.add(new BezierLine(verts, 0, false, cyclic));
|
||||
}
|
||||
}
|
||||
return bevelObject;
|
||||
}
|
||||
|
||||
private List<BezierLine> getScaledBeziers() {
|
||||
if (scale.equals(Vector3f.UNIT_XYZ)) {
|
||||
return beziers;
|
||||
}
|
||||
List<BezierLine> result = new ArrayList<BezierLine>();
|
||||
for (BezierLine bezierLine : beziers) {
|
||||
result.add(bezierLine.scale(scale));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method applies bevel and taper objects to the curve.
|
||||
* @param curve
|
||||
* the curve we apply the objects to
|
||||
* @param bevelObject
|
||||
* the bevel object
|
||||
* @param taperObject
|
||||
* the taper object
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return a list of geometries representing the beveled and/or tapered curve
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with reading occur
|
||||
*/
|
||||
private CurvesTemporalMesh applyBevelAndTaper(CurvesTemporalMesh curve, CurvesTemporalMesh bevelObject, CurvesTemporalMesh taperObject, BlenderContext blenderContext) throws BlenderFileException {
|
||||
List<BezierLine> bevelBezierLines = bevelObject.getScaledBeziers();
|
||||
List<BezierLine> curveLines = curve.beziers;
|
||||
if (bevelBezierLines.size() == 0 || curveLines.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CurvesTemporalMesh result = new CurvesTemporalMesh(blenderContext);
|
||||
for (BezierLine curveLine : curveLines) {
|
||||
Vector3f[] curveLineVertices = curveLine.getVertices(bevelStart, bevelEnd);
|
||||
|
||||
for (BezierLine bevelBezierLine : bevelBezierLines) {
|
||||
CurvesTemporalMesh partResult = new CurvesTemporalMesh(blenderContext);
|
||||
|
||||
Vector3f[] bevelLineVertices = bevelBezierLine.getVertices();
|
||||
List<Vector3f[]> bevels = new ArrayList<Vector3f[]>();
|
||||
|
||||
Vector3f[] bevelPoints = curvesHelper.transformToFirstLineOfBevelPoints(bevelLineVertices, curveLineVertices[0], curveLineVertices[1]);
|
||||
bevels.add(bevelPoints);
|
||||
for (int i = 1; i < curveLineVertices.length - 1; ++i) {
|
||||
bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[i - 1], curveLineVertices[i], curveLineVertices[i + 1]);
|
||||
bevels.add(bevelPoints);
|
||||
}
|
||||
bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[curveLineVertices.length - 2], curveLineVertices[curveLineVertices.length - 1], null);
|
||||
bevels.add(bevelPoints);
|
||||
|
||||
Vector3f subtractResult = new Vector3f();
|
||||
if (bevels.size() > 2) {
|
||||
// changing the first and last bevel so that they are parallel to their neighbours (blender works this way)
|
||||
// notice this implicates that the distances of every corresponding point in the two bevels must be identical and
|
||||
// equal to the distance between the points on curve that define the bevel position
|
||||
// so instead doing complicated rotations on each point we will simply properly translate each of them
|
||||
int[][] pointIndexes = new int[][] { { 0, 1 }, { curveLineVertices.length - 1, curveLineVertices.length - 2 } };
|
||||
for (int[] indexes : pointIndexes) {
|
||||
float distance = curveLineVertices[indexes[1]].subtract(curveLineVertices[indexes[0]], subtractResult).length();
|
||||
Vector3f[] bevel = bevels.get(indexes[0]);
|
||||
Vector3f[] nextBevel = bevels.get(indexes[1]);
|
||||
for (int i = 0; i < bevel.length; ++i) {
|
||||
float d = bevel[i].subtract(nextBevel[i], subtractResult).length();
|
||||
subtractResult.normalizeLocal().multLocal(distance - d);
|
||||
bevel[i].addLocal(subtractResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (taperObject != null) {
|
||||
float curveLength = curveLine.getLength(), lengthAlongCurve = bevelStart;
|
||||
for (int i = 0; i < curveLineVertices.length; ++i) {
|
||||
if (i > 0) {
|
||||
lengthAlongCurve += curveLineVertices[i].subtract(curveLineVertices[i - 1], subtractResult).length();
|
||||
}
|
||||
float taperScale = -taperObject.getValueAlongCurve(lengthAlongCurve / curveLength).z * taperObject.scale.z;
|
||||
if (taperScale != 1) {
|
||||
this.applyScale(bevels.get(i), curveLineVertices[i], taperScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// adding vertices to the part result
|
||||
for (Vector3f[] bevel : bevels) {
|
||||
for (Vector3f d : bevel) {
|
||||
partResult.getVertices().add(d);
|
||||
}
|
||||
}
|
||||
|
||||
// preparing faces for the part result (each face is a quad)
|
||||
int bevelVertCount = bevelPoints.length;
|
||||
for (int i = 0; i < bevels.size() - 1; ++i) {
|
||||
for (int j = 0; j < bevelVertCount - 1; ++j) {
|
||||
Integer[] indexes = new Integer[] { i * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j, i * bevelVertCount + j };
|
||||
partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult));
|
||||
}
|
||||
if (bevelBezierLine.isCyclic()) {
|
||||
int j = bevelVertCount - 1;
|
||||
Integer[] indexes = new Integer[] { i * bevelVertCount, (i + 1) * bevelVertCount, (i + 1) * bevelVertCount + j, i * bevelVertCount + j };
|
||||
partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult));
|
||||
partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult));
|
||||
}
|
||||
}
|
||||
|
||||
partResult.generateNormals();
|
||||
|
||||
if (fillCaps) {// caps in blender behave as if they weren't affected by the smooth factor
|
||||
// START CAP
|
||||
Vector3f[] cap = bevels.get(0);
|
||||
List<Integer> capIndexes = new ArrayList<Integer>(cap.length);
|
||||
Vector3f capNormal = curveLineVertices[0].subtract(curveLineVertices[1]).normalizeLocal();
|
||||
for (int i = 0; i < cap.length; ++i) {
|
||||
capIndexes.add(partResult.getVertices().size());
|
||||
partResult.getVertices().add(cap[i]);
|
||||
partResult.getNormals().add(capNormal);
|
||||
}
|
||||
Collections.reverse(capIndexes);// the indexes ned to be reversed for the face to have fron face outside the beveled line
|
||||
partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult));
|
||||
for (int i = 1; i < capIndexes.size(); ++i) {
|
||||
partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult));
|
||||
}
|
||||
|
||||
// END CAP
|
||||
cap = bevels.get(bevels.size() - 1);
|
||||
capIndexes.clear();
|
||||
capNormal = curveLineVertices[curveLineVertices.length - 1].subtract(curveLineVertices[curveLineVertices.length - 2]).normalizeLocal();
|
||||
for (int i = 0; i < cap.length; ++i) {
|
||||
capIndexes.add(partResult.getVertices().size());
|
||||
partResult.getVertices().add(cap[i]);
|
||||
partResult.getNormals().add(capNormal);
|
||||
}
|
||||
partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult));
|
||||
for (int i = 1; i < capIndexes.size(); ++i) {
|
||||
partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult));
|
||||
}
|
||||
}
|
||||
|
||||
result.append(partResult);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method generates normals for the curve. If any normals were already stored they are discarded.
|
||||
*/
|
||||
private void generateNormals() {
|
||||
Map<Integer, Vector3f> normalMap = new TreeMap<Integer, Vector3f>();
|
||||
for (Face face : faces) {
|
||||
// the first 3 verts are enough here (all faces are triangles except for the caps, but those are fully flat anyway)
|
||||
int index1 = face.getIndexes().get(0);
|
||||
int index2 = face.getIndexes().get(1);
|
||||
int index3 = face.getIndexes().get(2);
|
||||
|
||||
Vector3f n = FastMath.computeNormal(vertices.get(index1), vertices.get(index2), vertices.get(index3));
|
||||
for (int index : face.getIndexes()) {
|
||||
Vector3f normal = normalMap.get(index);
|
||||
if (normal == null) {
|
||||
normalMap.put(index, n.clone());
|
||||
} else {
|
||||
normal.addLocal(n).normalizeLocal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
normals.clear();
|
||||
Collections.addAll(normals, new Vector3f[normalMap.size()]);
|
||||
for (Entry<Integer, Vector3f> entry : normalMap.entrySet()) {
|
||||
normals.set(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the method applies scale for the given bevel points. The points table is
|
||||
* being modified so expect your result there.
|
||||
*
|
||||
* @param points
|
||||
* the bevel points
|
||||
* @param centerPoint
|
||||
* the center point of the bevel
|
||||
* @param scale
|
||||
* the scale to be applied
|
||||
*/
|
||||
private void applyScale(Vector3f[] points, Vector3f centerPoint, float scale) {
|
||||
Vector3f taperScaleVector = new Vector3f();
|
||||
for (Vector3f p : points) {
|
||||
taperScaleVector.set(centerPoint).subtractLocal(p).multLocal(1 - scale);
|
||||
p.addLocal(taperScaleVector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper class that represents a single bezier line. It consists of Edge's and allows to
|
||||
* get a subline of a length of the line.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static class BezierLine {
|
||||
/** The edges of the bezier line. */
|
||||
private Vector3f[] vertices;
|
||||
/** The material number of the line. */
|
||||
private int materialNumber;
|
||||
/** Indicates if the line is smooth of flat. */
|
||||
private boolean smooth;
|
||||
/** The length of the line. */
|
||||
private float length;
|
||||
/** Indicates if the current line is cyclic or not. */
|
||||
private boolean cyclic;
|
||||
|
||||
public BezierLine(Vector3f[] vertices, int materialNumber, boolean smooth, boolean cyclik) {
|
||||
this.vertices = vertices;
|
||||
this.materialNumber = materialNumber;
|
||||
this.smooth = smooth;
|
||||
cyclic = cyclik;
|
||||
this.recomputeLength();
|
||||
}
|
||||
|
||||
public BezierLine scale(Vector3f scale) {
|
||||
BezierLine result = new BezierLine(vertices, materialNumber, smooth, cyclic);
|
||||
result.vertices = new Vector3f[vertices.length];
|
||||
for (int i = 0; i < vertices.length; ++i) {
|
||||
result.vertices[i] = vertices[i].mult(scale);
|
||||
}
|
||||
result.recomputeLength();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void removeLastVertex() {
|
||||
Vector3f[] newVertices = new Vector3f[vertices.length - 1];
|
||||
for (int i = 0; i < vertices.length - 1; ++i) {
|
||||
newVertices[i] = vertices[i];
|
||||
}
|
||||
vertices = newVertices;
|
||||
this.recomputeLength();
|
||||
}
|
||||
|
||||
private void recomputeLength() {
|
||||
length = 0;
|
||||
for (int i = 1; i < vertices.length; ++i) {
|
||||
length += vertices[i - 1].distance(vertices[i]);
|
||||
}
|
||||
if (cyclic) {
|
||||
// if the first vertex is repeated at the end the distance will be = 0 so it won't affect the result, and if it is not repeated
|
||||
// then it is necessary to add the length between the last and the first vertex
|
||||
length += vertices[vertices.length - 1].distance(vertices[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3f[] getVertices() {
|
||||
return this.getVertices(0, 1);
|
||||
}
|
||||
|
||||
public Vector3f[] getVertices(float startSlice, float endSlice) {
|
||||
if (startSlice == 0 && endSlice == 1) {
|
||||
return vertices;
|
||||
}
|
||||
List<Vector3f> result = new ArrayList<Vector3f>();
|
||||
float length = this.getLength(), temp = 0;
|
||||
float startSliceLength = length * startSlice;
|
||||
float endSliceLength = length * endSlice;
|
||||
int index = 1;
|
||||
|
||||
if (startSlice > 0) {
|
||||
while (temp < startSliceLength) {
|
||||
Vector3f v1 = vertices[index - 1];
|
||||
Vector3f v2 = vertices[index++];
|
||||
float edgeLength = v1.distance(v2);
|
||||
temp += edgeLength;
|
||||
if (temp == startSliceLength) {
|
||||
result.add(v2);
|
||||
} else if (temp > startSliceLength) {
|
||||
result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endSlice < 1) {
|
||||
if (index == vertices.length) {
|
||||
Vector3f v1 = vertices[vertices.length - 2];
|
||||
Vector3f v2 = vertices[vertices.length - 1];
|
||||
result.add(v1.subtract(v2).normalizeLocal().multLocal(length - endSliceLength).addLocal(v2));
|
||||
} else {
|
||||
for (int i = index; i < vertices.length && temp < endSliceLength; ++i) {
|
||||
Vector3f v1 = vertices[index - 1];
|
||||
Vector3f v2 = vertices[index++];
|
||||
temp += v1.distance(v2);
|
||||
if (temp == endSliceLength) {
|
||||
result.add(v2);
|
||||
} else if (temp > endSliceLength) {
|
||||
result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.addAll(Arrays.asList(Arrays.copyOfRange(vertices, index, vertices.length)));
|
||||
}
|
||||
|
||||
return result.toArray(new Vector3f[result.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method computes the value of a point at the certain relational distance from its beginning.
|
||||
* @param alongRatio
|
||||
* the relative distance along the curve; should be a value between 0 and 1 inclusive;
|
||||
* if the value exceeds the boundaries it is truncated to them
|
||||
* @return computed value along the curve
|
||||
*/
|
||||
public Vector3f getValueAlongCurve(float alongRatio) {
|
||||
alongRatio = FastMath.clamp(alongRatio, 0, 1);
|
||||
Vector3f result = new Vector3f();
|
||||
float probeLength = this.getLength() * alongRatio;
|
||||
float length = 0;
|
||||
for (int i = 1; i < vertices.length; ++i) {
|
||||
float edgeLength = vertices[i].distance(vertices[i - 1]);
|
||||
if (length + edgeLength > probeLength) {
|
||||
float ratioAlongEdge = (probeLength - length) / edgeLength;
|
||||
return FastMath.interpolateLinear(ratioAlongEdge, vertices[i - 1], vertices[i]);
|
||||
} else if (length + edgeLength == probeLength) {
|
||||
return vertices[i];
|
||||
}
|
||||
length += edgeLength;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the material number of this bezier line
|
||||
*/
|
||||
public int getMaterialNumber() {
|
||||
return materialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the line is smooth of flat
|
||||
*/
|
||||
public boolean isSmooth() {
|
||||
return smooth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of this bezier line
|
||||
*/
|
||||
public float getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the current line is cyclic or not
|
||||
*/
|
||||
public boolean isCyclic() {
|
||||
return cyclic;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
/**
|
||||
* This exception is thrown when blend file data is somehow invalid.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class BlenderFileException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 7573482836437866767L;
|
||||
|
||||
/**
|
||||
* Constructor. Creates an exception with no description.
|
||||
*/
|
||||
public BlenderFileException() {
|
||||
// this constructor has no message
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Creates an exception containing the given message.
|
||||
* @param message
|
||||
* the message describing the problem that occurred
|
||||
*/
|
||||
public BlenderFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then.
|
||||
* @param throwable
|
||||
* an exception/error that occurred
|
||||
*/
|
||||
public BlenderFileException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Creates an exception with both a message and stacktrace.
|
||||
* @param message
|
||||
* the message describing the problem that occurred
|
||||
* @param throwable
|
||||
* an exception/error that occurred
|
||||
*/
|
||||
public BlenderFileException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
}
|
@ -1,371 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* An input stream with random access to data.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class BlenderInputStream extends InputStream {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName());
|
||||
/** The default size of the blender buffer. */
|
||||
private static final int DEFAULT_BUFFER_SIZE = 1048576; // 1MB
|
||||
/**
|
||||
* Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes.
|
||||
*/
|
||||
private int pointerSize;
|
||||
/**
|
||||
* Type of byte ordering used; 'v' means little endian and 'V' means big endian.
|
||||
*/
|
||||
private char endianess;
|
||||
/** Version of Blender the file was created in; '248' means version 2.48. */
|
||||
private String versionNumber;
|
||||
/** The buffer we store the read data to. */
|
||||
protected byte[] cachedBuffer;
|
||||
/** The total size of the stored data. */
|
||||
protected int size;
|
||||
/** The current position of the read cursor. */
|
||||
protected int position;
|
||||
|
||||
/**
|
||||
* Constructor. The input stream is stored and used to read data.
|
||||
* @param inputStream
|
||||
* the stream we read data from
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown if the file header has some invalid data
|
||||
*/
|
||||
public BlenderInputStream(InputStream inputStream) throws BlenderFileException {
|
||||
// the size value will canche while reading the file; the available() method cannot be counted on
|
||||
try {
|
||||
size = inputStream.available();
|
||||
} catch (IOException e) {
|
||||
size = 0;
|
||||
}
|
||||
if (size <= 0) {
|
||||
size = BlenderInputStream.DEFAULT_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
// buffered input stream is used here for much faster file reading
|
||||
BufferedInputStream bufferedInputStream;
|
||||
if (inputStream instanceof BufferedInputStream) {
|
||||
bufferedInputStream = (BufferedInputStream) inputStream;
|
||||
} else {
|
||||
bufferedInputStream = new BufferedInputStream(inputStream);
|
||||
}
|
||||
|
||||
try {
|
||||
this.readStreamToCache(bufferedInputStream);
|
||||
} catch (IOException e) {
|
||||
throw new BlenderFileException("Problems occurred while caching the file!", e);
|
||||
} finally {
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
LOGGER.warning("Unable to close stream with blender file.");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.readFileHeader();
|
||||
} catch (BlenderFileException e) {// the file might be packed, don't panic, try one more time ;)
|
||||
this.decompressFile();
|
||||
position = 0;
|
||||
this.readFileHeader();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads the whole stream into a buffer.
|
||||
* @param inputStream
|
||||
* the stream to read the file data from
|
||||
* @throws IOException
|
||||
* an exception is thrown when data read from the stream is invalid or there are problems with i/o
|
||||
* operations
|
||||
*/
|
||||
private void readStreamToCache(InputStream inputStream) throws IOException {
|
||||
int data = inputStream.read();
|
||||
cachedBuffer = new byte[size];
|
||||
size = 0;// this will count the actual size
|
||||
while (data != -1) {
|
||||
if (size >= cachedBuffer.length) {// widen the cached array
|
||||
byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)];
|
||||
System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length);
|
||||
cachedBuffer = newBuffer;
|
||||
}
|
||||
cachedBuffer[size++] = (byte) data;
|
||||
data = inputStream.read();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used when the blender file is gzipped. It decompresses the data and stores it back into the
|
||||
* cachedBuffer field.
|
||||
*/
|
||||
private void decompressFile() {
|
||||
GZIPInputStream gis = null;
|
||||
try {
|
||||
gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer));
|
||||
this.readStreamToCache(gis);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("IO errors occurred where they should NOT! " + "The data is already buffered at this point!", e);
|
||||
} finally {
|
||||
try {
|
||||
if (gis != null) {
|
||||
gis.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.warning(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loads the header from the given stream during instance creation.
|
||||
* @param inputStream
|
||||
* the stream we read the header from
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown if the file header has some invalid data
|
||||
*/
|
||||
private void readFileHeader() throws BlenderFileException {
|
||||
byte[] identifier = new byte[7];
|
||||
int bytesRead = this.readBytes(identifier);
|
||||
if (bytesRead != 7) {
|
||||
throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!");
|
||||
}
|
||||
String strIdentifier = new String(identifier);
|
||||
if (!"BLENDER".equals(strIdentifier)) {
|
||||
throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!");
|
||||
}
|
||||
char pointerSizeSign = (char) this.readByte();
|
||||
if (pointerSizeSign == '-') {
|
||||
pointerSize = 8;
|
||||
} else if (pointerSizeSign == '_') {
|
||||
pointerSize = 4;
|
||||
} else {
|
||||
throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign);
|
||||
}
|
||||
endianess = (char) this.readByte();
|
||||
if (endianess != 'v' && endianess != 'V') {
|
||||
throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess);
|
||||
}
|
||||
byte[] versionNumber = new byte[3];
|
||||
bytesRead = this.readBytes(versionNumber);
|
||||
if (bytesRead != 3) {
|
||||
throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!");
|
||||
}
|
||||
this.versionNumber = new String(versionNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return this.readByte();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads 1 byte from the stream.
|
||||
* It works just in the way the read method does.
|
||||
* It just not throw an exception because at this moment the whole file
|
||||
* is loaded into buffer, so no need for IOException to be thrown.
|
||||
* @return a byte from the stream (1 bytes read)
|
||||
*/
|
||||
public int readByte() {
|
||||
return cachedBuffer[position++] & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads a bytes number big enough to fill the table.
|
||||
* It does not throw exceptions so it is for internal use only.
|
||||
* @param bytes
|
||||
* an array to be filled with data
|
||||
* @return number of read bytes (a length of array actually)
|
||||
*/
|
||||
private int readBytes(byte[] bytes) {
|
||||
for (int i = 0; i < bytes.length; ++i) {
|
||||
bytes[i] = (byte) this.readByte();
|
||||
}
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads 2-byte number from the stream.
|
||||
* @return a number from the stream (2 bytes read)
|
||||
*/
|
||||
public int readShort() {
|
||||
int part1 = this.readByte();
|
||||
int part2 = this.readByte();
|
||||
if (endianess == 'v') {
|
||||
return (part2 << 8) + part1;
|
||||
} else {
|
||||
return (part1 << 8) + part2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads 4-byte number from the stream.
|
||||
* @return a number from the stream (4 bytes read)
|
||||
*/
|
||||
public int readInt() {
|
||||
int part1 = this.readByte();
|
||||
int part2 = this.readByte();
|
||||
int part3 = this.readByte();
|
||||
int part4 = this.readByte();
|
||||
if (endianess == 'v') {
|
||||
return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1;
|
||||
} else {
|
||||
return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads 4-byte floating point number (float) from the stream.
|
||||
* @return a number from the stream (4 bytes read)
|
||||
*/
|
||||
public float readFloat() {
|
||||
int intValue = this.readInt();
|
||||
return Float.intBitsToFloat(intValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads 8-byte number from the stream.
|
||||
* @return a number from the stream (8 bytes read)
|
||||
*/
|
||||
public long readLong() {
|
||||
long part1 = this.readInt();
|
||||
long part2 = this.readInt();
|
||||
long result = -1;
|
||||
if (endianess == 'v') {
|
||||
result = part2 << 32 | part1;
|
||||
} else {
|
||||
result = part1 << 32 | part2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads 8-byte floating point number (double) from the stream.
|
||||
* @return a number from the stream (8 bytes read)
|
||||
*/
|
||||
public double readDouble() {
|
||||
long longValue = this.readLong();
|
||||
return Double.longBitsToDouble(longValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either
|
||||
* 4 or 8 bytes of data.
|
||||
* @return the pointer value
|
||||
*/
|
||||
public long readPointer() {
|
||||
if (pointerSize == 4) {
|
||||
return this.readInt();
|
||||
}
|
||||
return this.readLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads the string. It assumes the string is terminated with zero in the stream.
|
||||
* @return the string read from the stream
|
||||
*/
|
||||
public String readString() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
int data = this.readByte();
|
||||
while (data != 0) {
|
||||
stringBuilder.append((char) data);
|
||||
data = this.readByte();
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the current position of the read cursor.
|
||||
* @param position
|
||||
* the position of the read cursor
|
||||
*/
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the position of the read cursor.
|
||||
* @return the position of the read cursor
|
||||
*/
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the blender version number where the file was created.
|
||||
* @return blender version number
|
||||
*/
|
||||
public String getVersionNumber() {
|
||||
return versionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the size of the pointer.
|
||||
* @return the size of the pointer
|
||||
*/
|
||||
public int getPointerSize() {
|
||||
return pointerSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method aligns cursor position forward to a given amount of bytes.
|
||||
* @param bytesAmount
|
||||
* the byte amount to which we aligh the cursor
|
||||
*/
|
||||
public void alignPosition(int bytesAmount) {
|
||||
if (bytesAmount <= 0) {
|
||||
throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!");
|
||||
}
|
||||
long move = position % bytesAmount;
|
||||
if (move > 0) {
|
||||
position += bytesAmount - move;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// this method is unimplemented because some loaders (ie. TGALoader) tend close the stream given from the outside
|
||||
// because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader
|
||||
// to read the image file, that is why we do not want it to be closed before the reading is done
|
||||
// and anyway this stream is only a cached buffer, so it does not hold any open connection to anything
|
||||
}
|
||||
}
|
@ -1,203 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2018 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The data block containing the description of the file.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class DnaBlockData {
|
||||
|
||||
private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; // SDNA
|
||||
private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; // NAME
|
||||
private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; // TYPE
|
||||
private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; // TLEN
|
||||
private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; // STRC
|
||||
/** Structures available inside the file. */
|
||||
private final Structure[] structures;
|
||||
/** A map that helps finding a structure by type. */
|
||||
private final Map<String, Structure> structuresMap;
|
||||
|
||||
/**
|
||||
* Constructor. Loads the block from the given stream during instance creation.
|
||||
* @param inputStream
|
||||
* the stream we read the block from
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is throw if the blend file is invalid or somehow corrupted
|
||||
*/
|
||||
public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
|
||||
int identifier;
|
||||
|
||||
// reading 'SDNA' identifier
|
||||
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
|
||||
|
||||
if (identifier != SDNA_ID) {
|
||||
throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier));
|
||||
}
|
||||
|
||||
// reading names
|
||||
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
|
||||
if (identifier != NAME_ID) {
|
||||
throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier));
|
||||
}
|
||||
int amount = inputStream.readInt();
|
||||
if (amount <= 0) {
|
||||
throw new BlenderFileException("The names amount number should be positive!");
|
||||
}
|
||||
String[] names = new String[amount];
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
names[i] = inputStream.readString();
|
||||
}
|
||||
|
||||
// reading types
|
||||
inputStream.alignPosition(4);
|
||||
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
|
||||
if (identifier != TYPE_ID) {
|
||||
throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier));
|
||||
}
|
||||
amount = inputStream.readInt();
|
||||
if (amount <= 0) {
|
||||
throw new BlenderFileException("The types amount number should be positive!");
|
||||
}
|
||||
String[] types = new String[amount];
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
types[i] = inputStream.readString();
|
||||
}
|
||||
|
||||
// reading lengths
|
||||
inputStream.alignPosition(4);
|
||||
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
|
||||
if (identifier != TLEN_ID) {
|
||||
throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier));
|
||||
}
|
||||
int[] lengths = new int[amount];// theamount is the same as int types
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
lengths[i] = inputStream.readShort();
|
||||
}
|
||||
|
||||
// reading structures
|
||||
inputStream.alignPosition(4);
|
||||
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
|
||||
if (identifier != STRC_ID) {
|
||||
throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier));
|
||||
}
|
||||
amount = inputStream.readInt();
|
||||
if (amount <= 0) {
|
||||
throw new BlenderFileException("The structures amount number should be positive!");
|
||||
}
|
||||
structures = new Structure[amount];
|
||||
structuresMap = new HashMap<String, Structure>(amount);
|
||||
for (int i = 0; i < amount; ++i) {
|
||||
structures[i] = new Structure(inputStream, names, types, blenderContext);
|
||||
if (structuresMap.containsKey(structures[i].getType())) {
|
||||
throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!");
|
||||
}
|
||||
structuresMap.put(structures[i].getType(), structures[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the amount of the structures.
|
||||
* @return the amount of the structures
|
||||
*/
|
||||
public int getStructuresCount() {
|
||||
return structures.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the structure of the given index.
|
||||
* @param index
|
||||
* the index of the structure
|
||||
* @return the structure of the given index
|
||||
*/
|
||||
public Structure getStructure(int index) {
|
||||
try {
|
||||
return (Structure) structures[index].clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new IllegalStateException("Structure should be clonable!!!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a structure of the given name. If the name does not exists then null is returned.
|
||||
* @param name
|
||||
* the name of the structure
|
||||
* @return the required structure or null if the given name is inapropriate
|
||||
*/
|
||||
public Structure getStructure(String name) {
|
||||
try {
|
||||
return (Structure) structuresMap.get(name).clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new IllegalStateException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates if the structure of the given name exists.
|
||||
* @param name
|
||||
* the name of the structure
|
||||
* @return true if the structure exists and false otherwise
|
||||
*/
|
||||
public boolean hasStructure(String name) {
|
||||
return structuresMap.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the given identifier code to string.
|
||||
* @param code
|
||||
* the code that is to be converted
|
||||
* @return the string value of the identifier
|
||||
*/
|
||||
private String toString(int code) {
|
||||
char c1 = (char) ((code & 0xFF000000) >> 24);
|
||||
char c2 = (char) ((code & 0xFF0000) >> 16);
|
||||
char c3 = (char) ((code & 0xFF00) >> 8);
|
||||
char c4 = (char) (code & 0xFF);
|
||||
return String.valueOf(c1) + c2 + c3 + c4;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n');
|
||||
for (Structure structure : structures) {
|
||||
stringBuilder.append(structure.toString()).append('\n');
|
||||
}
|
||||
return stringBuilder.append("===============").toString();
|
||||
}
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2018 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
/**
|
||||
* An array that can be dynamically modified
|
||||
* @author Marcin Roguski
|
||||
* @param <T>
|
||||
* the type of stored data in the array
|
||||
*/
|
||||
public class DynamicArray<T> implements Cloneable {
|
||||
|
||||
/** An array object that holds the required data. */
|
||||
private T[] array;
|
||||
/**
|
||||
* This table holds the sizes of dimensions of the dynamic table. Its length specifies the table dimension or a
|
||||
* pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths:
|
||||
* dynTable[a][b][c], where a,b,c are stored in the tableSizes table.
|
||||
*/
|
||||
private int[] tableSizes;
|
||||
|
||||
/**
|
||||
* Constructor. Builds an empty array of the specified sizes.
|
||||
* @param tableSizes
|
||||
* the sizes of the table
|
||||
* @throws IllegalArgumentException
|
||||
* an exception is thrown if one of the sizes is not a positive number
|
||||
*/
|
||||
public DynamicArray(int[] tableSizes, T[] data) {
|
||||
this.tableSizes = tableSizes;
|
||||
int totalSize = 1;
|
||||
for (int size : tableSizes) {
|
||||
if (size <= 0) {
|
||||
throw new IllegalArgumentException("The size of the table must be positive!");
|
||||
}
|
||||
totalSize *= size;
|
||||
}
|
||||
if (totalSize != data.length) {
|
||||
throw new IllegalArgumentException("The size of the table does not match the size of the given data!");
|
||||
}
|
||||
this.array = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a value on the specified position. The dimension of the table is not taken into
|
||||
* consideration.
|
||||
* @param position
|
||||
* the position of the data
|
||||
* @return required data
|
||||
*/
|
||||
public T get(int position) {
|
||||
return array[position];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a value on the specified position in multidimensional array. Be careful not to exceed the
|
||||
* table boundaries. Check the table's dimension first.
|
||||
* @param position
|
||||
* the position of the data indices of data position
|
||||
* @return required data required data
|
||||
*/
|
||||
public T get(int... position) {
|
||||
if (position.length != tableSizes.length) {
|
||||
throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!");
|
||||
}
|
||||
int index = 0;
|
||||
for (int i = 0; i < position.length - 1; ++i) {
|
||||
index += position[i] * tableSizes[i + 1];
|
||||
}
|
||||
index += position[position.length - 1];
|
||||
return array[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the total amount of data stored in the array.
|
||||
* @return the total amount of data stored in the array
|
||||
*/
|
||||
public int getTotalSize() {
|
||||
return array.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (array instanceof Character[]) {// in case of character array we convert it to String
|
||||
for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {// strings are terminater with '0'
|
||||
result.append(array[i]);
|
||||
}
|
||||
} else {
|
||||
result.append('[');
|
||||
for (int i = 0; i < array.length; ++i) {
|
||||
result.append(array[i].toString());
|
||||
if (i + 1 < array.length) {
|
||||
result.append(',');
|
||||
}
|
||||
}
|
||||
result.append(']');
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@ -1,327 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.Structure.DataType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class represents a single field in the structure. It can be either a primitive type or a table or a reference to
|
||||
* another structure.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
/* package */
|
||||
class Field implements Cloneable {
|
||||
|
||||
private static final int NAME_LENGTH = 24;
|
||||
private static final int TYPE_LENGTH = 16;
|
||||
/** The blender context. */
|
||||
public BlenderContext blenderContext;
|
||||
/** The type of the field. */
|
||||
public String type;
|
||||
/** The name of the field. */
|
||||
public String name;
|
||||
/** The value of the field. Filled during data reading. */
|
||||
public Object value;
|
||||
/** This variable indicates the level of the pointer. */
|
||||
public int pointerLevel;
|
||||
/**
|
||||
* This variable determines the sizes of the array. If the value is null then the field is not an array.
|
||||
*/
|
||||
public int[] tableSizes;
|
||||
/** This variable indicates if the field is a function pointer. */
|
||||
public boolean function;
|
||||
|
||||
/**
|
||||
* Constructor. Saves the field data and parses its name.
|
||||
* @param name
|
||||
* the name of the field
|
||||
* @param type
|
||||
* the type of the field
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown if the names contain errors
|
||||
*/
|
||||
public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException {
|
||||
this.type = type;
|
||||
this.blenderContext = blenderContext;
|
||||
this.parseField(new StringBuilder(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we
|
||||
* have a clean empty copy of the field to fill with data.
|
||||
* @param field
|
||||
* the object that we copy
|
||||
*/
|
||||
private Field(Field field) {
|
||||
type = field.type;
|
||||
name = field.name;
|
||||
blenderContext = field.blenderContext;
|
||||
pointerLevel = field.pointerLevel;
|
||||
if (field.tableSizes != null) {
|
||||
tableSizes = field.tableSizes.clone();
|
||||
}
|
||||
function = field.function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return new Field(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fills the field with data read from the input stream.
|
||||
* @param blenderInputStream
|
||||
* the stream we read data from
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when the blend file is somehow invalid or corrupted
|
||||
*/
|
||||
public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException {
|
||||
int dataToRead = 1;
|
||||
if (tableSizes != null && tableSizes.length > 0) {
|
||||
for (int size : tableSizes) {
|
||||
if (size <= 0) {
|
||||
throw new BlenderFileException("The field " + name + " has invalid table size: " + size);
|
||||
}
|
||||
dataToRead *= size;
|
||||
}
|
||||
}
|
||||
DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, blenderContext) : DataType.POINTER;
|
||||
switch (dataType) {
|
||||
case POINTER:
|
||||
if (dataToRead == 1) {
|
||||
Pointer pointer = new Pointer(pointerLevel, function, blenderContext);
|
||||
pointer.fill(blenderInputStream);
|
||||
value = pointer;
|
||||
} else {
|
||||
Pointer[] data = new Pointer[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
Pointer pointer = new Pointer(pointerLevel, function, blenderContext);
|
||||
pointer.fill(blenderInputStream);
|
||||
data[i] = pointer;
|
||||
}
|
||||
value = new DynamicArray<Pointer>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
case CHARACTER:
|
||||
// character is also stored as a number, because sometimes the new blender version uses
|
||||
// other number type instead of character as a field type
|
||||
// and characters are very often used as byte number stores instead of real chars
|
||||
if (dataToRead == 1) {
|
||||
value = Byte.valueOf((byte) blenderInputStream.readByte());
|
||||
} else {
|
||||
Character[] data = new Character[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
data[i] = Character.valueOf((char) blenderInputStream.readByte());
|
||||
}
|
||||
value = new DynamicArray<Character>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
case SHORT:
|
||||
if (dataToRead == 1) {
|
||||
value = Integer.valueOf(blenderInputStream.readShort());
|
||||
} else {
|
||||
Number[] data = new Number[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
data[i] = Integer.valueOf(blenderInputStream.readShort());
|
||||
}
|
||||
value = new DynamicArray<Number>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
case INTEGER:
|
||||
if (dataToRead == 1) {
|
||||
value = Integer.valueOf(blenderInputStream.readInt());
|
||||
} else {
|
||||
Number[] data = new Number[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
data[i] = Integer.valueOf(blenderInputStream.readInt());
|
||||
}
|
||||
value = new DynamicArray<Number>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
case LONG:
|
||||
if (dataToRead == 1) {
|
||||
value = Long.valueOf(blenderInputStream.readLong());
|
||||
} else {
|
||||
Number[] data = new Number[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
data[i] = Long.valueOf(blenderInputStream.readLong());
|
||||
}
|
||||
value = new DynamicArray<Number>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
case FLOAT:
|
||||
if (dataToRead == 1) {
|
||||
value = Float.valueOf(blenderInputStream.readFloat());
|
||||
} else {
|
||||
Number[] data = new Number[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
data[i] = Float.valueOf(blenderInputStream.readFloat());
|
||||
}
|
||||
value = new DynamicArray<Number>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
case DOUBLE:
|
||||
if (dataToRead == 1) {
|
||||
value = Double.valueOf(blenderInputStream.readDouble());
|
||||
} else {
|
||||
Number[] data = new Number[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
data[i] = Double.valueOf(blenderInputStream.readDouble());
|
||||
}
|
||||
value = new DynamicArray<Number>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
case VOID:
|
||||
break;
|
||||
case STRUCTURE:
|
||||
if (dataToRead == 1) {
|
||||
Structure structure = blenderContext.getDnaBlockData().getStructure(type);
|
||||
structure.fill(blenderContext.getInputStream());
|
||||
value = structure;
|
||||
} else {
|
||||
Structure[] data = new Structure[dataToRead];
|
||||
for (int i = 0; i < dataToRead; ++i) {
|
||||
Structure structure = blenderContext.getDnaBlockData().getStructure(type);
|
||||
structure.fill(blenderContext.getInputStream());
|
||||
data[i] = structure;
|
||||
}
|
||||
value = new DynamicArray<Structure>(tableSizes, data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unimplemented filling of type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method parses the field name to determine how the field should be used.
|
||||
* @param nameBuilder
|
||||
* the name of the field (given as StringBuilder)
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown if the names contain errors
|
||||
*/
|
||||
private void parseField(StringBuilder nameBuilder) throws BlenderFileException {
|
||||
this.removeWhitespaces(nameBuilder);
|
||||
// veryfying if the name is a pointer
|
||||
int pointerIndex = nameBuilder.indexOf("*");
|
||||
while (pointerIndex >= 0) {
|
||||
++pointerLevel;
|
||||
nameBuilder.deleteCharAt(pointerIndex);
|
||||
pointerIndex = nameBuilder.indexOf("*");
|
||||
}
|
||||
// veryfying if the name is a function pointer
|
||||
if (nameBuilder.indexOf("(") >= 0) {
|
||||
function = true;
|
||||
this.removeCharacter(nameBuilder, '(');
|
||||
this.removeCharacter(nameBuilder, ')');
|
||||
} else {
|
||||
// veryfying if the name is a table
|
||||
int tableStartIndex = 0;
|
||||
List<Integer> lengths = new ArrayList<Integer>(3);// 3 dimensions will be enough in most cases
|
||||
do {
|
||||
tableStartIndex = nameBuilder.indexOf("[");
|
||||
if (tableStartIndex > 0) {
|
||||
int tableStopIndex = nameBuilder.indexOf("]");
|
||||
if (tableStopIndex < 0) {
|
||||
throw new BlenderFileException("Invalid structure name: " + name);
|
||||
}
|
||||
try {
|
||||
lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex)));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e);
|
||||
}
|
||||
nameBuilder.delete(tableStartIndex, tableStopIndex + 1);
|
||||
}
|
||||
} while (tableStartIndex > 0);
|
||||
if (!lengths.isEmpty()) {
|
||||
tableSizes = new int[lengths.size()];
|
||||
for (int i = 0; i < tableSizes.length; ++i) {
|
||||
tableSizes[i] = lengths.get(i).intValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
name = nameBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method removes the required character from the text.
|
||||
* @param text
|
||||
* the text we remove characters from
|
||||
* @param toRemove
|
||||
* the character to be removed
|
||||
*/
|
||||
private void removeCharacter(StringBuilder text, char toRemove) {
|
||||
for (int i = 0; i < text.length(); ++i) {
|
||||
if (text.charAt(i) == toRemove) {
|
||||
text.deleteCharAt(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method removes all whitespace from the text.
|
||||
* @param text
|
||||
* the text we remove whitespace from
|
||||
*/
|
||||
private void removeWhitespaces(StringBuilder text) {
|
||||
for (int i = 0; i < text.length(); ++i) {
|
||||
if (Character.isWhitespace(text.charAt(i))) {
|
||||
text.deleteCharAt(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method builds the full name of the field (with function, pointer and table indications).
|
||||
* @return the full name of the field
|
||||
*/
|
||||
/*package*/ String getFullName() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (function) {
|
||||
result.append('(');
|
||||
}
|
||||
for (int i = 0; i < pointerLevel; ++i) {
|
||||
result.append('*');
|
||||
}
|
||||
result.append(name);
|
||||
if (tableSizes != null) {
|
||||
for (int i = 0; i < tableSizes.length; ++i) {
|
||||
result.append('[').append(tableSizes[i]).append(']');
|
||||
}
|
||||
}
|
||||
if (function) {
|
||||
result.append(")()");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(this.getFullName());
|
||||
|
||||
// insert appropriate number of spaces to format the output corrently
|
||||
int nameLength = result.length();
|
||||
result.append(' ');// at least one space is a must
|
||||
for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {// we start from i=1 because one space is already added
|
||||
result.append(' ');
|
||||
}
|
||||
result.append(type);
|
||||
nameLength = result.length();
|
||||
for (int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) {
|
||||
result.append(' ');
|
||||
}
|
||||
if (value instanceof Character) {
|
||||
result.append(" = ").append((int) ((Character) value).charValue());
|
||||
} else {
|
||||
result.append(" = ").append(value != null ? value.toString() : "null");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@ -1,213 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
|
||||
/**
|
||||
* A class that holds the header data of a file block. The file block itself is not implemented. This class holds its
|
||||
* start position in the stream and using this the structure can fill itself with the proper data.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class FileBlockHeader {
|
||||
private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName());
|
||||
|
||||
/** Identifier of the file-block [4 bytes]. */
|
||||
private BlockCode code;
|
||||
/** Total length of the data after the file-block-header [4 bytes]. */
|
||||
private int size;
|
||||
/**
|
||||
* Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer
|
||||
* size)].
|
||||
*/
|
||||
private long oldMemoryAddress;
|
||||
/** Index of the SDNA structure [4 bytes]. */
|
||||
private int sdnaIndex;
|
||||
/** Number of structure located in this file-block [4 bytes]. */
|
||||
private int count;
|
||||
/** Start position of the block's data in the stream. */
|
||||
private int blockPosition;
|
||||
|
||||
/**
|
||||
* Constructor. Loads the block header from the given stream during instance creation.
|
||||
* @param inputStream
|
||||
* the stream we read the block header from
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the pointer size is neither 4 nor 8
|
||||
*/
|
||||
public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
|
||||
inputStream.alignPosition(4);
|
||||
code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte());
|
||||
size = inputStream.readInt();
|
||||
oldMemoryAddress = inputStream.readPointer();
|
||||
sdnaIndex = inputStream.readInt();
|
||||
count = inputStream.readInt();
|
||||
blockPosition = inputStream.getPosition();
|
||||
if (BlockCode.BLOCK_DNA1 == code) {
|
||||
blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext));
|
||||
} else {
|
||||
inputStream.setPosition(blockPosition + size);
|
||||
blenderContext.addFileBlockHeader(Long.valueOf(oldMemoryAddress), this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the structure described by the header filled with appropriate data.
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return structure filled with data
|
||||
* @throws BlenderFileException
|
||||
*/
|
||||
public Structure getStructure(BlenderContext blenderContext) throws BlenderFileException {
|
||||
blenderContext.getInputStream().setPosition(blockPosition);
|
||||
Structure structure = blenderContext.getDnaBlockData().getStructure(sdnaIndex);
|
||||
structure.fill(blenderContext.getInputStream());
|
||||
return structure;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the code of this data block.
|
||||
* @return the code of this data block
|
||||
*/
|
||||
public BlockCode getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the size of the data stored in this block.
|
||||
* @return the size of the data stored in this block
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the sdna index.
|
||||
* @return the sdna index
|
||||
*/
|
||||
public int getSdnaIndex() {
|
||||
return sdnaIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* This data returns the number of structure stored in the data block after this header.
|
||||
* @return the number of structure stored in the data block after this header
|
||||
*/
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the start position of the data block in the blend file stream.
|
||||
* @return the start position of the data block
|
||||
*/
|
||||
public int getBlockPosition() {
|
||||
return blockPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates if the block is the last block in the file.
|
||||
* @return true if this block is the last one in the file nad false otherwise
|
||||
*/
|
||||
public boolean isLastBlock() {
|
||||
return BlockCode.BLOCK_ENDB == code;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates if the block is the SDNA block.
|
||||
* @return true if this block is the SDNA block and false otherwise
|
||||
*/
|
||||
public boolean isDnaBlock() {
|
||||
return BlockCode.BLOCK_DNA1 == code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
|
||||
}
|
||||
|
||||
public static enum BlockCode {
|
||||
BLOCK_ME00('M' << 24 | 'E' << 16), // mesh
|
||||
BLOCK_CA00('C' << 24 | 'A' << 16), // camera
|
||||
BLOCK_LA00('L' << 24 | 'A' << 16), // lamp
|
||||
BLOCK_OB00('O' << 24 | 'B' << 16), // object
|
||||
BLOCK_MA00('M' << 24 | 'A' << 16), // material
|
||||
BLOCK_SC00('S' << 24 | 'C' << 16), // scene
|
||||
BLOCK_WO00('W' << 24 | 'O' << 16), // world
|
||||
BLOCK_TX00('T' << 24 | 'X' << 16), // texture
|
||||
BLOCK_IP00('I' << 24 | 'P' << 16), // ipo
|
||||
BLOCK_AC00('A' << 24 | 'C' << 16), // action
|
||||
BLOCK_IM00('I' << 24 | 'M' << 16), // image
|
||||
BLOCK_TE00('T' << 24 | 'E' << 16),
|
||||
BLOCK_WM00('W' << 24 | 'M' << 16),
|
||||
BLOCK_SR00('S' << 24 | 'R' << 16),
|
||||
BLOCK_SN00('S' << 24 | 'N' << 16),
|
||||
BLOCK_BR00('B' << 24 | 'R' << 16),
|
||||
BLOCK_LS00('L' << 24 | 'S' << 16),
|
||||
BLOCK_GR00('G' << 24 | 'R' << 16),
|
||||
BLOCK_AR00('A' << 24 | 'R' << 16),
|
||||
BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'),
|
||||
BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'),
|
||||
BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'),
|
||||
BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'),
|
||||
BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'),
|
||||
BLOCK_TEST('T' << 24 | 'E' << 16 | 'S' << 8 | 'T'),
|
||||
BLOCK_UNKN(0);
|
||||
|
||||
private int code;
|
||||
|
||||
private BlockCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public static BlockCode valueOf(int code) {
|
||||
for (BlockCode blockCode : BlockCode.values()) {
|
||||
if (blockCode.code == code) {
|
||||
return blockCode;
|
||||
}
|
||||
}
|
||||
byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) };
|
||||
for (int i = 0; i < codeBytes.length; ++i) {
|
||||
if (codeBytes[i] == 0) {
|
||||
codeBytes[i] = '0';
|
||||
}
|
||||
}
|
||||
LOGGER.log(Level.WARNING, "Unknown block header: {0}", new String(codeBytes));
|
||||
return BLOCK_UNKN;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
|
||||
/**
|
||||
* A class that represents a pointer of any level that can be stored in the file.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class Pointer {
|
||||
|
||||
/** The blender context. */
|
||||
private BlenderContext blenderContext;
|
||||
/** The level of the pointer. */
|
||||
private int pointerLevel;
|
||||
/** The address in file it points to. */
|
||||
private long oldMemoryAddress;
|
||||
/** This variable indicates if the field is a function pointer. */
|
||||
public boolean function;
|
||||
|
||||
/**
|
||||
* Constructr. Stores the basic data about the pointer.
|
||||
* @param pointerLevel
|
||||
* the level of the pointer
|
||||
* @param function
|
||||
* this variable indicates if the field is a function pointer
|
||||
* @param blenderContext
|
||||
* the repository f data; used in fetching the value that the pointer points
|
||||
*/
|
||||
public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) {
|
||||
this.pointerLevel = pointerLevel;
|
||||
this.function = function;
|
||||
this.blenderContext = blenderContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method
|
||||
* for this.
|
||||
* @param inputStream
|
||||
* the stream we read the pointer value from
|
||||
*/
|
||||
public void fill(BlenderInputStream inputStream) {
|
||||
oldMemoryAddress = inputStream.readPointer();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fetches the data stored under the given address.
|
||||
* @return the data read from the file
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blend file structure is somehow invalid or corrupted
|
||||
*/
|
||||
public List<Structure> fetchData() throws BlenderFileException {
|
||||
if (oldMemoryAddress == 0) {
|
||||
throw new NullPointerException("The pointer points to nothing!");
|
||||
}
|
||||
List<Structure> structures = null;
|
||||
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress);
|
||||
if (dataFileBlock == null) {
|
||||
throw new BlenderFileException("No data stored for address: " + oldMemoryAddress + ". Make sure you did not open the newer blender file with older blender version.");
|
||||
}
|
||||
BlenderInputStream inputStream = blenderContext.getInputStream();
|
||||
if (pointerLevel > 1) {
|
||||
int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount();
|
||||
for (int i = 0; i < pointersAmount; ++i) {
|
||||
inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i);
|
||||
long oldMemoryAddress = inputStream.readPointer();
|
||||
if (oldMemoryAddress != 0L) {
|
||||
Pointer p = new Pointer(pointerLevel - 1, function, blenderContext);
|
||||
p.oldMemoryAddress = oldMemoryAddress;
|
||||
if (structures == null) {
|
||||
structures = p.fetchData();
|
||||
} else {
|
||||
structures.addAll(p.fetchData());
|
||||
}
|
||||
} else {
|
||||
// it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index
|
||||
// of the material is important, that is why we need null's to indicate that some materials' slots are empty
|
||||
if (structures == null) {
|
||||
structures = new ArrayList<Structure>();
|
||||
}
|
||||
structures.add(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inputStream.setPosition(dataFileBlock.getBlockPosition());
|
||||
structures = new ArrayList<Structure>(dataFileBlock.getCount());
|
||||
for (int i = 0; i < dataFileBlock.getCount(); ++i) {
|
||||
Structure structure = blenderContext.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex());
|
||||
structure.fill(blenderContext.getInputStream());
|
||||
structures.add(structure);
|
||||
}
|
||||
return structures;
|
||||
}
|
||||
return structures;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates if this pointer points to a function.
|
||||
* @return <b>true</b> if this is a function pointer and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isFunction() {
|
||||
return function;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates if this is a null-pointer or not.
|
||||
* @return <b>true</b> if the pointer is null and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isNull() {
|
||||
return oldMemoryAddress == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates if this is a null-pointer or not.
|
||||
* @return <b>true</b> if the pointer is not null and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isNotNull() {
|
||||
return oldMemoryAddress != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the old memory address of the structure pointed by the pointer.
|
||||
* @return the old memory address of the structure pointed by the pointer
|
||||
*/
|
||||
public long getOldMemoryAddress() {
|
||||
return oldMemoryAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 + (int) (oldMemoryAddress ^ oldMemoryAddress >>> 32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Pointer other = (Pointer) obj;
|
||||
if (oldMemoryAddress != other.oldMemoryAddress) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,328 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2018 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.file;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
|
||||
/**
|
||||
* A class representing a single structure in the file.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class Structure implements Cloneable {
|
||||
|
||||
/** The address of the block that fills the structure. */
|
||||
private transient Long oldMemoryAddress;
|
||||
/** The type of the structure. */
|
||||
private String type;
|
||||
/**
|
||||
* The fields of the structure. Each field consists of a pair: name-type.
|
||||
*/
|
||||
private Field[] fields;
|
||||
|
||||
/**
|
||||
* Constructor that copies the data of the structure.
|
||||
* @param structure
|
||||
* the structure to copy.
|
||||
* @throws CloneNotSupportedException
|
||||
* this exception should never be thrown
|
||||
*/
|
||||
private Structure(Structure structure) throws CloneNotSupportedException {
|
||||
type = structure.type;
|
||||
fields = new Field[structure.fields.length];
|
||||
for (int i = 0; i < fields.length; ++i) {
|
||||
fields[i] = (Field) structure.fields[i].clone();
|
||||
}
|
||||
oldMemoryAddress = structure.oldMemoryAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Loads the structure from the given stream during instance creation.
|
||||
* @param inputStream
|
||||
* the stream we read the structure from
|
||||
* @param names
|
||||
* the names from which the name of structure and its fields will be taken
|
||||
* @param types
|
||||
* the names of types for the structure
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception occurs if the amount of fields, defined in the file, is negative
|
||||
*/
|
||||
public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException {
|
||||
int nameIndex = inputStream.readShort();
|
||||
type = types[nameIndex];
|
||||
int fieldsAmount = inputStream.readShort();
|
||||
if (fieldsAmount < 0) {
|
||||
throw new BlenderFileException("The amount of fields of " + type + " structure cannot be negative!");
|
||||
}
|
||||
if (fieldsAmount > 0) {
|
||||
fields = new Field[fieldsAmount];
|
||||
for (int i = 0; i < fieldsAmount; ++i) {
|
||||
int typeIndex = inputStream.readShort();
|
||||
nameIndex = inputStream.readShort();
|
||||
fields[i] = new Field(names[nameIndex], types[typeIndex], blenderContext);
|
||||
}
|
||||
}
|
||||
oldMemoryAddress = Long.valueOf(-1L);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fills the structure with data.
|
||||
* @param inputStream
|
||||
* the stream we read data from, its read cursor should be placed at the start position of the data for the
|
||||
* structure
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when the blend file is somehow invalid or corrupted
|
||||
*/
|
||||
public void fill(BlenderInputStream inputStream) throws BlenderFileException {
|
||||
int position = inputStream.getPosition();
|
||||
inputStream.setPosition(position - 8 - inputStream.getPointerSize());
|
||||
oldMemoryAddress = Long.valueOf(inputStream.readPointer());
|
||||
inputStream.setPosition(position);
|
||||
for (Field field : fields) {
|
||||
field.fill(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the value of the filed with a given name.
|
||||
* @param fieldName
|
||||
* the name of the field
|
||||
* @return the value of the field or null if no field with a given name is found
|
||||
*/
|
||||
public Object getFieldValue(String fieldName) {
|
||||
return this.getFieldValue(fieldName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the value of the filed with a given name.
|
||||
* @param fieldName
|
||||
* the name of the field
|
||||
* @param defaultValue
|
||||
* the value that is being returned when no field of a given name is found
|
||||
* @return the value of the field or the given default value if no field with a given name is found
|
||||
*/
|
||||
public Object getFieldValue(String fieldName, Object defaultValue) {
|
||||
for (Field field : fields) {
|
||||
if (field.name.equalsIgnoreCase(fieldName)) {
|
||||
return field.value;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the value of the filed with a given name. The structure is considered to have flat fields
|
||||
* only (no substructures).
|
||||
* @param fieldName
|
||||
* the name of the field
|
||||
* @return the value of the field or null if no field with a given name is found
|
||||
*/
|
||||
public Object getFlatFieldValue(String fieldName) {
|
||||
for (Field field : fields) {
|
||||
Object value = field.value;
|
||||
if (field.name.equalsIgnoreCase(fieldName)) {
|
||||
return value;
|
||||
} else if (value instanceof Structure) {
|
||||
value = ((Structure) value).getFlatFieldValue(fieldName);
|
||||
if (value != null) {// we can compare references here, since we use one static object as a NULL field value
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be used on structures that are of a 'ListBase' type. It creates a List of structures that are
|
||||
* held by this structure within the blend file.
|
||||
* @return a list of filled structures
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blend file structure is somehow invalid or corrupted
|
||||
* @throws IllegalArgumentException
|
||||
* this exception is thrown if the type of the structure is not 'ListBase'
|
||||
*/
|
||||
public List<Structure> evaluateListBase() throws BlenderFileException {
|
||||
if (!"ListBase".equals(type)) {
|
||||
throw new IllegalStateException("This structure is not of type: 'ListBase'");
|
||||
}
|
||||
Pointer first = (Pointer) this.getFieldValue("first");
|
||||
Pointer last = (Pointer) this.getFieldValue("last");
|
||||
long currentAddress = 0;
|
||||
long lastAddress = last.getOldMemoryAddress();
|
||||
List<Structure> result = new LinkedList<Structure>();
|
||||
while (currentAddress != lastAddress) {
|
||||
currentAddress = first.getOldMemoryAddress();
|
||||
Structure structure = first.fetchData().get(0);
|
||||
result.add(structure);
|
||||
first = (Pointer) structure.getFlatFieldValue("next");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the type of the structure.
|
||||
* @return the type of the structure
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the amount of fields for the current structure.
|
||||
* @return the amount of fields for the current structure
|
||||
*/
|
||||
public int getFieldsAmount() {
|
||||
return fields.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the full field name of the given index.
|
||||
* @param fieldIndex
|
||||
* the index of the field
|
||||
* @return the full field name of the given index
|
||||
*/
|
||||
public String getFieldFullName(int fieldIndex) {
|
||||
return fields[fieldIndex].getFullName();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the field type of the given index.
|
||||
* @param fieldIndex
|
||||
* the index of the field
|
||||
* @return the field type of the given index
|
||||
*/
|
||||
public String getFieldType(int fieldIndex) {
|
||||
return fields[fieldIndex].type;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the address of the structure. The structure should be filled with data otherwise an exception
|
||||
* is thrown.
|
||||
* @return the address of the feature stored in this structure
|
||||
*/
|
||||
public Long getOldMemoryAddress() {
|
||||
if (oldMemoryAddress.longValue() == -1L) {
|
||||
throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!");
|
||||
}
|
||||
return oldMemoryAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the name of the structure. If the structure has an ID field then the name is returned.
|
||||
* Otherwise the name does not exists and the method returns null.
|
||||
* @return the name of the structure read from the ID field or null
|
||||
*/
|
||||
public String getName() {
|
||||
Object fieldValue = this.getFieldValue("ID");
|
||||
if (fieldValue instanceof Structure) {
|
||||
Structure id = (Structure) fieldValue;
|
||||
return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix
|
||||
}
|
||||
Object name = this.getFieldValue("name", null);
|
||||
return name == null ? null : name.toString().substring(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n");
|
||||
for (int i = 0; i < fields.length; ++i) {
|
||||
result.append(fields[i].toString()).append('\n');
|
||||
}
|
||||
return result.append('}').toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return new Structure(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum enumerates all known data types that can be found in the blend file.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */static enum DataType {
|
||||
|
||||
CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER;
|
||||
/** The map containing the known primary types. */
|
||||
private static final Map<String, DataType> PRIMARY_TYPES = new HashMap<String, DataType>(10);
|
||||
|
||||
static {
|
||||
PRIMARY_TYPES.put("char", CHARACTER);
|
||||
PRIMARY_TYPES.put("uchar", CHARACTER);
|
||||
PRIMARY_TYPES.put("short", SHORT);
|
||||
PRIMARY_TYPES.put("ushort", SHORT);
|
||||
PRIMARY_TYPES.put("int", INTEGER);
|
||||
PRIMARY_TYPES.put("long", LONG);
|
||||
PRIMARY_TYPES.put("ulong", LONG);
|
||||
PRIMARY_TYPES.put("uint64_t", LONG);
|
||||
PRIMARY_TYPES.put("float", FLOAT);
|
||||
PRIMARY_TYPES.put("double", DOUBLE);
|
||||
PRIMARY_TYPES.put("void", VOID);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the data type that is appropriate to the given type name. WARNING! The type recognition
|
||||
* is case sensitive!
|
||||
* @param type
|
||||
* the type name of the data
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return appropriate enum value to the given type name
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown if the given type name does not exist in the blend file
|
||||
*/
|
||||
public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException {
|
||||
DataType result = PRIMARY_TYPES.get(type);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
if (blenderContext.getDnaBlockData().hasStructure(type)) {
|
||||
return STRUCTURE;
|
||||
}
|
||||
throw new BlenderFileException("Unknown data type: " + type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a collection of known primary types names
|
||||
*/
|
||||
/* package */static Collection<String> getKnownPrimaryTypesNames() {
|
||||
return PRIMARY_TYPES.keySet();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,214 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.landscape;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.light.AmbientLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.post.filters.FogFilter;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.textures.ColorBand;
|
||||
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
|
||||
import com.jme3.scene.plugins.blender.textures.ImageUtils;
|
||||
import com.jme3.scene.plugins.blender.textures.TextureHelper;
|
||||
import com.jme3.scene.plugins.blender.textures.TexturePixel;
|
||||
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
|
||||
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Image.Format;
|
||||
import com.jme3.texture.TextureCubeMap;
|
||||
import com.jme3.util.SkyFactory;
|
||||
|
||||
/**
|
||||
* The class that allows to load the following: <li>the ambient light of the scene <li>the sky of the scene (with or without texture)
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class LandscapeHelper extends AbstractBlenderHelper {
|
||||
private static final Logger LOGGER = Logger.getLogger(LandscapeHelper.class.getName());
|
||||
|
||||
private static final int SKYTYPE_BLEND = 1;
|
||||
private static final int SKYTYPE_REAL = 2;
|
||||
private static final int SKYTYPE_PAPER = 4;
|
||||
|
||||
private static final int MODE_MIST = 0x01;
|
||||
|
||||
public LandscapeHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads scene ambient light.
|
||||
* @param worldStructure
|
||||
* the world's blender structure
|
||||
* @return the scene's ambient light
|
||||
*/
|
||||
public Light toAmbientLight(Structure worldStructure) {
|
||||
LOGGER.fine("Loading ambient light.");
|
||||
AmbientLight ambientLight = null;
|
||||
float ambr = ((Number) worldStructure.getFieldValue("ambr")).floatValue();
|
||||
float ambg = ((Number) worldStructure.getFieldValue("ambg")).floatValue();
|
||||
float ambb = ((Number) worldStructure.getFieldValue("ambb")).floatValue();
|
||||
if (ambr > 0 || ambg > 0 || ambb > 0) {
|
||||
ambientLight = new AmbientLight();
|
||||
ColorRGBA ambientLightColor = new ColorRGBA(ambr, ambg, ambb, 0.0f);
|
||||
ambientLight.setColor(ambientLightColor);
|
||||
LOGGER.log(Level.FINE, "Loaded ambient light: {0}.", ambientLightColor);
|
||||
} else {
|
||||
LOGGER.finer("Ambient light is set to BLACK which means: no ambient light! The ambient light node will not be included in the result.");
|
||||
}
|
||||
return ambientLight;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method loads fog for the scene.
|
||||
* NOTICE! Remember to manually set the distance and density of the fog.
|
||||
* Unfortunately blender's fog parameters in no way fit to the JME.
|
||||
* @param worldStructure
|
||||
* the world's structure
|
||||
* @return fog filter or null if scene does not define it
|
||||
*/
|
||||
public FogFilter toFog(Structure worldStructure) {
|
||||
FogFilter result = null;
|
||||
int mode = ((Number) worldStructure.getFieldValue("mode")).intValue();
|
||||
if ((mode & MODE_MIST) != 0) {
|
||||
LOGGER.fine("Loading fog.");
|
||||
result = new FogFilter();
|
||||
result.setName("FIfog");
|
||||
result.setFogColor(this.toBackgroundColor(worldStructure));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the background color.
|
||||
* @param worldStructure
|
||||
* the world's structure
|
||||
* @return the horizon color of the world which is used as a background color.
|
||||
*/
|
||||
public ColorRGBA toBackgroundColor(Structure worldStructure) {
|
||||
float horr = ((Number) worldStructure.getFieldValue("horr")).floatValue();
|
||||
float horg = ((Number) worldStructure.getFieldValue("horg")).floatValue();
|
||||
float horb = ((Number) worldStructure.getFieldValue("horb")).floatValue();
|
||||
return new ColorRGBA(horr, horg, horb, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads scene's sky. Sky can be plain or textured.
|
||||
* If no sky type is selected in blender then no sky is loaded.
|
||||
* @param worldStructure
|
||||
* the world's structure
|
||||
* @return the scene's sky
|
||||
* @throws BlenderFileException
|
||||
* blender exception is thrown when problems with blender file occur
|
||||
*/
|
||||
public Spatial toSky(Structure worldStructure) throws BlenderFileException {
|
||||
int skytype = ((Number) worldStructure.getFieldValue("skytype")).intValue();
|
||||
if (skytype == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LOGGER.fine("Loading sky.");
|
||||
ColorRGBA horizontalColor = this.toBackgroundColor(worldStructure);
|
||||
|
||||
float zenr = ((Number) worldStructure.getFieldValue("zenr")).floatValue();
|
||||
float zeng = ((Number) worldStructure.getFieldValue("zeng")).floatValue();
|
||||
float zenb = ((Number) worldStructure.getFieldValue("zenb")).floatValue();
|
||||
ColorRGBA zenithColor = new ColorRGBA(zenr, zeng, zenb, 1);
|
||||
|
||||
// jutr for this case load generated textures wheather user had set it or not because those might be needed to properly load the sky
|
||||
boolean loadGeneratedTextures = blenderContext.getBlenderKey().isLoadGeneratedTextures();
|
||||
blenderContext.getBlenderKey().setLoadGeneratedTextures(true);
|
||||
|
||||
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
|
||||
List<CombinedTexture> loadedTextures = null;
|
||||
try {
|
||||
loadedTextures = textureHelper.readTextureData(worldStructure, new float[] { horizontalColor.r, horizontalColor.g, horizontalColor.b, horizontalColor.a }, true);
|
||||
} finally {
|
||||
blenderContext.getBlenderKey().setLoadGeneratedTextures(loadGeneratedTextures);
|
||||
}
|
||||
|
||||
TextureCubeMap texture = null;
|
||||
if (loadedTextures != null && loadedTextures.size() > 0) {
|
||||
if (loadedTextures.size() > 1) {
|
||||
throw new IllegalStateException("There should be only one combined texture for sky!");
|
||||
}
|
||||
CombinedTexture combinedTexture = loadedTextures.get(0);
|
||||
texture = combinedTexture.generateSkyTexture(horizontalColor, zenithColor, blenderContext);
|
||||
} else {
|
||||
LOGGER.fine("Preparing colors for colorband.");
|
||||
int colorbandType = ColorBand.IPO_CARDINAL;
|
||||
List<ColorRGBA> colorbandColors = new ArrayList<ColorRGBA>(3);
|
||||
colorbandColors.add(horizontalColor);
|
||||
if ((skytype & SKYTYPE_BLEND) != 0) {
|
||||
if ((skytype & SKYTYPE_PAPER) != 0) {
|
||||
colorbandType = ColorBand.IPO_LINEAR;
|
||||
}
|
||||
if ((skytype & SKYTYPE_REAL) != 0) {
|
||||
colorbandColors.add(0, zenithColor);
|
||||
}
|
||||
colorbandColors.add(zenithColor);
|
||||
}
|
||||
|
||||
int size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize();
|
||||
|
||||
List<Integer> positions = new ArrayList<Integer>(colorbandColors.size());
|
||||
positions.add(0);
|
||||
if (colorbandColors.size() == 2) {
|
||||
positions.add(size - 1);
|
||||
} else if (colorbandColors.size() == 3) {
|
||||
positions.add(size / 2);
|
||||
positions.add(size - 1);
|
||||
}
|
||||
|
||||
LOGGER.fine("Generating sky texture.");
|
||||
float[][] values = new ColorBand(colorbandType, colorbandColors, positions, size).computeValues();
|
||||
|
||||
Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
|
||||
PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
|
||||
TexturePixel pixel = new TexturePixel();
|
||||
|
||||
LOGGER.fine("Creating side textures.");
|
||||
int[] sideImagesIndexes = new int[] { 0, 1, 4, 5 };
|
||||
for (int i : sideImagesIndexes) {
|
||||
for (int y = 0; y < size; ++y) {
|
||||
pixel.red = values[y][0];
|
||||
pixel.green = values[y][1];
|
||||
pixel.blue = values[y][2];
|
||||
|
||||
for (int x = 0; x < size; ++x) {
|
||||
pixelIO.write(image, i, pixel, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.fine("Creating top texture.");
|
||||
pixelIO.read(image, 0, pixel, 0, image.getHeight() - 1);
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = 0; x < size; ++x) {
|
||||
pixelIO.write(image, 3, pixel, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.fine("Creating bottom texture.");
|
||||
pixelIO.read(image, 0, pixel, 0, 0);
|
||||
for (int y = 0; y < size; ++y) {
|
||||
for (int x = 0; x < size; ++x) {
|
||||
pixelIO.write(image, 2, pixel, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
texture = new TextureCubeMap(image);
|
||||
}
|
||||
|
||||
LOGGER.fine("Sky texture created. Creating sky.");
|
||||
return SkyFactory.createSky(blenderContext.getAssetManager(), texture, SkyFactory.EnvMapType.CubeMap);
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.lights;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.light.Light;
|
||||
import com.jme3.light.PointLight;
|
||||
import com.jme3.light.SpotLight;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* A class that is used in light calculations.
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class LightHelper extends AbstractBlenderHelper {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(LightHelper.class.getName());
|
||||
|
||||
/**
|
||||
* This constructor parses the given blender version and stores the result. Some functionalities may differ in
|
||||
* different blender versions.
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public LightHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
public Light toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
Light result = (Light) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
Light light = null;
|
||||
int type = ((Number) structure.getFieldValue("type")).intValue();
|
||||
switch (type) {
|
||||
case 0:// Lamp
|
||||
light = new PointLight();
|
||||
float distance = ((Number) structure.getFieldValue("dist")).floatValue();
|
||||
((PointLight) light).setRadius(distance);
|
||||
break;
|
||||
case 1:// Sun
|
||||
LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine. Using PointLight with radius = Float.MAX_VALUE.");
|
||||
light = new PointLight();
|
||||
((PointLight) light).setRadius(Float.MAX_VALUE);
|
||||
break;
|
||||
case 2:// Spot
|
||||
light = new SpotLight();
|
||||
// range
|
||||
((SpotLight) light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue());
|
||||
// outer angle
|
||||
float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue() * FastMath.DEG_TO_RAD * 0.5f;
|
||||
((SpotLight) light).setSpotOuterAngle(outerAngle);
|
||||
|
||||
// inner angle
|
||||
float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue();
|
||||
spotblend = FastMath.clamp(spotblend, 0, 1);
|
||||
float innerAngle = outerAngle * (1 - spotblend);
|
||||
((SpotLight) light).setSpotInnerAngle(innerAngle);
|
||||
break;
|
||||
case 3:// Hemi
|
||||
LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine. Using DirectionalLight instead.");
|
||||
case 4:// Area
|
||||
light = new DirectionalLight();
|
||||
break;
|
||||
default:
|
||||
throw new BlenderFileException("Unknown light source type: " + type);
|
||||
}
|
||||
float r = ((Number) structure.getFieldValue("r")).floatValue();
|
||||
float g = ((Number) structure.getFieldValue("g")).floatValue();
|
||||
float b = ((Number) structure.getFieldValue("b")).floatValue();
|
||||
light.setColor(new ColorRGBA(r, g, b, 1.0f));
|
||||
light.setName(structure.getName());
|
||||
return light;
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.materials;
|
||||
|
||||
/**
|
||||
* An interface used in calculating alpha mask during particles' texture calculations.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */interface IAlphaMask {
|
||||
/**
|
||||
* This method sets the size of the texture's image.
|
||||
* @param width
|
||||
* the width of the image
|
||||
* @param height
|
||||
* the height of the image
|
||||
*/
|
||||
void setImageSize(int width, int height);
|
||||
|
||||
/**
|
||||
* This method returns the alpha value for the specified texture position.
|
||||
* @param x
|
||||
* the X coordinate of the texture position
|
||||
* @param y
|
||||
* the Y coordinate of the texture position
|
||||
* @return the alpha value for the specified texture position
|
||||
*/
|
||||
byte getAlpha(float x, float y);
|
||||
}
|
@ -1,366 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.materials;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState.BlendMode;
|
||||
import com.jme3.material.RenderState.FaceCullMode;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue.Bucket;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Format;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
|
||||
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
|
||||
import com.jme3.scene.plugins.blender.textures.TextureHelper;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
/**
|
||||
* This class holds the data about the material.
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public final class MaterialContext implements Savable {
|
||||
private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName());
|
||||
|
||||
// texture mapping types
|
||||
public static final int MTEX_COL = 0x01;
|
||||
public static final int MTEX_NOR = 0x02;
|
||||
public static final int MTEX_SPEC = 0x04;
|
||||
public static final int MTEX_EMIT = 0x40;
|
||||
public static final int MTEX_ALPHA = 0x80;
|
||||
public static final int MTEX_AMB = 0x800;
|
||||
|
||||
public static final int FLAG_TRANSPARENT = 0x10000;
|
||||
|
||||
/* package */final String name;
|
||||
/* package */final List<CombinedTexture> loadedTextures;
|
||||
|
||||
/* package */final ColorRGBA diffuseColor;
|
||||
/* package */final DiffuseShader diffuseShader;
|
||||
/* package */final SpecularShader specularShader;
|
||||
/* package */final ColorRGBA specularColor;
|
||||
/* package */final float ambientFactor;
|
||||
/* package */final float shininess;
|
||||
/* package */final boolean shadeless;
|
||||
/* package */final boolean vertexColor;
|
||||
/* package */final boolean transparent;
|
||||
/* package */final boolean vTangent;
|
||||
/* package */FaceCullMode faceCullMode;
|
||||
|
||||
/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
name = structure.getName();
|
||||
|
||||
int mode = ((Number) structure.getFieldValue("mode")).intValue();
|
||||
shadeless = (mode & 0x4) != 0;
|
||||
vertexColor = (mode & 0x80) != 0;
|
||||
vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents
|
||||
|
||||
int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue();
|
||||
diffuseShader = DiffuseShader.values()[diff_shader];
|
||||
ambientFactor = ((Number) structure.getFieldValue("amb")).floatValue();
|
||||
|
||||
if (shadeless) {
|
||||
float r = ((Number) structure.getFieldValue("r")).floatValue();
|
||||
float g = ((Number) structure.getFieldValue("g")).floatValue();
|
||||
float b = ((Number) structure.getFieldValue("b")).floatValue();
|
||||
float alpha = ((Number) structure.getFieldValue("alpha")).floatValue();
|
||||
|
||||
diffuseColor = new ColorRGBA(r, g, b, alpha);
|
||||
specularShader = null;
|
||||
specularColor = null;
|
||||
shininess = 0.0f;
|
||||
} else {
|
||||
diffuseColor = this.readDiffuseColor(structure, diffuseShader);
|
||||
|
||||
int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue();
|
||||
specularShader = SpecularShader.values()[spec_shader];
|
||||
specularColor = this.readSpecularColor(structure);
|
||||
float shininess = ((Number) structure.getFieldValue("har")).floatValue();// this is (probably) the specular hardness in blender
|
||||
this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS;
|
||||
}
|
||||
|
||||
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
|
||||
loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false);
|
||||
|
||||
long flag = ((Number)structure.getFieldValue("flag")).longValue();
|
||||
if((flag & FLAG_TRANSPARENT) != 0) {
|
||||
// veryfying if the transparency is present
|
||||
// (in blender transparent mask is 0x10000 but it's better to verify it because blender can indicate transparency when
|
||||
// it is not required
|
||||
boolean transparent = false;
|
||||
if (diffuseColor != null) {
|
||||
transparent = diffuseColor.a < 1.0f;
|
||||
if (loadedTextures.size() > 0) {// texture covers the material color
|
||||
diffuseColor.set(1, 1, 1, 1);
|
||||
}
|
||||
}
|
||||
if (specularColor != null) {
|
||||
transparent = transparent || specularColor.a < 1.0f;
|
||||
}
|
||||
this.transparent = transparent;
|
||||
} else {
|
||||
transparent = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the material
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies material to a given geometry.
|
||||
*
|
||||
* @param geometry
|
||||
* the geometry
|
||||
* @param geometriesOMA
|
||||
* the geometries OMA
|
||||
* @param userDefinedUVCoordinates
|
||||
* UV coords defined by user
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public void applyMaterial(Geometry geometry, Long geometriesOMA, Map<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
|
||||
Material material = null;
|
||||
if (shadeless) {
|
||||
material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
|
||||
if (!transparent) {
|
||||
diffuseColor.a = 1;
|
||||
}
|
||||
|
||||
material.setColor("Color", diffuseColor);
|
||||
} else {
|
||||
material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md");
|
||||
material.setBoolean("UseMaterialColors", Boolean.TRUE);
|
||||
|
||||
// setting the colors
|
||||
if (!transparent) {
|
||||
diffuseColor.a = 1;
|
||||
}
|
||||
material.setColor("Diffuse", diffuseColor);
|
||||
|
||||
material.setColor("Specular", specularColor);
|
||||
material.setFloat("Shininess", shininess);
|
||||
|
||||
material.setColor("Ambient", new ColorRGBA(ambientFactor, ambientFactor, ambientFactor, 1f));
|
||||
}
|
||||
|
||||
// initializing unused "user-defined UV coords" to all available
|
||||
Map<String, List<Vector2f>> unusedUserDefinedUVCoords = Collections.emptyMap();
|
||||
if(userDefinedUVCoordinates != null && !userDefinedUVCoordinates.isEmpty()) {
|
||||
unusedUserDefinedUVCoords = new HashMap<>(userDefinedUVCoordinates);
|
||||
}
|
||||
|
||||
// applying textures
|
||||
int textureIndex = 0;
|
||||
if (loadedTextures != null && loadedTextures.size() > 0) {
|
||||
if (loadedTextures.size() > TextureHelper.TEXCOORD_TYPES.length) {
|
||||
LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different textures. JME supports only {0} UV mappings.", TextureHelper.TEXCOORD_TYPES.length);
|
||||
}
|
||||
for (CombinedTexture combinedTexture : loadedTextures) {
|
||||
if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
|
||||
String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext);
|
||||
|
||||
this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture());
|
||||
|
||||
if(usedUserUVSet == null || unusedUserDefinedUVCoords.containsKey(usedUserUVSet)) {
|
||||
List<Vector2f> uvs = combinedTexture.getResultUVS();
|
||||
if(uvs != null && uvs.size() > 0) {
|
||||
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);
|
||||
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
|
||||
geometry.getMesh().setBuffer(uvCoordsBuffer);
|
||||
}//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file)
|
||||
|
||||
// Remove used "user-defined UV coords" from the unused collection
|
||||
if(usedUserUVSet != null) {
|
||||
unusedUserDefinedUVCoords.remove(usedUserUVSet);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unusedUserDefinedUVCoords != null && unusedUserDefinedUVCoords.size() > 0) {
|
||||
LOGGER.fine("Storing unused, user defined UV coordinates sets.");
|
||||
if (unusedUserDefinedUVCoords.size() > TextureHelper.TEXCOORD_TYPES.length) {
|
||||
LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length);
|
||||
}
|
||||
for (Entry<String, List<Vector2f>> entry : unusedUserDefinedUVCoords.entrySet()) {
|
||||
if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) {
|
||||
List<Vector2f> uvs = entry.getValue();
|
||||
VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]);
|
||||
uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()])));
|
||||
geometry.getMesh().setBuffer(uvCoordsBuffer);
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "The user's UV set named: '{0}' could not be stored because JME only supports up to {1} different UV's.", new Object[] {
|
||||
entry.getKey(), TextureHelper.TEXCOORD_TYPES.length
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applying additional data
|
||||
material.setName(name);
|
||||
if (vertexColor) {
|
||||
material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true);
|
||||
}
|
||||
material.getAdditionalRenderState().setFaceCullMode(faceCullMode != null ? faceCullMode : blenderContext.getBlenderKey().getFaceCullMode());
|
||||
if (transparent) {
|
||||
material.setTransparent(true);
|
||||
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
|
||||
geometry.setQueueBucket(Bucket.Transparent);
|
||||
}
|
||||
|
||||
geometry.setMaterial(material);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the texture to the given material.
|
||||
*
|
||||
* @param material
|
||||
* the material that we add texture to
|
||||
* @param mapTo
|
||||
* the texture mapping type
|
||||
* @param texture
|
||||
* the added texture
|
||||
*/
|
||||
private void setTexture(Material material, int mapTo, Texture texture) {
|
||||
switch (mapTo) {
|
||||
case MTEX_COL:
|
||||
material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture);
|
||||
break;
|
||||
case MTEX_NOR:
|
||||
material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture);
|
||||
break;
|
||||
case MTEX_SPEC:
|
||||
material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture);
|
||||
break;
|
||||
case MTEX_EMIT:
|
||||
material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture);
|
||||
break;
|
||||
case MTEX_ALPHA:
|
||||
if (!shadeless) {
|
||||
material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture);
|
||||
} else {
|
||||
LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name);
|
||||
}
|
||||
break;
|
||||
case MTEX_AMB:
|
||||
material.setTexture(MaterialHelper.TEXTURE_TYPE_LIGHTMAP, texture);
|
||||
break;
|
||||
default:
|
||||
LOGGER.severe("Unknown mapping type: " + mapTo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if the material has at least one generated texture and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean hasGeneratedTextures() {
|
||||
if (loadedTextures != null) {
|
||||
for (CombinedTexture generatedTextures : loadedTextures) {
|
||||
if (generatedTextures.hasGeneratedTextures()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the face cull mode.
|
||||
* @param faceCullMode
|
||||
* the face cull mode
|
||||
*/
|
||||
public void setFaceCullMode(FaceCullMode faceCullMode) {
|
||||
this.faceCullMode = faceCullMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the diffuse color.
|
||||
*
|
||||
* @param materialStructure
|
||||
* the material structure
|
||||
* @param diffuseShader
|
||||
* the diffuse shader
|
||||
* @return the diffuse color
|
||||
*/
|
||||
private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) {
|
||||
// bitwise 'or' of all textures mappings
|
||||
int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue();
|
||||
|
||||
// diffuse color
|
||||
float r = ((Number) materialStructure.getFieldValue("r")).floatValue();
|
||||
float g = ((Number) materialStructure.getFieldValue("g")).floatValue();
|
||||
float b = ((Number) materialStructure.getFieldValue("b")).floatValue();
|
||||
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
|
||||
if ((commonMapto & 0x01) == 0x01) {// Col
|
||||
return new ColorRGBA(r, g, b, alpha);
|
||||
} else {
|
||||
switch (diffuseShader) {
|
||||
case FRESNEL:
|
||||
case ORENNAYAR:
|
||||
case TOON:
|
||||
break;// TODO: find what is the proper modification
|
||||
case MINNAERT:
|
||||
case LAMBERT:// TODO: check if that is correct
|
||||
float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue();
|
||||
r *= ref;
|
||||
g *= ref;
|
||||
b *= ref;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString());
|
||||
}
|
||||
return new ColorRGBA(r, g, b, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns a specular color used by the material.
|
||||
*
|
||||
* @param materialStructure
|
||||
* the material structure filled with data
|
||||
* @return a specular color used by the material
|
||||
*/
|
||||
private ColorRGBA readSpecularColor(Structure materialStructure) {
|
||||
float specularIntensity = ((Number) materialStructure.getFieldValue("spec")).floatValue();
|
||||
float r = ((Number) materialStructure.getFieldValue("specr")).floatValue() * specularIntensity;
|
||||
float g = ((Number) materialStructure.getFieldValue("specg")).floatValue() * specularIntensity;
|
||||
float b = ((Number) materialStructure.getFieldValue("specb")).floatValue() * specularIntensity;
|
||||
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
|
||||
return new ColorRGBA(r, g, b, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
throw new IOException("Material context is not for saving! It implements savable only to be passed to another blend file as a Savable in user data!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(JmeImporter e) throws IOException {
|
||||
throw new IOException("Material context is not for loading! It implements savable only to be passed to another blend file as a Savable in user data!");
|
||||
}
|
||||
}
|
@ -1,379 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.materials;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.material.MatParam;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.shader.VarType;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Image.Format;
|
||||
import com.jme3.texture.image.ColorSpace;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
public class MaterialHelper extends AbstractBlenderHelper {
|
||||
private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName());
|
||||
protected static final float DEFAULT_SHININESS = 20.0f;
|
||||
|
||||
public static final String TEXTURE_TYPE_COLOR = "ColorMap";
|
||||
public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap";
|
||||
public static final String TEXTURE_TYPE_NORMAL = "NormalMap";
|
||||
public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap";
|
||||
public static final String TEXTURE_TYPE_GLOW = "GlowMap";
|
||||
public static final String TEXTURE_TYPE_ALPHA = "AlphaMap";
|
||||
public static final String TEXTURE_TYPE_LIGHTMAP = "LightMap";
|
||||
|
||||
public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0);
|
||||
public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1);
|
||||
public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2);
|
||||
public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3);
|
||||
protected final Map<Integer, IAlphaMask> alphaMasks = new HashMap<Integer, IAlphaMask>();
|
||||
|
||||
/**
|
||||
* The type of the material's diffuse shader.
|
||||
*/
|
||||
public static enum DiffuseShader {
|
||||
LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the material's specular shader.
|
||||
*/
|
||||
public static enum SpecularShader {
|
||||
COOKTORRENCE, PHONG, BLINN, TOON, WARDISO
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
|
||||
* versions.
|
||||
*
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public MaterialHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
// setting alpha masks
|
||||
alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() {
|
||||
public void setImageSize(int width, int height) {
|
||||
}
|
||||
|
||||
public byte getAlpha(float x, float y) {
|
||||
return (byte) 255;
|
||||
}
|
||||
});
|
||||
alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() {
|
||||
private float r;
|
||||
private float[] center;
|
||||
|
||||
public void setImageSize(int width, int height) {
|
||||
r = Math.min(width, height) * 0.5f;
|
||||
center = new float[] { width * 0.5f, height * 0.5f };
|
||||
}
|
||||
|
||||
public byte getAlpha(float x, float y) {
|
||||
float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));
|
||||
return (byte) (d >= r ? 0 : 255);
|
||||
}
|
||||
});
|
||||
alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() {
|
||||
private float r;
|
||||
private float[] center;
|
||||
|
||||
public void setImageSize(int width, int height) {
|
||||
r = Math.min(width, height) * 0.5f;
|
||||
center = new float[] { width * 0.5f, height * 0.5f };
|
||||
}
|
||||
|
||||
public byte getAlpha(float x, float y) {
|
||||
float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1])));
|
||||
return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f);
|
||||
}
|
||||
});
|
||||
alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() {
|
||||
private float r;
|
||||
private float[] center;
|
||||
|
||||
public void setImageSize(int width, int height) {
|
||||
r = Math.min(width, height) * 0.5f;
|
||||
center = new float[] { width * 0.5f, height * 0.5f };
|
||||
}
|
||||
|
||||
public byte getAlpha(float x, float y) {
|
||||
float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r;
|
||||
return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the material structure to jme Material.
|
||||
* @param structure
|
||||
* structure with material data
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return jme material
|
||||
* @throws BlenderFileException
|
||||
* an exception is throw when problems with blend file occur
|
||||
*/
|
||||
public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if ("ID".equals(structure.getType())) {
|
||||
LOGGER.fine("Loading material from external blend file.");
|
||||
return (MaterialContext) this.loadLibrary(structure);
|
||||
}
|
||||
|
||||
LOGGER.fine("Loading material.");
|
||||
result = new MaterialContext(structure, blenderContext);
|
||||
LOGGER.log(Level.FINE, "Material''s name: {0}", result.name);
|
||||
Long oma = structure.getOldMemoryAddress();
|
||||
blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, structure);
|
||||
blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the given material into particles-usable material.
|
||||
* The texture and glow color are being copied.
|
||||
* The method assumes it receives the Lighting type of material.
|
||||
* @param material
|
||||
* the source material
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return material converted into particles-usable material
|
||||
*/
|
||||
public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) {
|
||||
Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
|
||||
|
||||
// copying texture
|
||||
MatParam diffuseMap = material.getParam("DiffuseMap");
|
||||
if (diffuseMap != null) {
|
||||
Texture texture = ((Texture) diffuseMap.getValue()).clone();
|
||||
|
||||
// applying alpha mask to the texture
|
||||
Image image = texture.getImage();
|
||||
ByteBuffer sourceBB = image.getData(0);
|
||||
sourceBB.rewind();
|
||||
int w = image.getWidth();
|
||||
int h = image.getHeight();
|
||||
ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4);
|
||||
IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex);
|
||||
iAlphaMask.setImageSize(w, h);
|
||||
|
||||
for (int x = 0; x < w; ++x) {
|
||||
for (int y = 0; y < h; ++y) {
|
||||
bb.put(sourceBB.get());
|
||||
bb.put(sourceBB.get());
|
||||
bb.put(sourceBB.get());
|
||||
bb.put(iAlphaMask.getAlpha(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
image = new Image(Format.RGBA8, w, h, bb, ColorSpace.Linear);
|
||||
texture.setImage(image);
|
||||
|
||||
result.setTextureParam("Texture", VarType.Texture2D, texture);
|
||||
}
|
||||
|
||||
// copying glow color
|
||||
MatParam glowColor = material.getParam("GlowColor");
|
||||
if (glowColor != null) {
|
||||
ColorRGBA color = (ColorRGBA) glowColor.getValue();
|
||||
result.setParam("GlowColor", VarType.Vector3, color);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or
|
||||
* curve) but needs to have 'mat' field/
|
||||
*
|
||||
* @param structureWithMaterials
|
||||
* the structure containing the mesh data
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return a list of vertices colors, each color belongs to a single vertex
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blend file structure is somehow invalid or corrupted
|
||||
*/
|
||||
public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException {
|
||||
Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat");
|
||||
MaterialContext[] materials = null;
|
||||
if (ppMaterials.isNotNull()) {
|
||||
List<Structure> materialStructures = ppMaterials.fetchData();
|
||||
if (materialStructures != null && materialStructures.size() > 0) {
|
||||
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
|
||||
materials = new MaterialContext[materialStructures.size()];
|
||||
int i = 0;
|
||||
for (Structure s : materialStructures) {
|
||||
materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
return materials;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts rgb values to hsv values.
|
||||
*
|
||||
* @param r
|
||||
* red value of the color
|
||||
* @param g
|
||||
* green value of the color
|
||||
* @param b
|
||||
* blue value of the color
|
||||
* @param hsv
|
||||
* hsv values of a color (this table contains the result of the transformation)
|
||||
*/
|
||||
public void rgbToHsv(float r, float g, float b, float[] hsv) {
|
||||
float cmax = r;
|
||||
float cmin = r;
|
||||
cmax = g > cmax ? g : cmax;
|
||||
cmin = g < cmin ? g : cmin;
|
||||
cmax = b > cmax ? b : cmax;
|
||||
cmin = b < cmin ? b : cmin;
|
||||
|
||||
hsv[2] = cmax; /* value */
|
||||
if (cmax != 0.0) {
|
||||
hsv[1] = (cmax - cmin) / cmax;
|
||||
} else {
|
||||
hsv[1] = 0.0f;
|
||||
hsv[0] = 0.0f;
|
||||
}
|
||||
if (hsv[1] == 0.0) {
|
||||
hsv[0] = -1.0f;
|
||||
} else {
|
||||
float cdelta = cmax - cmin;
|
||||
float rc = (cmax - r) / cdelta;
|
||||
float gc = (cmax - g) / cdelta;
|
||||
float bc = (cmax - b) / cdelta;
|
||||
if (r == cmax) {
|
||||
hsv[0] = bc - gc;
|
||||
} else if (g == cmax) {
|
||||
hsv[0] = 2.0f + rc - bc;
|
||||
} else {
|
||||
hsv[0] = 4.0f + gc - rc;
|
||||
}
|
||||
hsv[0] *= 60.0f;
|
||||
if (hsv[0] < 0.0f) {
|
||||
hsv[0] += 360.0f;
|
||||
}
|
||||
}
|
||||
|
||||
hsv[0] /= 360.0f;
|
||||
if (hsv[0] < 0.0f) {
|
||||
hsv[0] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts rgb values to hsv values.
|
||||
*
|
||||
* @param h
|
||||
* hue
|
||||
* @param s
|
||||
* saturation
|
||||
* @param v
|
||||
* value
|
||||
* @param rgb
|
||||
* rgb result vector (should have 3 elements)
|
||||
*/
|
||||
public void hsvToRgb(float h, float s, float v, float[] rgb) {
|
||||
h *= 360.0f;
|
||||
if (s == 0.0) {
|
||||
rgb[0] = rgb[1] = rgb[2] = v;
|
||||
} else {
|
||||
if (h == 360) {
|
||||
h = 0;
|
||||
} else {
|
||||
h /= 60;
|
||||
}
|
||||
int i = (int) Math.floor(h);
|
||||
float f = h - i;
|
||||
float p = v * (1.0f - s);
|
||||
float q = v * (1.0f - s * f);
|
||||
float t = v * (1.0f - s * (1.0f - f));
|
||||
switch (i) {
|
||||
case 0:
|
||||
rgb[0] = v;
|
||||
rgb[1] = t;
|
||||
rgb[2] = p;
|
||||
break;
|
||||
case 1:
|
||||
rgb[0] = q;
|
||||
rgb[1] = v;
|
||||
rgb[2] = p;
|
||||
break;
|
||||
case 2:
|
||||
rgb[0] = p;
|
||||
rgb[1] = v;
|
||||
rgb[2] = t;
|
||||
break;
|
||||
case 3:
|
||||
rgb[0] = p;
|
||||
rgb[1] = q;
|
||||
rgb[2] = v;
|
||||
break;
|
||||
case 4:
|
||||
rgb[0] = t;
|
||||
rgb[1] = p;
|
||||
rgb[2] = v;
|
||||
break;
|
||||
case 5:
|
||||
rgb[0] = v;
|
||||
rgb[1] = p;
|
||||
rgb[2] = q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,581 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.math;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.Quaternion;
|
||||
|
||||
/**
|
||||
* <code>DQuaternion</code> defines a single example of a more general class of
|
||||
* hypercomplex numbers. DQuaternions extends a rotation in three dimensions to a
|
||||
* rotation in four dimensions. This avoids "gimbal lock" and allows for smooth
|
||||
* continuous rotation.
|
||||
*
|
||||
* <code>DQuaternion</code> is defined by four double point numbers: {x y z w}.
|
||||
*
|
||||
* This class's only purpose is to give better accuracy in floating point operations during computations.
|
||||
* This is made by copying the original Quaternion class from jme3 core and leaving only required methods and basic computation methods, so that
|
||||
* the class is smaller and easier to maintain.
|
||||
* Should any other methods be needed, they will be added.
|
||||
*
|
||||
* @author Mark Powell
|
||||
* @author Joshua Slack
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public final class DQuaternion implements Savable, Cloneable, java.io.Serializable {
|
||||
private static final long serialVersionUID = 5009180713885017539L;
|
||||
|
||||
/**
|
||||
* Represents the identity quaternion rotation (0, 0, 0, 1).
|
||||
*/
|
||||
public static final DQuaternion IDENTITY = new DQuaternion();
|
||||
public static final DQuaternion DIRECTION_Z = new DQuaternion();
|
||||
public static final DQuaternion ZERO = new DQuaternion(0, 0, 0, 0);
|
||||
protected double x, y, z, w = 1;
|
||||
|
||||
/**
|
||||
* Constructor instantiates a new <code>DQuaternion</code> object
|
||||
* initializing all values to zero, except w which is initialized to 1.
|
||||
*
|
||||
*/
|
||||
public DQuaternion() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor instantiates a new <code>DQuaternion</code> object from the
|
||||
* given list of parameters.
|
||||
*
|
||||
* @param x
|
||||
* the x value of the quaternion.
|
||||
* @param y
|
||||
* the y value of the quaternion.
|
||||
* @param z
|
||||
* the z value of the quaternion.
|
||||
* @param w
|
||||
* the w value of the quaternion.
|
||||
*/
|
||||
public DQuaternion(double x, double y, double z, double w) {
|
||||
this.set(x, y, z, w);
|
||||
}
|
||||
|
||||
public DQuaternion(Quaternion q) {
|
||||
this(q.getX(), q.getY(), q.getZ(), q.getW());
|
||||
}
|
||||
|
||||
public Quaternion toQuaternion() {
|
||||
return new Quaternion((float) x, (float) y, (float) z, (float) w);
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public double getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public double getW() {
|
||||
return w;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the data in a <code>DQuaternion</code> object from the given list
|
||||
* of parameters.
|
||||
*
|
||||
* @param x
|
||||
* the x value of the quaternion.
|
||||
* @param y
|
||||
* the y value of the quaternion.
|
||||
* @param z
|
||||
* the z value of the quaternion.
|
||||
* @param w
|
||||
* the w value of the quaternion.
|
||||
* @return this
|
||||
*/
|
||||
public DQuaternion set(double x, double y, double z, double w) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.w = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data in this <code>DQuaternion</code> object to be equal to the
|
||||
* passed <code>DQuaternion</code> object. The values are copied producing
|
||||
* a new object.
|
||||
*
|
||||
* @param q
|
||||
* The DQuaternion to copy values from.
|
||||
* @return this
|
||||
*/
|
||||
public DQuaternion set(DQuaternion q) {
|
||||
x = q.x;
|
||||
y = q.y;
|
||||
z = q.z;
|
||||
w = q.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this DQuaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1).
|
||||
*/
|
||||
public void loadIdentity() {
|
||||
x = y = z = 0;
|
||||
w = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>norm</code> returns the norm of this quaternion. This is the dot
|
||||
* product of this quaternion with itself.
|
||||
*
|
||||
* @return the norm of the quaternion.
|
||||
*/
|
||||
public double norm() {
|
||||
return w * w + x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
public DQuaternion fromRotationMatrix(double m00, double m01, double m02,
|
||||
double m10, double m11, double m12, double m20, double m21, double m22) {
|
||||
// first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix
|
||||
// so that the scale does not affect the rotation
|
||||
double lengthSquared = m00 * m00 + m10 * m10 + m20 * m20;
|
||||
if (lengthSquared != 1f && lengthSquared != 0f) {
|
||||
lengthSquared = 1.0 / Math.sqrt(lengthSquared);
|
||||
m00 *= lengthSquared;
|
||||
m10 *= lengthSquared;
|
||||
m20 *= lengthSquared;
|
||||
}
|
||||
lengthSquared = m01 * m01 + m11 * m11 + m21 * m21;
|
||||
if (lengthSquared != 1 && lengthSquared != 0f) {
|
||||
lengthSquared = 1.0 / Math.sqrt(lengthSquared);
|
||||
m01 *= lengthSquared;
|
||||
m11 *= lengthSquared;
|
||||
m21 *= lengthSquared;
|
||||
}
|
||||
lengthSquared = m02 * m02 + m12 * m12 + m22 * m22;
|
||||
if (lengthSquared != 1f && lengthSquared != 0f) {
|
||||
lengthSquared = 1.0 / Math.sqrt(lengthSquared);
|
||||
m02 *= lengthSquared;
|
||||
m12 *= lengthSquared;
|
||||
m22 *= lengthSquared;
|
||||
}
|
||||
|
||||
// Use the Graphics Gems code, from
|
||||
// ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z
|
||||
// *NOT* the "Matrix and Quaternions FAQ", which has errors!
|
||||
|
||||
// the trace is the sum of the diagonal elements; see
|
||||
// http://mathworld.wolfram.com/MatrixTrace.html
|
||||
double t = m00 + m11 + m22;
|
||||
|
||||
// we protect the division by s by ensuring that s>=1
|
||||
if (t >= 0) { // |w| >= .5
|
||||
double s = Math.sqrt(t + 1); // |s|>=1 ...
|
||||
w = 0.5f * s;
|
||||
s = 0.5f / s; // so this division isn't bad
|
||||
x = (m21 - m12) * s;
|
||||
y = (m02 - m20) * s;
|
||||
z = (m10 - m01) * s;
|
||||
} else if (m00 > m11 && m00 > m22) {
|
||||
double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1
|
||||
x = s * 0.5f; // |x| >= .5
|
||||
s = 0.5f / s;
|
||||
y = (m10 + m01) * s;
|
||||
z = (m02 + m20) * s;
|
||||
w = (m21 - m12) * s;
|
||||
} else if (m11 > m22) {
|
||||
double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1
|
||||
y = s * 0.5f; // |y| >= .5
|
||||
s = 0.5f / s;
|
||||
x = (m10 + m01) * s;
|
||||
z = (m21 + m12) * s;
|
||||
w = (m02 - m20) * s;
|
||||
} else {
|
||||
double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1
|
||||
z = s * 0.5f; // |z| >= .5
|
||||
s = 0.5f / s;
|
||||
x = (m02 + m20) * s;
|
||||
y = (m21 + m12) * s;
|
||||
w = (m10 - m01) * s;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>toRotationMatrix</code> converts this quaternion to a rotational
|
||||
* matrix. The result is stored in result. 4th row and 4th column values are
|
||||
* untouched. Note: the result is created from a normalized version of this quat.
|
||||
*
|
||||
* @param result
|
||||
* The Matrix4f to store the result in.
|
||||
* @return the rotation matrix representation of this quaternion.
|
||||
*/
|
||||
public Matrix toRotationMatrix(Matrix result) {
|
||||
Vector3d originalScale = new Vector3d();
|
||||
|
||||
result.toScaleVector(originalScale);
|
||||
result.setScale(1, 1, 1);
|
||||
double norm = this.norm();
|
||||
// we explicitly test norm against one here, saving a division
|
||||
// at the cost of a test and branch. Is it worth it?
|
||||
double s = norm == 1f ? 2f : norm > 0f ? 2f / norm : 0;
|
||||
|
||||
// compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
|
||||
// will be used 2-4 times each.
|
||||
double xs = x * s;
|
||||
double ys = y * s;
|
||||
double zs = z * s;
|
||||
double xx = x * xs;
|
||||
double xy = x * ys;
|
||||
double xz = x * zs;
|
||||
double xw = w * xs;
|
||||
double yy = y * ys;
|
||||
double yz = y * zs;
|
||||
double yw = w * ys;
|
||||
double zz = z * zs;
|
||||
double zw = w * zs;
|
||||
|
||||
// using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
|
||||
result.set(0, 0, 1 - (yy + zz));
|
||||
result.set(0, 1, xy - zw);
|
||||
result.set(0, 2, xz + yw);
|
||||
result.set(1, 0, xy + zw);
|
||||
result.set(1, 1, 1 - (xx + zz));
|
||||
result.set(1, 2, yz - xw);
|
||||
result.set(2, 0, xz - yw);
|
||||
result.set(2, 1, yz + xw);
|
||||
result.set(2, 2, 1 - (xx + yy));
|
||||
|
||||
result.setScale(originalScale);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>fromAngleAxis</code> sets this quaternion to the values specified
|
||||
* by an angle and an axis of rotation. This method creates an object, so
|
||||
* use fromAngleNormalAxis if your axis is already normalized.
|
||||
*
|
||||
* @param angle
|
||||
* the angle to rotate (in radians).
|
||||
* @param axis
|
||||
* the axis of rotation.
|
||||
* @return this quaternion
|
||||
*/
|
||||
public DQuaternion fromAngleAxis(double angle, Vector3d axis) {
|
||||
Vector3d normAxis = axis.normalize();
|
||||
this.fromAngleNormalAxis(angle, normAxis);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>fromAngleNormalAxis</code> sets this quaternion to the values
|
||||
* specified by an angle and a normalized axis of rotation.
|
||||
*
|
||||
* @param angle
|
||||
* the angle to rotate (in radians).
|
||||
* @param axis
|
||||
* the axis of rotation (already normalized).
|
||||
*/
|
||||
public DQuaternion fromAngleNormalAxis(double angle, Vector3d axis) {
|
||||
if (axis.x == 0 && axis.y == 0 && axis.z == 0) {
|
||||
this.loadIdentity();
|
||||
} else {
|
||||
double halfAngle = 0.5f * angle;
|
||||
double sin = Math.sin(halfAngle);
|
||||
w = Math.cos(halfAngle);
|
||||
x = sin * axis.x;
|
||||
y = sin * axis.y;
|
||||
z = sin * axis.z;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>add</code> adds the values of this quaternion to those of the
|
||||
* parameter quaternion. The result is returned as a new quaternion.
|
||||
*
|
||||
* @param q
|
||||
* the quaternion to add to this.
|
||||
* @return the new quaternion.
|
||||
*/
|
||||
public DQuaternion add(DQuaternion q) {
|
||||
return new DQuaternion(x + q.x, y + q.y, z + q.z, w + q.w);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>add</code> adds the values of this quaternion to those of the
|
||||
* parameter quaternion. The result is stored in this DQuaternion.
|
||||
*
|
||||
* @param q
|
||||
* the quaternion to add to this.
|
||||
* @return This DQuaternion after addition.
|
||||
*/
|
||||
public DQuaternion addLocal(DQuaternion q) {
|
||||
x += q.x;
|
||||
y += q.y;
|
||||
z += q.z;
|
||||
w += q.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>subtract</code> subtracts the values of the parameter quaternion
|
||||
* from those of this quaternion. The result is returned as a new
|
||||
* quaternion.
|
||||
*
|
||||
* @param q
|
||||
* the quaternion to subtract from this.
|
||||
* @return the new quaternion.
|
||||
*/
|
||||
public DQuaternion subtract(DQuaternion q) {
|
||||
return new DQuaternion(x - q.x, y - q.y, z - q.z, w - q.w);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>subtract</code> subtracts the values of the parameter quaternion
|
||||
* from those of this quaternion. The result is stored in this DQuaternion.
|
||||
*
|
||||
* @param q
|
||||
* the quaternion to subtract from this.
|
||||
* @return This DQuaternion after subtraction.
|
||||
*/
|
||||
public DQuaternion subtractLocal(DQuaternion q) {
|
||||
x -= q.x;
|
||||
y -= q.y;
|
||||
z -= q.z;
|
||||
w -= q.w;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>mult</code> multiplies this quaternion by a parameter quaternion.
|
||||
* The result is returned as a new quaternion. It should be noted that
|
||||
* quaternion multiplication is not commutative so q * p != p * q.
|
||||
*
|
||||
* @param q
|
||||
* the quaternion to multiply this quaternion by.
|
||||
* @return the new quaternion.
|
||||
*/
|
||||
public DQuaternion mult(DQuaternion q) {
|
||||
return this.mult(q, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>mult</code> multiplies this quaternion by a parameter quaternion.
|
||||
* The result is returned as a new quaternion. It should be noted that
|
||||
* quaternion multiplication is not commutative so q * p != p * q.
|
||||
*
|
||||
* It IS safe for q and res to be the same object.
|
||||
* It IS NOT safe for this and res to be the same object.
|
||||
*
|
||||
* @param q
|
||||
* the quaternion to multiply this quaternion by.
|
||||
* @param res
|
||||
* the quaternion to store the result in.
|
||||
* @return the new quaternion.
|
||||
*/
|
||||
public DQuaternion mult(DQuaternion q, DQuaternion res) {
|
||||
if (res == null) {
|
||||
res = new DQuaternion();
|
||||
}
|
||||
double qw = q.w, qx = q.x, qy = q.y, qz = q.z;
|
||||
res.x = x * qw + y * qz - z * qy + w * qx;
|
||||
res.y = -x * qz + y * qw + z * qx + w * qy;
|
||||
res.z = x * qy - y * qx + z * qw + w * qz;
|
||||
res.w = -x * qx - y * qy - z * qz + w * qw;
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>mult</code> multiplies this quaternion by a parameter vector. The
|
||||
* result is returned as a new vector.
|
||||
*
|
||||
* @param v
|
||||
* the vector to multiply this quaternion by.
|
||||
* @return the new vector.
|
||||
*/
|
||||
public Vector3d mult(Vector3d v) {
|
||||
return this.mult(v, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies this DQuaternion by the supplied quaternion. The result is
|
||||
* stored in this DQuaternion, which is also returned for chaining. Similar
|
||||
* to this *= q.
|
||||
*
|
||||
* @param q
|
||||
* The DQuaternion to multiply this one by.
|
||||
* @return This DQuaternion, after multiplication.
|
||||
*/
|
||||
public DQuaternion multLocal(DQuaternion q) {
|
||||
double x1 = x * q.w + y * q.z - z * q.y + w * q.x;
|
||||
double y1 = -x * q.z + y * q.w + z * q.x + w * q.y;
|
||||
double z1 = x * q.y - y * q.x + z * q.w + w * q.z;
|
||||
w = -x * q.x - y * q.y - z * q.z + w * q.w;
|
||||
x = x1;
|
||||
y = y1;
|
||||
z = z1;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>mult</code> multiplies this quaternion by a parameter vector. The
|
||||
* result is returned as a new vector.
|
||||
*
|
||||
* @param v
|
||||
* the vector to multiply this quaternion by.
|
||||
* @param store
|
||||
* the vector to store the result in. It IS safe for v and store
|
||||
* to be the same object.
|
||||
* @return the result vector.
|
||||
*/
|
||||
public Vector3d mult(Vector3d v, Vector3d store) {
|
||||
if (store == null) {
|
||||
store = new Vector3d();
|
||||
}
|
||||
if (v.x == 0 && v.y == 0 && v.z == 0) {
|
||||
store.set(0, 0, 0);
|
||||
} else {
|
||||
double vx = v.x, vy = v.y, vz = v.z;
|
||||
store.x = w * w * vx + 2 * y * w * vz - 2 * z * w * vy + x * x * vx + 2 * y * x * vy + 2 * z * x * vz - z * z * vx - y * y * vx;
|
||||
store.y = 2 * x * y * vx + y * y * vy + 2 * z * y * vz + 2 * w * z * vx - z * z * vy + w * w * vy - 2 * x * w * vz - x * x * vy;
|
||||
store.z = 2 * x * z * vx + 2 * y * z * vy + z * z * vz - 2 * w * y * vx - y * y * vz + 2 * w * x * vy - x * x * vz + w * w * vz;
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>toString</code> creates the string representation of this <code>DQuaternion</code>. The values of the quaternion are displaced (x,
|
||||
* y, z, w), in the following manner: <br>
|
||||
* (x, y, z, w)
|
||||
*
|
||||
* @return the string representation of this object.
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + x + ", " + y + ", " + z + ", " + w + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>equals</code> determines if two quaternions are logically equal,
|
||||
* that is, if the values of (x, y, z, w) are the same for both quaternions.
|
||||
*
|
||||
* @param o
|
||||
* the object to compare for equality
|
||||
* @return true if they are equal, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof DQuaternion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DQuaternion comp = (DQuaternion) o;
|
||||
if (Double.compare(x, comp.x) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (Double.compare(y, comp.y) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (Double.compare(z, comp.z) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (Double.compare(w, comp.w) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>hashCode</code> returns the hash code value as an integer and is
|
||||
* supported for the benefit of hashing based collection classes such as
|
||||
* Hashtable, HashMap, HashSet etc.
|
||||
*
|
||||
* @return the hashcode for this instance of DQuaternion.
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long hash = 37;
|
||||
hash = 37 * hash + Double.doubleToLongBits(x);
|
||||
hash = 37 * hash + Double.doubleToLongBits(y);
|
||||
hash = 37 * hash + Double.doubleToLongBits(z);
|
||||
hash = 37 * hash + Double.doubleToLongBits(w);
|
||||
return (int) hash;
|
||||
|
||||
}
|
||||
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
OutputCapsule cap = e.getCapsule(this);
|
||||
cap.write(x, "x", 0);
|
||||
cap.write(y, "y", 0);
|
||||
cap.write(z, "z", 0);
|
||||
cap.write(w, "w", 1);
|
||||
}
|
||||
|
||||
public void read(JmeImporter e) throws IOException {
|
||||
InputCapsule cap = e.getCapsule(this);
|
||||
x = cap.readFloat("x", 0);
|
||||
y = cap.readFloat("y", 0);
|
||||
z = cap.readFloat("z", 0);
|
||||
w = cap.readFloat("w", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DQuaternion clone() {
|
||||
try {
|
||||
return (DQuaternion) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(); // can not happen
|
||||
}
|
||||
}
|
||||
}
|
@ -1,188 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.math;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
/**
|
||||
* Started Date: Jul 16, 2004<br>
|
||||
* <br>
|
||||
* Represents a translation, rotation and scale in one object.
|
||||
*
|
||||
* This class's only purpose is to give better accuracy in floating point operations during computations.
|
||||
* This is made by copying the original Transfrom class from jme3 core and removing unnecessary methods so that
|
||||
* the class is smaller and easier to maintain.
|
||||
* Should any other methods be needed, they will be added.
|
||||
*
|
||||
* @author Jack Lindamood
|
||||
* @author Joshua Slack
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public final class DTransform implements Savable, Cloneable, java.io.Serializable {
|
||||
private static final long serialVersionUID = 7812915425940606722L;
|
||||
|
||||
private DQuaternion rotation;
|
||||
private Vector3d translation;
|
||||
private Vector3d scale;
|
||||
|
||||
public DTransform() {
|
||||
translation = new Vector3d();
|
||||
rotation = new DQuaternion();
|
||||
scale = new Vector3d();
|
||||
}
|
||||
|
||||
public DTransform(Transform transform) {
|
||||
translation = new Vector3d(transform.getTranslation());
|
||||
rotation = new DQuaternion(transform.getRotation());
|
||||
scale = new Vector3d(transform.getScale());
|
||||
}
|
||||
|
||||
public Transform toTransform() {
|
||||
return new Transform(translation.toVector3f(), rotation.toQuaternion(), scale.toVector3f());
|
||||
}
|
||||
|
||||
public Matrix toMatrix() {
|
||||
Matrix m = Matrix.identity(4);
|
||||
m.setTranslation(translation);
|
||||
m.setRotationQuaternion(rotation);
|
||||
m.setScale(scale);
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this translation to the given value.
|
||||
* @param trans
|
||||
* The new translation for this matrix.
|
||||
* @return this
|
||||
*/
|
||||
public DTransform setTranslation(Vector3d trans) {
|
||||
translation.set(trans);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this rotation to the given DQuaternion value.
|
||||
* @param rot
|
||||
* The new rotation for this matrix.
|
||||
* @return this
|
||||
*/
|
||||
public DTransform setRotation(DQuaternion rot) {
|
||||
rotation.set(rot);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this scale to the given value.
|
||||
* @param scale
|
||||
* The new scale for this matrix.
|
||||
* @return this
|
||||
*/
|
||||
public DTransform setScale(Vector3d scale) {
|
||||
this.scale.set(scale);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this scale to the given value.
|
||||
* @param scale
|
||||
* The new scale for this matrix.
|
||||
* @return this
|
||||
*/
|
||||
public DTransform setScale(float scale) {
|
||||
this.scale.set(scale, scale, scale);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the translation vector in this matrix.
|
||||
* @return translation vector.
|
||||
*/
|
||||
public Vector3d getTranslation() {
|
||||
return translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rotation quaternion in this matrix.
|
||||
* @return rotation quaternion.
|
||||
*/
|
||||
public DQuaternion getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scale vector in this matrix.
|
||||
* @return scale vector.
|
||||
*/
|
||||
public Vector3d getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n" + "[ " + rotation.x + ", " + rotation.y + ", " + rotation.z + ", " + rotation.w + "]\n" + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]";
|
||||
}
|
||||
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
OutputCapsule capsule = e.getCapsule(this);
|
||||
capsule.write(rotation, "rot", new DQuaternion());
|
||||
capsule.write(translation, "translation", Vector3d.ZERO);
|
||||
capsule.write(scale, "scale", Vector3d.UNIT_XYZ);
|
||||
}
|
||||
|
||||
public void read(JmeImporter e) throws IOException {
|
||||
InputCapsule capsule = e.getCapsule(this);
|
||||
|
||||
rotation = (DQuaternion) capsule.readSavable("rot", new DQuaternion());
|
||||
translation = (Vector3d) capsule.readSavable("translation", Vector3d.ZERO);
|
||||
scale = (Vector3d) capsule.readSavable("scale", Vector3d.UNIT_XYZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DTransform clone() {
|
||||
try {
|
||||
DTransform tq = (DTransform) super.clone();
|
||||
tq.rotation = rotation.clone();
|
||||
tq.scale = scale.clone();
|
||||
tq.translation = translation.clone();
|
||||
return tq;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.math;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
import org.ejml.ops.CommonOps;
|
||||
import org.ejml.simple.SimpleMatrix;
|
||||
import org.ejml.simple.SimpleSVD;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
|
||||
/**
|
||||
* Encapsulates a 4x4 matrix
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class Matrix extends SimpleMatrix {
|
||||
private static final long serialVersionUID = 2396600537315902559L;
|
||||
|
||||
public Matrix(int rows, int cols) {
|
||||
super(rows, cols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor
|
||||
*/
|
||||
public Matrix(SimpleMatrix m) {
|
||||
super(m);
|
||||
}
|
||||
|
||||
public Matrix(double[][] data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
public static Matrix identity(int size) {
|
||||
Matrix result = new Matrix(size, size);
|
||||
CommonOps.setIdentity(result.mat);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Matrix pseudoinverse() {
|
||||
return this.pseudoinverse(1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Matrix pseudoinverse(double lambda) {
|
||||
SimpleSVD<SimpleMatrix> simpleSVD = this.svd();
|
||||
|
||||
SimpleMatrix U = simpleSVD.getU();
|
||||
SimpleMatrix S = simpleSVD.getW();
|
||||
SimpleMatrix V = simpleSVD.getV();
|
||||
|
||||
int N = Math.min(this.numRows(),this.numCols());
|
||||
double maxSingular = 0;
|
||||
for( int i = 0; i < N; ++i ) {
|
||||
if( S.get(i, i) > maxSingular ) {
|
||||
maxSingular = S.get(i, i);
|
||||
}
|
||||
}
|
||||
|
||||
double tolerance = FastMath.DBL_EPSILON * Math.max(this.numRows(),this.numCols()) * maxSingular;
|
||||
for(int i=0;i<Math.min(S.numRows(), S.numCols());++i) {
|
||||
double a = S.get(i, i);
|
||||
if(a <= tolerance) {
|
||||
a = 0;
|
||||
} else {
|
||||
a = a/(a * a + lambda * lambda);
|
||||
}
|
||||
S.set(i, i, a);
|
||||
}
|
||||
return new Matrix(V.mult(S.transpose()).mult(U.transpose()));
|
||||
}
|
||||
|
||||
public void setColumn(Vector3d col, int column) {
|
||||
this.setColumn(column, 0, col.x, col.y, col.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just for some debug informations in order to compare the results with the scilab computation program.
|
||||
* @param name the name of the matrix
|
||||
* @param m the matrix to print out
|
||||
* @return the String format of the matrix to easily input it to Scilab
|
||||
*/
|
||||
public String toScilabString(String name, SimpleMatrix m) {
|
||||
String result = name + " = [";
|
||||
|
||||
for(int i=0;i<m.numRows();++i) {
|
||||
for(int j=0;j<m.numCols();++j) {
|
||||
result += m.get(i, j) + " ";
|
||||
}
|
||||
result += ";";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a String representation of the matrix
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
DecimalFormat df = new DecimalFormat("#.0000");
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int r = 0; r < this.numRows(); ++r) {
|
||||
buf.append("\n| ");
|
||||
for (int c = 0; c < this.numCols(); ++c) {
|
||||
buf.append(df.format(this.get(r, c))).append(' ');
|
||||
}
|
||||
buf.append('|');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void setTranslation(Vector3d translation) {
|
||||
this.setColumn(translation, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scale.
|
||||
*
|
||||
* @param scale
|
||||
* the scale vector to set
|
||||
*/
|
||||
public void setScale(Vector3d scale) {
|
||||
this.setScale(scale.x, scale.y, scale.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scale.
|
||||
*
|
||||
* @param x
|
||||
* the X scale
|
||||
* @param y
|
||||
* the Y scale
|
||||
* @param z
|
||||
* the Z scale
|
||||
*/
|
||||
public void setScale(double x, double y, double z) {
|
||||
Vector3d vect1 = new Vector3d(this.get(0, 0), this.get(1, 0), this.get(2, 0));
|
||||
vect1.normalizeLocal().multLocal(x);
|
||||
this.set(0, 0, vect1.x);
|
||||
this.set(1, 0, vect1.y);
|
||||
this.set(2, 0, vect1.z);
|
||||
|
||||
vect1.set(this.get(0, 1), this.get(1, 1), this.get(2, 1));
|
||||
vect1.normalizeLocal().multLocal(y);
|
||||
this.set(0, 1, vect1.x);
|
||||
this.set(1, 1, vect1.y);
|
||||
this.set(2, 1, vect1.z);
|
||||
|
||||
vect1.set(this.get(0, 2), this.get(1, 2), this.get(2, 2));
|
||||
vect1.normalizeLocal().multLocal(z);
|
||||
this.set(0, 2, vect1.x);
|
||||
this.set(1, 2, vect1.y);
|
||||
this.set(2, 2, vect1.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>setRotationQuaternion</code> builds a rotation from a
|
||||
* <code>Quaternion</code>.
|
||||
*
|
||||
* @param quat
|
||||
* the quaternion to build the rotation from.
|
||||
* @throws NullPointerException
|
||||
* if quat is null.
|
||||
*/
|
||||
public void setRotationQuaternion(DQuaternion quat) {
|
||||
quat.toRotationMatrix(this);
|
||||
}
|
||||
|
||||
public DTransform toTransform() {
|
||||
DTransform result = new DTransform();
|
||||
result.setTranslation(this.toTranslationVector());
|
||||
result.setRotation(this.toRotationQuat());
|
||||
result.setScale(this.toScaleVector());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Vector3d toTranslationVector() {
|
||||
return new Vector3d(this.get(0, 3), this.get(1, 3), this.get(2, 3));
|
||||
}
|
||||
|
||||
public DQuaternion toRotationQuat() {
|
||||
DQuaternion quat = new DQuaternion();
|
||||
quat.fromRotationMatrix(this.get(0, 0), this.get(0, 1), this.get(0, 2), this.get(1, 0), this.get(1, 1), this.get(1, 2), this.get(2, 0), this.get(2, 1), this.get(2, 2));
|
||||
return quat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the scale vector from the matrix and stores it into a given
|
||||
* vector.
|
||||
*/
|
||||
public Vector3d toScaleVector() {
|
||||
Vector3d result = new Vector3d();
|
||||
this.toScaleVector(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the scale vector from the matrix and stores it into a given
|
||||
* vector.
|
||||
*
|
||||
* @param vector the vector where the scale will be stored
|
||||
*/
|
||||
public void toScaleVector(Vector3d vector) {
|
||||
double scaleX = Math.sqrt(this.get(0, 0) * this.get(0, 0) + this.get(1, 0) * this.get(1, 0) + this.get(2, 0) * this.get(2, 0));
|
||||
double scaleY = Math.sqrt(this.get(0, 1) * this.get(0, 1) + this.get(1, 1) * this.get(1, 1) + this.get(2, 1) * this.get(2, 1));
|
||||
double scaleZ = Math.sqrt(this.get(0, 2) * this.get(0, 2) + this.get(1, 2) * this.get(1, 2) + this.get(2, 2) * this.get(2, 2));
|
||||
vector.set(scaleX, scaleY, scaleZ);
|
||||
}
|
||||
}
|
@ -1,867 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jme3.scene.plugins.blender.math;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.export.InputCapsule;
|
||||
import com.jme3.export.JmeExporter;
|
||||
import com.jme3.export.JmeImporter;
|
||||
import com.jme3.export.OutputCapsule;
|
||||
import com.jme3.export.Savable;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
/*
|
||||
* -- Added *Local methods to cut down on object creation - JS
|
||||
*/
|
||||
|
||||
/**
|
||||
* <code>Vector3d</code> defines a Vector for a three float value tuple. <code>Vector3d</code> can represent any three dimensional value, such as a
|
||||
* vertex, a normal, etc. Utility methods are also included to aid in
|
||||
* mathematical calculations.
|
||||
*
|
||||
* This class's only purpose is to give better accuracy in floating point operations during computations.
|
||||
* This is made by copying the original Vector3f class from jme3 core and leaving only required methods and basic computation methods, so that
|
||||
* the class is smaller and easier to maintain.
|
||||
* Should any other methods be needed, they will be added.
|
||||
*
|
||||
* @author Mark Powell
|
||||
* @author Joshua Slack
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public final class Vector3d implements Savable, Cloneable, Serializable {
|
||||
private static final long serialVersionUID = 3090477054277293078L;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(Vector3d.class.getName());
|
||||
|
||||
public final static Vector3d ZERO = new Vector3d();
|
||||
public final static Vector3d UNIT_XYZ = new Vector3d(1, 1, 1);
|
||||
public final static Vector3d UNIT_X = new Vector3d(1, 0, 0);
|
||||
public final static Vector3d UNIT_Y = new Vector3d(0, 1, 0);
|
||||
public final static Vector3d UNIT_Z = new Vector3d(0, 0, 1);
|
||||
|
||||
/**
|
||||
* the x value of the vector.
|
||||
*/
|
||||
public double x;
|
||||
|
||||
/**
|
||||
* the y value of the vector.
|
||||
*/
|
||||
public double y;
|
||||
|
||||
/**
|
||||
* the z value of the vector.
|
||||
*/
|
||||
public double z;
|
||||
|
||||
/**
|
||||
* Constructor instantiates a new <code>Vector3d</code> with default
|
||||
* values of (0,0,0).
|
||||
*
|
||||
*/
|
||||
public Vector3d() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor instantiates a new <code>Vector3d</code> with provides
|
||||
* values.
|
||||
*
|
||||
* @param x
|
||||
* the x value of the vector.
|
||||
* @param y
|
||||
* the y value of the vector.
|
||||
* @param z
|
||||
* the z value of the vector.
|
||||
*/
|
||||
public Vector3d(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor instantiates a new <code>Vector3d</code> that is a copy
|
||||
* of the provided vector
|
||||
* @param vector3f
|
||||
* The Vector3f to copy
|
||||
*/
|
||||
public Vector3d(Vector3f vector3f) {
|
||||
this(vector3f.x, vector3f.y, vector3f.z);
|
||||
}
|
||||
|
||||
public Vector3f toVector3f() {
|
||||
return new Vector3f((float) x, (float) y, (float) z);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>set</code> sets the x,y,z values of the vector based on passed
|
||||
* parameters.
|
||||
*
|
||||
* @param x
|
||||
* the x value of the vector.
|
||||
* @param y
|
||||
* the y value of the vector.
|
||||
* @param z
|
||||
* the z value of the vector.
|
||||
* @return this vector
|
||||
*/
|
||||
public Vector3d set(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>set</code> sets the x,y,z values of the vector by copying the
|
||||
* supplied vector.
|
||||
*
|
||||
* @param vect
|
||||
* the vector to copy.
|
||||
* @return this vector
|
||||
*/
|
||||
public Vector3d set(Vector3d vect) {
|
||||
return this.set(vect.x, vect.y, vect.z);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>add</code> adds a provided vector to this vector creating a
|
||||
* resultant vector which is returned. If the provided vector is null, null
|
||||
* is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to add to this.
|
||||
* @return the resultant vector.
|
||||
*/
|
||||
public Vector3d add(Vector3d vec) {
|
||||
if (null == vec) {
|
||||
LOGGER.warning("Provided vector is null, null returned.");
|
||||
return null;
|
||||
}
|
||||
return new Vector3d(x + vec.x, y + vec.y, z + vec.z);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>add</code> adds the values of a provided vector storing the
|
||||
* values in the supplied vector.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to add to this
|
||||
* @param result
|
||||
* the vector to store the result in
|
||||
* @return result returns the supplied result vector.
|
||||
*/
|
||||
public Vector3d add(Vector3d vec, Vector3d result) {
|
||||
result.x = x + vec.x;
|
||||
result.y = y + vec.y;
|
||||
result.z = z + vec.z;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>addLocal</code> adds a provided vector to this vector internally,
|
||||
* and returns a handle to this vector for easy chaining of calls. If the
|
||||
* provided vector is null, null is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to add to this vector.
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d addLocal(Vector3d vec) {
|
||||
if (null == vec) {
|
||||
LOGGER.warning("Provided vector is null, null returned.");
|
||||
return null;
|
||||
}
|
||||
x += vec.x;
|
||||
y += vec.y;
|
||||
z += vec.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>add</code> adds the provided values to this vector, creating a
|
||||
* new vector that is then returned.
|
||||
*
|
||||
* @param addX
|
||||
* the x value to add.
|
||||
* @param addY
|
||||
* the y value to add.
|
||||
* @param addZ
|
||||
* the z value to add.
|
||||
* @return the result vector.
|
||||
*/
|
||||
public Vector3d add(double addX, double addY, double addZ) {
|
||||
return new Vector3d(x + addX, y + addY, z + addZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>addLocal</code> adds the provided values to this vector
|
||||
* internally, and returns a handle to this vector for easy chaining of
|
||||
* calls.
|
||||
*
|
||||
* @param addX
|
||||
* value to add to x
|
||||
* @param addY
|
||||
* value to add to y
|
||||
* @param addZ
|
||||
* value to add to z
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d addLocal(double addX, double addY, double addZ) {
|
||||
x += addX;
|
||||
y += addY;
|
||||
z += addZ;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>scaleAdd</code> multiplies this vector by a scalar then adds the
|
||||
* given Vector3d.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to multiply this vector by.
|
||||
* @param add
|
||||
* the value to add
|
||||
*/
|
||||
public Vector3d scaleAdd(double scalar, Vector3d add) {
|
||||
x = x * scalar + add.x;
|
||||
y = y * scalar + add.y;
|
||||
z = z * scalar + add.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>scaleAdd</code> multiplies the given vector by a scalar then adds
|
||||
* the given vector.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to multiply this vector by.
|
||||
* @param mult
|
||||
* the value to multiply the scalar by
|
||||
* @param add
|
||||
* the value to add
|
||||
*/
|
||||
public Vector3d scaleAdd(double scalar, Vector3d mult, Vector3d add) {
|
||||
x = mult.x * scalar + add.x;
|
||||
y = mult.y * scalar + add.y;
|
||||
z = mult.z * scalar + add.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>dot</code> calculates the dot product of this vector with a
|
||||
* provided vector. If the provided vector is null, 0 is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to dot with this vector.
|
||||
* @return the resultant dot product of this vector and a given vector.
|
||||
*/
|
||||
public double dot(Vector3d vec) {
|
||||
if (null == vec) {
|
||||
LOGGER.warning("Provided vector is null, 0 returned.");
|
||||
return 0;
|
||||
}
|
||||
return x * vec.x + y * vec.y + z * vec.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>cross</code> calculates the cross product of this vector with a
|
||||
* parameter vector v.
|
||||
*
|
||||
* @param v
|
||||
* the vector to take the cross product of with this.
|
||||
* @return the cross product vector.
|
||||
*/
|
||||
public Vector3d cross(Vector3d v) {
|
||||
return this.cross(v, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>cross</code> calculates the cross product of this vector with a
|
||||
* parameter vector v. The result is stored in <code>result</code>
|
||||
*
|
||||
* @param v
|
||||
* the vector to take the cross product of with this.
|
||||
* @param result
|
||||
* the vector to store the cross product result.
|
||||
* @return result, after receiving the cross product vector.
|
||||
*/
|
||||
public Vector3d cross(Vector3d v, Vector3d result) {
|
||||
return this.cross(v.x, v.y, v.z, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>cross</code> calculates the cross product of this vector with a
|
||||
* parameter vector v. The result is stored in <code>result</code>
|
||||
*
|
||||
* @param otherX
|
||||
* x component of the vector to take the cross product of with this.
|
||||
* @param otherY
|
||||
* y component of the vector to take the cross product of with this.
|
||||
* @param otherZ
|
||||
* z component of the vector to take the cross product of with this.
|
||||
* @param result
|
||||
* the vector to store the cross product result.
|
||||
* @return result, after receiving the cross product vector.
|
||||
*/
|
||||
public Vector3d cross(double otherX, double otherY, double otherZ, Vector3d result) {
|
||||
if (result == null) {
|
||||
result = new Vector3d();
|
||||
}
|
||||
double resX = y * otherZ - z * otherY;
|
||||
double resY = z * otherX - x * otherZ;
|
||||
double resZ = x * otherY - y * otherX;
|
||||
result.set(resX, resY, resZ);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>crossLocal</code> calculates the cross product of this vector
|
||||
* with a parameter vector v.
|
||||
*
|
||||
* @param v
|
||||
* the vector to take the cross product of with this.
|
||||
* @return this.
|
||||
*/
|
||||
public Vector3d crossLocal(Vector3d v) {
|
||||
return this.crossLocal(v.x, v.y, v.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>crossLocal</code> calculates the cross product of this vector
|
||||
* with a parameter vector v.
|
||||
*
|
||||
* @param otherX
|
||||
* x component of the vector to take the cross product of with this.
|
||||
* @param otherY
|
||||
* y component of the vector to take the cross product of with this.
|
||||
* @param otherZ
|
||||
* z component of the vector to take the cross product of with this.
|
||||
* @return this.
|
||||
*/
|
||||
public Vector3d crossLocal(double otherX, double otherY, double otherZ) {
|
||||
double tempx = y * otherZ - z * otherY;
|
||||
double tempy = z * otherX - x * otherZ;
|
||||
z = x * otherY - y * otherX;
|
||||
x = tempx;
|
||||
y = tempy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>length</code> calculates the magnitude of this vector.
|
||||
*
|
||||
* @return the length or magnitude of the vector.
|
||||
*/
|
||||
public double length() {
|
||||
return Math.sqrt(this.lengthSquared());
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>lengthSquared</code> calculates the squared value of the
|
||||
* magnitude of the vector.
|
||||
*
|
||||
* @return the magnitude squared of the vector.
|
||||
*/
|
||||
public double lengthSquared() {
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>distanceSquared</code> calculates the distance squared between
|
||||
* this vector and vector v.
|
||||
*
|
||||
* @param v
|
||||
* the second vector to determine the distance squared.
|
||||
* @return the distance squared between the two vectors.
|
||||
*/
|
||||
public double distanceSquared(Vector3d v) {
|
||||
double dx = x - v.x;
|
||||
double dy = y - v.y;
|
||||
double dz = z - v.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>distance</code> calculates the distance between this vector and
|
||||
* vector v.
|
||||
*
|
||||
* @param v
|
||||
* the second vector to determine the distance.
|
||||
* @return the distance between the two vectors.
|
||||
*/
|
||||
public double distance(Vector3d v) {
|
||||
return Math.sqrt(this.distanceSquared(v));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>mult</code> multiplies this vector by a scalar. The resultant
|
||||
* vector is returned.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to multiply this vector by.
|
||||
* @return the new vector.
|
||||
*/
|
||||
public Vector3d mult(double scalar) {
|
||||
return new Vector3d(x * scalar, y * scalar, z * scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>mult</code> multiplies this vector by a scalar. The resultant
|
||||
* vector is supplied as the second parameter and returned.
|
||||
*
|
||||
* @param scalar
|
||||
* the scalar to multiply this vector by.
|
||||
* @param product
|
||||
* the product to store the result in.
|
||||
* @return product
|
||||
*/
|
||||
public Vector3d mult(double scalar, Vector3d product) {
|
||||
if (null == product) {
|
||||
product = new Vector3d();
|
||||
}
|
||||
|
||||
product.x = x * scalar;
|
||||
product.y = y * scalar;
|
||||
product.z = z * scalar;
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>multLocal</code> multiplies this vector by a scalar internally,
|
||||
* and returns a handle to this vector for easy chaining of calls.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to multiply this vector by.
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d multLocal(double scalar) {
|
||||
x *= scalar;
|
||||
y *= scalar;
|
||||
z *= scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>multLocal</code> multiplies a provided vector by this vector
|
||||
* internally, and returns a handle to this vector for easy chaining of
|
||||
* calls. If the provided vector is null, null is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to multiply by this vector.
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d multLocal(Vector3d vec) {
|
||||
if (null == vec) {
|
||||
LOGGER.warning("Provided vector is null, null returned.");
|
||||
return null;
|
||||
}
|
||||
x *= vec.x;
|
||||
y *= vec.y;
|
||||
z *= vec.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>multLocal</code> multiplies this vector by 3 scalars
|
||||
* internally, and returns a handle to this vector for easy chaining of
|
||||
* calls.
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d multLocal(double x, double y, double z) {
|
||||
this.x *= x;
|
||||
this.y *= y;
|
||||
this.z *= z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>multLocal</code> multiplies a provided vector by this vector
|
||||
* internally, and returns a handle to this vector for easy chaining of
|
||||
* calls. If the provided vector is null, null is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to mult to this vector.
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d mult(Vector3d vec) {
|
||||
if (null == vec) {
|
||||
LOGGER.warning("Provided vector is null, null returned.");
|
||||
return null;
|
||||
}
|
||||
return this.mult(vec, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>multLocal</code> multiplies a provided vector by this vector
|
||||
* internally, and returns a handle to this vector for easy chaining of
|
||||
* calls. If the provided vector is null, null is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to mult to this vector.
|
||||
* @param store
|
||||
* result vector (null to create a new vector)
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d mult(Vector3d vec, Vector3d store) {
|
||||
if (null == vec) {
|
||||
LOGGER.warning("Provided vector is null, null returned.");
|
||||
return null;
|
||||
}
|
||||
if (store == null) {
|
||||
store = new Vector3d();
|
||||
}
|
||||
return store.set(x * vec.x, y * vec.y, z * vec.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>divide</code> divides the values of this vector by a scalar and
|
||||
* returns the result. The values of this vector remain untouched.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to divide this vectors attributes by.
|
||||
* @return the result <code>Vector</code>.
|
||||
*/
|
||||
public Vector3d divide(double scalar) {
|
||||
scalar = 1f / scalar;
|
||||
return new Vector3d(x * scalar, y * scalar, z * scalar);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>divideLocal</code> divides this vector by a scalar internally,
|
||||
* and returns a handle to this vector for easy chaining of calls. Dividing
|
||||
* by zero will result in an exception.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to divides this vector by.
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d divideLocal(double scalar) {
|
||||
scalar = 1f / scalar;
|
||||
x *= scalar;
|
||||
y *= scalar;
|
||||
z *= scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>divide</code> divides the values of this vector by a scalar and
|
||||
* returns the result. The values of this vector remain untouched.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to divide this vectors attributes by.
|
||||
* @return the result <code>Vector</code>.
|
||||
*/
|
||||
public Vector3d divide(Vector3d scalar) {
|
||||
return new Vector3d(x / scalar.x, y / scalar.y, z / scalar.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>divideLocal</code> divides this vector by a scalar internally,
|
||||
* and returns a handle to this vector for easy chaining of calls. Dividing
|
||||
* by zero will result in an exception.
|
||||
*
|
||||
* @param scalar
|
||||
* the value to divides this vector by.
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d divideLocal(Vector3d scalar) {
|
||||
x /= scalar.x;
|
||||
y /= scalar.y;
|
||||
z /= scalar.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>negate</code> returns the negative of this vector. All values are
|
||||
* negated and set to a new vector.
|
||||
*
|
||||
* @return the negated vector.
|
||||
*/
|
||||
public Vector3d negate() {
|
||||
return new Vector3d(-x, -y, -z);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>negateLocal</code> negates the internal values of this vector.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public Vector3d negateLocal() {
|
||||
x = -x;
|
||||
y = -y;
|
||||
z = -z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>subtract</code> subtracts the values of a given vector from those
|
||||
* of this vector creating a new vector object. If the provided vector is
|
||||
* null, null is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to subtract from this vector.
|
||||
* @return the result vector.
|
||||
*/
|
||||
public Vector3d subtract(Vector3d vec) {
|
||||
return new Vector3d(x - vec.x, y - vec.y, z - vec.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>subtractLocal</code> subtracts a provided vector from this vector
|
||||
* internally, and returns a handle to this vector for easy chaining of
|
||||
* calls. If the provided vector is null, null is returned.
|
||||
*
|
||||
* @param vec
|
||||
* the vector to subtract
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d subtractLocal(Vector3d vec) {
|
||||
if (null == vec) {
|
||||
LOGGER.warning("Provided vector is null, null returned.");
|
||||
return null;
|
||||
}
|
||||
x -= vec.x;
|
||||
y -= vec.y;
|
||||
z -= vec.z;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>subtract</code>
|
||||
*
|
||||
* @param vec
|
||||
* the vector to subtract from this
|
||||
* @param result
|
||||
* the vector to store the result in
|
||||
* @return result
|
||||
*/
|
||||
public Vector3d subtract(Vector3d vec, Vector3d result) {
|
||||
if (result == null) {
|
||||
result = new Vector3d();
|
||||
}
|
||||
result.x = x - vec.x;
|
||||
result.y = y - vec.y;
|
||||
result.z = z - vec.z;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>subtract</code> subtracts the provided values from this vector,
|
||||
* creating a new vector that is then returned.
|
||||
*
|
||||
* @param subtractX
|
||||
* the x value to subtract.
|
||||
* @param subtractY
|
||||
* the y value to subtract.
|
||||
* @param subtractZ
|
||||
* the z value to subtract.
|
||||
* @return the result vector.
|
||||
*/
|
||||
public Vector3d subtract(double subtractX, double subtractY, double subtractZ) {
|
||||
return new Vector3d(x - subtractX, y - subtractY, z - subtractZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>subtractLocal</code> subtracts the provided values from this vector
|
||||
* internally, and returns a handle to this vector for easy chaining of
|
||||
* calls.
|
||||
*
|
||||
* @param subtractX
|
||||
* the x value to subtract.
|
||||
* @param subtractY
|
||||
* the y value to subtract.
|
||||
* @param subtractZ
|
||||
* the z value to subtract.
|
||||
* @return this
|
||||
*/
|
||||
public Vector3d subtractLocal(double subtractX, double subtractY, double subtractZ) {
|
||||
x -= subtractX;
|
||||
y -= subtractY;
|
||||
z -= subtractZ;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>normalize</code> returns the unit vector of this vector.
|
||||
*
|
||||
* @return unit vector of this vector.
|
||||
*/
|
||||
public Vector3d normalize() {
|
||||
double length = x * x + y * y + z * z;
|
||||
if (length != 1f && length != 0f) {
|
||||
length = 1.0f / Math.sqrt(length);
|
||||
return new Vector3d(x * length, y * length, z * length);
|
||||
}
|
||||
return this.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>normalizeLocal</code> makes this vector into a unit vector of
|
||||
* itself.
|
||||
*
|
||||
* @return this.
|
||||
*/
|
||||
public Vector3d normalizeLocal() {
|
||||
// NOTE: this implementation is more optimized
|
||||
// than the old jme normalize as this method
|
||||
// is commonly used.
|
||||
double length = x * x + y * y + z * z;
|
||||
if (length != 1f && length != 0f) {
|
||||
length = 1.0f / Math.sqrt(length);
|
||||
x *= length;
|
||||
y *= length;
|
||||
z *= length;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>angleBetween</code> returns (in radians) the angle between two vectors.
|
||||
* It is assumed that both this vector and the given vector are unit vectors (iow, normalized).
|
||||
*
|
||||
* @param otherVector
|
||||
* a unit vector to find the angle against
|
||||
* @return the angle in radians.
|
||||
*/
|
||||
public double angleBetween(Vector3d otherVector) {
|
||||
double dot = this.dot(otherVector);
|
||||
// the vectors are normalized, but if they are parallel then the dot product migh get a value like: 1.000000000000000002
|
||||
// which is caused by floating point operations; in such case, the acos function will return NaN so we need to clamp this value
|
||||
dot = FastMath.clamp((float) dot, -1, 1);
|
||||
return Math.acos(dot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3d clone() {
|
||||
try {
|
||||
return (Vector3d) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(); // can not happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* are these two vectors the same? they are is they both have the same x,y,
|
||||
* and z values.
|
||||
*
|
||||
* @param o
|
||||
* the object to compare for equality
|
||||
* @return true if they are equal
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Vector3d)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector3d comp = (Vector3d) o;
|
||||
if (Double.compare(x, comp.x) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (Double.compare(y, comp.y) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (Double.compare(z, comp.z) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>hashCode</code> returns a unique code for this vector object based
|
||||
* on its values. If two vectors are logically equivalent, they will return
|
||||
* the same hash code value.
|
||||
* @return the hash code value of this vector.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
long hash = 37;
|
||||
hash += 37 * hash + Double.doubleToLongBits(x);
|
||||
hash += 37 * hash + Double.doubleToLongBits(y);
|
||||
hash += 37 * hash + Double.doubleToLongBits(z);
|
||||
return (int) hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>toString</code> returns the string representation of this vector.
|
||||
* The format is:
|
||||
*
|
||||
* org.jme.math.Vector3d [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ]
|
||||
*
|
||||
* @return the string representation of this vector.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(" + x + ", " + y + ", " + z + ")";
|
||||
}
|
||||
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
OutputCapsule capsule = e.getCapsule(this);
|
||||
capsule.write(x, "x", 0);
|
||||
capsule.write(y, "y", 0);
|
||||
capsule.write(z, "z", 0);
|
||||
}
|
||||
|
||||
public void read(JmeImporter e) throws IOException {
|
||||
InputCapsule capsule = e.getCapsule(this);
|
||||
x = capsule.readDouble("x", 0);
|
||||
y = capsule.readDouble("y", 0);
|
||||
z = capsule.readDouble("z", 0);
|
||||
}
|
||||
}
|
@ -1,349 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.meshes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.math.Vector3d;
|
||||
import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
|
||||
|
||||
/**
|
||||
* A class that represents a single edge between two vertices.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class Edge {
|
||||
private static final Logger LOGGER = Logger.getLogger(Edge.class.getName());
|
||||
|
||||
private static final int FLAG_EDGE_NOT_IN_FACE = 0x80;
|
||||
|
||||
/** The vertices indexes. */
|
||||
private int index1, index2;
|
||||
/** The vertices that can be set if we need and abstract edge outside the mesh (for computations). */
|
||||
private Vector3f v1, v2;
|
||||
/** The weight of the edge. */
|
||||
private float crease;
|
||||
/** A variable that indicates if this edge belongs to any face or not. */
|
||||
private boolean inFace;
|
||||
/** The mesh that owns the edge. */
|
||||
private TemporalMesh temporalMesh;
|
||||
|
||||
public Edge(Vector3f v1, Vector3f v2) {
|
||||
this.v1 = v1 == null ? new Vector3f() : v1;
|
||||
this.v2 = v2 == null ? new Vector3f() : v2;
|
||||
index1 = 0;
|
||||
index2 = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor only stores the indexes of the vertices. The position vertices should be stored
|
||||
* outside this class.
|
||||
* @param index1
|
||||
* the first index of the edge
|
||||
* @param index2
|
||||
* the second index of the edge
|
||||
* @param crease
|
||||
* the weight of the face
|
||||
* @param inFace
|
||||
* a variable that indicates if this edge belongs to any face or not
|
||||
*/
|
||||
public Edge(int index1, int index2, float crease, boolean inFace, TemporalMesh temporalMesh) {
|
||||
this.index1 = index1;
|
||||
this.index2 = index2;
|
||||
this.crease = crease;
|
||||
this.inFace = inFace;
|
||||
this.temporalMesh = temporalMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Edge clone() {
|
||||
return new Edge(index1, index2, crease, inFace, temporalMesh);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first index of the edge
|
||||
*/
|
||||
public int getFirstIndex() {
|
||||
return index1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the second index of the edge
|
||||
*/
|
||||
public int getSecondIndex() {
|
||||
return index2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first vertex of the edge
|
||||
*/
|
||||
public Vector3f getFirstVertex() {
|
||||
return temporalMesh == null ? v1 : temporalMesh.getVertices().get(index1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the second vertex of the edge
|
||||
*/
|
||||
public Vector3f getSecondVertex() {
|
||||
return temporalMesh == null ? v2 : temporalMesh.getVertices().get(index2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index other than the given.
|
||||
* @param index
|
||||
* index of the edge
|
||||
* @return the remaining index number
|
||||
*/
|
||||
public int getOtherIndex(int index) {
|
||||
if (index == index1) {
|
||||
return index2;
|
||||
}
|
||||
if (index == index2) {
|
||||
return index1;
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot give the other index for [" + index + "] because this index does not exist in edge: " + this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the crease value of the edge (its weight)
|
||||
*/
|
||||
public float getCrease() {
|
||||
return crease;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if the edge is used by at least one face and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isInFace() {
|
||||
return inFace;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of the edge
|
||||
*/
|
||||
public float getLength() {
|
||||
return this.getFirstVertex().distance(this.getSecondVertex());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mesh this edge belongs to
|
||||
*/
|
||||
public TemporalMesh getTemporalMesh() {
|
||||
return temporalMesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the centroid of the edge
|
||||
*/
|
||||
public Vector3f computeCentroid() {
|
||||
return this.getFirstVertex().add(this.getSecondVertex()).divideLocal(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shifts indexes by a given amount.
|
||||
* @param shift
|
||||
* how much the indexes should be shifted
|
||||
* @param predicate
|
||||
* the predicate that verifies which indexes should be shifted; if null then all will be shifted
|
||||
*/
|
||||
public void shiftIndexes(int shift, IndexPredicate predicate) {
|
||||
if (predicate == null) {
|
||||
index1 += shift;
|
||||
index2 += shift;
|
||||
} else {
|
||||
index1 += predicate.execute(index1) ? shift : 0;
|
||||
index2 += predicate.execute(index2) ? shift : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips the order of the indexes.
|
||||
*/
|
||||
public void flipIndexes() {
|
||||
int temp = index1;
|
||||
index1 = index2;
|
||||
index2 = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The crossing method first computes the points on both lines (that contain the edges)
|
||||
* who are closest in distance. If the distance between points is smaller than FastMath.FLT_EPSILON
|
||||
* the we consider them to be the same point (the lines cross).
|
||||
* The second step is to check if both points are contained within the edges.
|
||||
*
|
||||
* The method of computing the crossing point is as follows:
|
||||
* Let's assume that:
|
||||
* (P0, P1) are the points of the first edge
|
||||
* (Q0, Q1) are the points of the second edge
|
||||
*
|
||||
* u = P1 - P0
|
||||
* v = Q1 - Q0
|
||||
*
|
||||
* This gives us the equations of two lines:
|
||||
* L1: (x = P1x + ux*t1; y = P1y + uy*t1; z = P1z + uz*t1)
|
||||
* L2: (x = P2x + vx*t2; y = P2y + vy*t2; z = P2z + vz*t2)
|
||||
*
|
||||
* Comparing the x and y of the first two equations for each line will allow us to compute t1 and t2
|
||||
* (which is implemented below).
|
||||
* Using t1 and t2 we can compute (x, y, z) of each line and that will give us two points that we need to compare.
|
||||
*
|
||||
* @param edge
|
||||
* the edge we check against crossing
|
||||
* @return <b>true</b> if the edges cross and false otherwise
|
||||
*/
|
||||
public boolean cross(Edge edge) {
|
||||
return this.getCrossPoint(edge) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method computes the crossing pint of this edge and another edge. If
|
||||
* there is no crossing then null is returned.
|
||||
*
|
||||
* @param edge
|
||||
* the edge to compute corss point with
|
||||
* @return cross point on null if none exist
|
||||
*/
|
||||
public Vector3f getCrossPoint(Edge edge) {
|
||||
return this.getCrossPoint(edge, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method computes the crossing pint of this edge and another edge. If
|
||||
* there is no crossing then null is returned. Also null is returned if the edges are parallel.
|
||||
* This method also allows to get the crossing point of the straight lines that contain these edges if
|
||||
* you set the 'extend' parameter to true.
|
||||
*
|
||||
* @param edge
|
||||
* the edge to compute corss point with
|
||||
* @param extendThisEdge
|
||||
* set to <b>true</b> to find a crossing point along the whole
|
||||
* straight that contains the current edge
|
||||
* @param extendSecondEdge
|
||||
* set to <b>true</b> to find a crossing point along the whole
|
||||
* straight that contains the given edge
|
||||
* @return cross point on null if none exist or the edges are parallel
|
||||
*/
|
||||
public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) {
|
||||
Vector3d P1 = new Vector3d(this.getFirstVertex());
|
||||
Vector3d P2 = new Vector3d(edge.getFirstVertex());
|
||||
Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal();
|
||||
Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal();
|
||||
|
||||
if(Math.abs(u.dot(v)) >= 1 - FastMath.DBL_EPSILON) {
|
||||
// the edges are parallel; do not care about the crossing point
|
||||
return null;
|
||||
}
|
||||
|
||||
double t1 = 0, t2 = 0;
|
||||
if(u.x == 0 && v.x == 0) {
|
||||
t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y);
|
||||
t1 = (P2.z - P1.z + v.z * t2) / u.z;
|
||||
} else if(u.y == 0 && v.y == 0) {
|
||||
t2 = (u.x * (P2.z - P1.z) - u.z * (P2.x - P1.x)) / (u.z * v.x - u.x * v.z);
|
||||
t1 = (P2.x - P1.x + v.x * t2) / u.x;
|
||||
} else if(u.z == 0 && v.z == 0) {
|
||||
t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y);
|
||||
t1 = (P2.x - P1.x + v.x * t2) / u.x;
|
||||
} else {
|
||||
t2 = (P1.y * u.x - P1.x * u.y + P2.x * u.y - P2.y * u.x) / (v.y * u.x - u.y * v.x);
|
||||
t1 = (P2.x - P1.x + v.x * t2) / u.x;
|
||||
if(Math.abs(P1.z - P2.z + u.z * t1 - v.z * t2) > FastMath.FLT_EPSILON) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Vector3d p1 = P1.add(u.mult(t1));
|
||||
Vector3d p2 = P2.add(v.mult(t2));
|
||||
|
||||
if (p1.distance(p2) <= FastMath.FLT_EPSILON) {
|
||||
if(extendThisEdge && extendSecondEdge) {
|
||||
return p1.toVector3f();
|
||||
}
|
||||
// the lines cross, check if p1 and p2 are within the edges
|
||||
Vector3d p = p1.subtract(P1);
|
||||
double cos = p.dot(u) / p.length();
|
||||
if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - this.getLength() <= FastMath.FLT_EPSILON) {
|
||||
// p1 is inside the first edge, lets check the other edge now
|
||||
p = p2.subtract(P2);
|
||||
cos = p.dot(v) / p.length();
|
||||
if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - edge.getLength() <= FastMath.FLT_EPSILON) {
|
||||
return p1.toVector3f();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String result = "Edge [" + index1 + ", " + index2 + "] {" + crease + "}";
|
||||
result += " (" + this.getFirstVertex() + " -> " + this.getSecondVertex() + ")";
|
||||
if (inFace) {
|
||||
result += "[F]";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// The hash code must be identical for the same two indexes, no matter their order.
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
int lowerIndex = Math.min(index1, index2);
|
||||
int higherIndex = Math.max(index1, index2);
|
||||
result = prime * result + lowerIndex;
|
||||
result = prime * result + higherIndex;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Edge)) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
Edge other = (Edge) obj;
|
||||
return Math.min(index1, index2) == Math.min(other.index1, other.index2) && Math.max(index1, index2) == Math.max(other.index1, other.index2);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method loads all edges from the given mesh structure that does not belong to any face.
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @param temporalMesh
|
||||
* the owner of the edges
|
||||
* @return all edges without faces
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with file reading occur
|
||||
*/
|
||||
public static List<Edge> loadAll(Structure meshStructure, TemporalMesh temporalMesh) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName());
|
||||
List<Edge> result = new ArrayList<Edge>();
|
||||
|
||||
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
|
||||
|
||||
if (pMEdge.isNotNull()) {
|
||||
List<Structure> edges = pMEdge.fetchData();
|
||||
for (Structure edge : edges) {
|
||||
int flag = ((Number) edge.getFieldValue("flag")).intValue();
|
||||
|
||||
int v1 = ((Number) edge.getFieldValue("v1")).intValue();
|
||||
int v2 = ((Number) edge.getFieldValue("v2")).intValue();
|
||||
// I do not know why, but blender stores (possibly only sometimes) crease as negative values and shows positive in the editor
|
||||
float crease = Math.abs(((Number) edge.getFieldValue("crease")).floatValue());
|
||||
boolean edgeInFace = (flag & Edge.FLAG_EDGE_NOT_IN_FACE) == 0;
|
||||
result.add(new Edge(v1, v2, crease, edgeInFace, temporalMesh));
|
||||
}
|
||||
}
|
||||
LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size());
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,613 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.meshes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* A class that represents a single face in the mesh. The face is a polygon. Its minimum count of
|
||||
* vertices is = 3.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class Face implements Comparator<Integer> {
|
||||
private static final Logger LOGGER = Logger.getLogger(Face.class.getName());
|
||||
|
||||
/** The indexes loop of the face. */
|
||||
private IndexesLoop indexes;
|
||||
|
||||
private List<IndexesLoop> triangulatedFaces;
|
||||
/** Indicates if the face is smooth or solid. */
|
||||
private boolean smooth;
|
||||
/** The material index of the face. */
|
||||
private int materialNumber;
|
||||
/** UV coordinate sets attached to the face. The key is the set name and value are the UV coords. */
|
||||
private Map<String, List<Vector2f>> faceUVCoords;
|
||||
/** The vertex colors of the face. */
|
||||
private List<byte[]> vertexColors;
|
||||
/** The temporal mesh the face belongs to. */
|
||||
private TemporalMesh temporalMesh;
|
||||
|
||||
/**
|
||||
* Creates a complete face with all available data.
|
||||
* @param indexes
|
||||
* the indexes of the face (required)
|
||||
* @param smooth
|
||||
* indicates if the face is smooth or solid
|
||||
* @param materialNumber
|
||||
* the material index of the face
|
||||
* @param faceUVCoords
|
||||
* UV coordinate sets of the face (optional)
|
||||
* @param vertexColors
|
||||
* the vertex colors of the face (optional)
|
||||
* @param temporalMesh
|
||||
* the temporal mesh the face belongs to (required)
|
||||
*/
|
||||
public Face(Integer[] indexes, boolean smooth, int materialNumber, Map<String, List<Vector2f>> faceUVCoords, List<byte[]> vertexColors, TemporalMesh temporalMesh) {
|
||||
this.setTemporalMesh(temporalMesh);
|
||||
this.indexes = new IndexesLoop(indexes);
|
||||
this.smooth = smooth;
|
||||
this.materialNumber = materialNumber;
|
||||
this.faceUVCoords = faceUVCoords;
|
||||
this.temporalMesh = temporalMesh;
|
||||
this.vertexColors = vertexColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor. Used by the clone method.
|
||||
*/
|
||||
private Face() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Face clone() {
|
||||
Face result = new Face();
|
||||
result.indexes = indexes.clone();
|
||||
result.smooth = smooth;
|
||||
result.materialNumber = materialNumber;
|
||||
if (faceUVCoords != null) {
|
||||
result.faceUVCoords = new HashMap<String, List<Vector2f>>(faceUVCoords.size());
|
||||
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
|
||||
List<Vector2f> uvs = new ArrayList<Vector2f>(entry.getValue().size());
|
||||
for (Vector2f v : entry.getValue()) {
|
||||
uvs.add(v.clone());
|
||||
}
|
||||
result.faceUVCoords.put(entry.getKey(), uvs);
|
||||
}
|
||||
}
|
||||
if (vertexColors != null) {
|
||||
result.vertexColors = new ArrayList<byte[]>(vertexColors.size());
|
||||
for (byte[] colors : vertexColors) {
|
||||
result.vertexColors.add(colors.clone());
|
||||
}
|
||||
}
|
||||
result.temporalMesh = temporalMesh;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index at the given position in the index loop. If the given position is negative or exceeds
|
||||
* the amount of vertices - it is being looped properly so that it always hits an index.
|
||||
* For example getIndex(-1) will return the index before the 0 - in this case it will be the last one.
|
||||
* @param indexPosition
|
||||
* the index position
|
||||
* @return index value at the given position
|
||||
*/
|
||||
private Integer getIndex(int indexPosition) {
|
||||
if (indexPosition >= indexes.size()) {
|
||||
indexPosition = indexPosition % indexes.size();
|
||||
} else if (indexPosition < 0) {
|
||||
indexPosition = indexes.size() - -indexPosition % indexes.size();
|
||||
}
|
||||
return indexes.get(indexPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mesh this face belongs to
|
||||
*/
|
||||
public TemporalMesh getTemporalMesh() {
|
||||
return temporalMesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the original indexes of the face
|
||||
*/
|
||||
public IndexesLoop getIndexes() {
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the centroid of the face
|
||||
*/
|
||||
public Vector3f computeCentroid() {
|
||||
Vector3f result = new Vector3f();
|
||||
List<Vector3f> vertices = temporalMesh.getVertices();
|
||||
for (Integer index : indexes) {
|
||||
result.addLocal(vertices.get(index));
|
||||
}
|
||||
return result.divideLocal(indexes.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list)
|
||||
*/
|
||||
public List<List<Integer>> getCurrentIndexes() {
|
||||
if (triangulatedFaces == null) {
|
||||
return Arrays.asList(indexes.getAll());
|
||||
}
|
||||
List<List<Integer>> result = new ArrayList<List<Integer>>(triangulatedFaces.size());
|
||||
for (IndexesLoop loop : triangulatedFaces) {
|
||||
result.add(loop.getAll());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method detaches the triangle from the face. This method keeps the indexes loop normalized - every index
|
||||
* has only two neighbours. So if detaching the triangle causes a vertex to have more than two neighbours - it is
|
||||
* also detached and returned as a result.
|
||||
* The result is an empty list if no such situation happens.
|
||||
* @param triangleIndexes
|
||||
* the indexes of a triangle to be detached
|
||||
* @return a list of faces that need to be detached as well in order to keep them normalized
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when vertices of a face create more than one loop; this is found during path finding
|
||||
*/
|
||||
private List<Face> detachTriangle(Integer[] triangleIndexes) throws BlenderFileException {
|
||||
LOGGER.fine("Detaching triangle.");
|
||||
if (triangleIndexes.length != 3) {
|
||||
throw new IllegalArgumentException("Cannot detach triangle with that does not have 3 indexes!");
|
||||
}
|
||||
MeshHelper meshHelper = temporalMesh.getBlenderContext().getHelper(MeshHelper.class);
|
||||
List<Face> detachedFaces = new ArrayList<Face>();
|
||||
List<Integer> path = new ArrayList<Integer>(indexes.size());
|
||||
|
||||
boolean[] edgeRemoved = new boolean[] { indexes.removeEdge(triangleIndexes[0], triangleIndexes[1]), indexes.removeEdge(triangleIndexes[0], triangleIndexes[2]), indexes.removeEdge(triangleIndexes[1], triangleIndexes[2]) };
|
||||
Integer[][] indexesPairs = new Integer[][] { new Integer[] { triangleIndexes[0], triangleIndexes[1] }, new Integer[] { triangleIndexes[0], triangleIndexes[2] }, new Integer[] { triangleIndexes[1], triangleIndexes[2] } };
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!edgeRemoved[i]) {
|
||||
indexes.findPath(indexesPairs[i][0], indexesPairs[i][1], path);
|
||||
if (path.size() == 0) {
|
||||
indexes.findPath(indexesPairs[i][1], indexesPairs[i][0], path);
|
||||
}
|
||||
if (path.size() == 0) {
|
||||
throw new IllegalStateException("Triangulation failed. Cannot find path between two indexes. Please apply triangulation in Blender as a workaround.");
|
||||
}
|
||||
if (detachedFaces.size() == 0 && path.size() < indexes.size()) {
|
||||
Integer[] indexesSublist = path.toArray(new Integer[path.size()]);
|
||||
detachedFaces.add(new Face(indexesSublist, smooth, materialNumber, meshHelper.selectUVSubset(this, indexesSublist), meshHelper.selectVertexColorSubset(this, indexesSublist), temporalMesh));
|
||||
for (int j = 0; j < path.size() - 1; ++j) {
|
||||
indexes.removeEdge(path.get(j), path.get(j + 1));
|
||||
}
|
||||
indexes.removeEdge(path.get(path.size() - 1), path.get(0));
|
||||
} else {
|
||||
indexes.addEdge(path.get(path.size() - 1), path.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return detachedFaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the temporal mesh for the face. The given mesh cannot be null.
|
||||
* @param temporalMesh
|
||||
* the temporal mesh of the face
|
||||
* @throws IllegalArgumentException
|
||||
* thrown if given temporal mesh is null
|
||||
*/
|
||||
public void setTemporalMesh(TemporalMesh temporalMesh) {
|
||||
if (temporalMesh == null) {
|
||||
throw new IllegalArgumentException("No temporal mesh for the face given!");
|
||||
}
|
||||
this.temporalMesh = temporalMesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips the order of the indexes.
|
||||
*/
|
||||
public void flipIndexes() {
|
||||
indexes.reverse();
|
||||
if (faceUVCoords != null) {
|
||||
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
|
||||
Collections.reverse(entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flips UV coordinates.
|
||||
* @param u
|
||||
* indicates if U coords should be flipped
|
||||
* @param v
|
||||
* indicates if V coords should be flipped
|
||||
*/
|
||||
public void flipUV(boolean u, boolean v) {
|
||||
if (faceUVCoords != null) {
|
||||
for (Entry<String, List<Vector2f>> entry : faceUVCoords.entrySet()) {
|
||||
for (Vector2f uv : entry.getValue()) {
|
||||
uv.set(u ? 1 - uv.x : uv.x, v ? 1 - uv.y : uv.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the UV sets of the face
|
||||
*/
|
||||
public Map<String, List<Vector2f>> getUvSets() {
|
||||
return faceUVCoords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current vertex count of the face
|
||||
*/
|
||||
public int vertexCount() {
|
||||
return indexes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* The method triangulates the face.
|
||||
*/
|
||||
public TriangulationWarning triangulate() {
|
||||
LOGGER.fine("Triangulating face.");
|
||||
assert indexes.size() >= 3 : "Invalid indexes amount for face. 3 is the required minimum!";
|
||||
triangulatedFaces = new ArrayList<IndexesLoop>(indexes.size() - 2);
|
||||
Integer[] indexes = new Integer[3];
|
||||
TriangulationWarning warning = TriangulationWarning.NONE;
|
||||
|
||||
try {
|
||||
List<Face> facesToTriangulate = new ArrayList<Face>(Arrays.asList(this.clone()));
|
||||
while (facesToTriangulate.size() > 0 && warning == TriangulationWarning.NONE) {
|
||||
Face face = facesToTriangulate.remove(0);
|
||||
// two special cases will improve the computations speed
|
||||
if(face.getIndexes().size() == 3) {
|
||||
triangulatedFaces.add(face.getIndexes().clone());
|
||||
} else {
|
||||
int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1;
|
||||
while (face.vertexCount() > 0) {
|
||||
indexes[0] = face.getIndex(0);
|
||||
indexes[1] = face.findClosestVertex(indexes[0], -1);
|
||||
indexes[2] = face.findClosestVertex(indexes[0], indexes[1]);
|
||||
|
||||
LOGGER.finer("Veryfying improper triangulation of the temporal mesh.");
|
||||
if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) {
|
||||
warning = TriangulationWarning.CLOSEST_VERTS;
|
||||
break;
|
||||
}
|
||||
if (previousIndex1 == indexes[0] && previousIndex2 == indexes[1] && previousIndex3 == indexes[2]) {
|
||||
warning = TriangulationWarning.INFINITE_LOOP;
|
||||
break;
|
||||
}
|
||||
previousIndex1 = indexes[0];
|
||||
previousIndex2 = indexes[1];
|
||||
previousIndex3 = indexes[2];
|
||||
|
||||
Arrays.sort(indexes, this);
|
||||
facesToTriangulate.addAll(face.detachTriangle(indexes));
|
||||
triangulatedFaces.add(new IndexesLoop(indexes));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (BlenderFileException e) {
|
||||
LOGGER.log(Level.WARNING, "Errors occurred during face triangulation: {0}. The face will be triangulated with the most direct algorithm, but the results might not be identical to blender.", e.getLocalizedMessage());
|
||||
warning = TriangulationWarning.UNKNOWN;
|
||||
}
|
||||
if(warning != TriangulationWarning.NONE) {
|
||||
LOGGER.finest("Triangulation the face using the most direct algorithm.");
|
||||
indexes[0] = this.getIndex(0);
|
||||
for (int i = 1; i < this.vertexCount() - 1; ++i) {
|
||||
indexes[1] = this.getIndex(i);
|
||||
indexes[2] = this.getIndex(i + 1);
|
||||
triangulatedFaces.add(new IndexesLoop(indexes));
|
||||
}
|
||||
}
|
||||
return warning;
|
||||
}
|
||||
|
||||
/**
|
||||
* A warning that indicates a problem with face triangulation. The warnings are collected and displayed once for each type for a mesh to
|
||||
* avoid multiple warning loggings during triangulation. The amount of iterations can be really huge and logging every single failure would
|
||||
* really slow down the importing process and make logs unreadable.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static enum TriangulationWarning {
|
||||
NONE(null),
|
||||
CLOSEST_VERTS("Unable to find two closest vertices while triangulating face."),
|
||||
INFINITE_LOOP("Infinite loop detected during triangulation."),
|
||||
UNKNOWN("There was an unknown problem with face triangulation. Please see log for details.");
|
||||
|
||||
private String description;
|
||||
|
||||
private TriangulationWarning(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if the face is smooth and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isSmooth() {
|
||||
return smooth;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the material index of the face
|
||||
*/
|
||||
public int getMaterialNumber() {
|
||||
return materialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vertices colord of the face
|
||||
*/
|
||||
public List<byte[]> getVertexColors() {
|
||||
return vertexColors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Face " + indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method finds the closest vertex to the one specified by <b>index</b>.
|
||||
* If the vertexToIgnore is positive than it will be ignored in the result.
|
||||
* The closest vertex must be able to create an edge that is fully contained
|
||||
* within the face and does not cross any other edges. Also if the
|
||||
* vertexToIgnore is not negative then the condition that the edge between
|
||||
* the found index and the one to ignore is inside the face must also be
|
||||
* met.
|
||||
*
|
||||
* @param index
|
||||
* the index of the vertex that needs to have found the nearest
|
||||
* neighbour
|
||||
* @param indexToIgnore
|
||||
* the index to ignore in the result (pass -1 if none is to be
|
||||
* ignored)
|
||||
* @return the index of the closest vertex to the given one
|
||||
*/
|
||||
private int findClosestVertex(int index, int indexToIgnore) {
|
||||
int result = -1;
|
||||
List<Vector3f> vertices = temporalMesh.getVertices();
|
||||
Vector3f v1 = vertices.get(index);
|
||||
float distance = Float.MAX_VALUE;
|
||||
for (int i : indexes) {
|
||||
if (i != index && i != indexToIgnore) {
|
||||
Vector3f v2 = vertices.get(i);
|
||||
float d = v2.distance(v1);
|
||||
if (d < distance && this.contains(new Edge(index, i, 0, true, temporalMesh)) && (indexToIgnore < 0 || this.contains(new Edge(indexToIgnore, i, 0, true, temporalMesh)))) {
|
||||
result = i;
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method verifies if the edge is contained within the face.
|
||||
* It means it cannot cross any other edge and it must be inside the face and not outside of it.
|
||||
* @param edge
|
||||
* the edge to be checked
|
||||
* @return <b>true</b> if the given edge is contained within the face and <b>false</b> otherwise
|
||||
*/
|
||||
private boolean contains(Edge edge) {
|
||||
int index1 = edge.getFirstIndex();
|
||||
int index2 = edge.getSecondIndex();
|
||||
// check if the line between the vertices is not a border edge of the face
|
||||
if (!indexes.areNeighbours(index1, index2)) {
|
||||
for (int i = 0; i < indexes.size(); ++i) {
|
||||
int i1 = this.getIndex(i - 1);
|
||||
int i2 = this.getIndex(i);
|
||||
// check if the edges have no common verts (because if they do, they cannot cross)
|
||||
if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) {
|
||||
if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// computing the edge's middle point
|
||||
Vector3f edgeMiddlePoint = edge.computeCentroid();
|
||||
// computing the edge that is perpendicular to the given edge and has a length of 1 (length actually does not matter)
|
||||
Vector3f edgeVector = edge.getSecondVertex().subtract(edge.getFirstVertex());
|
||||
Vector3f edgeNormal = temporalMesh.getNormals().get(index1).cross(edgeVector).normalizeLocal();
|
||||
Edge e = new Edge(edgeMiddlePoint, edgeNormal.add(edgeMiddlePoint));
|
||||
// compute the vectors from the middle point to the crossing between the extended edge 'e' and other edges of the face
|
||||
List<Vector3f> crossingVectors = new ArrayList<Vector3f>();
|
||||
for (int i = 0; i < indexes.size(); ++i) {
|
||||
int i1 = this.getIndex(i);
|
||||
int i2 = this.getIndex(i + 1);
|
||||
Vector3f crossPoint = e.getCrossPoint(new Edge(i1, i2, 0, false, temporalMesh), true, false);
|
||||
if(crossPoint != null) {
|
||||
crossingVectors.add(crossPoint.subtractLocal(edgeMiddlePoint));
|
||||
}
|
||||
}
|
||||
if(crossingVectors.size() == 0) {
|
||||
return false;// edges do not cross
|
||||
}
|
||||
|
||||
// use only distinct vertices (doubles may appear if the crossing point is a vertex)
|
||||
List<Vector3f> distinctCrossingVectors = new ArrayList<Vector3f>();
|
||||
for(Vector3f cv : crossingVectors) {
|
||||
double minDistance = Double.MAX_VALUE;
|
||||
for(Vector3f dcv : distinctCrossingVectors) {
|
||||
minDistance = Math.min(minDistance, dcv.distance(cv));
|
||||
}
|
||||
if(minDistance > FastMath.FLT_EPSILON) {
|
||||
distinctCrossingVectors.add(cv);
|
||||
}
|
||||
}
|
||||
|
||||
if(distinctCrossingVectors.size() == 0) {
|
||||
throw new IllegalStateException("There MUST be at least 2 crossing vertices!");
|
||||
}
|
||||
// checking if all crossing vectors point to the same direction (if yes then the edge is outside the face)
|
||||
float direction = Math.signum(distinctCrossingVectors.get(0).dot(edgeNormal));// if at least one vector has different direction that this - it means that the edge is inside the face
|
||||
for(int i=1;i<distinctCrossingVectors.size();++i) {
|
||||
if(direction != Math.signum(distinctCrossingVectors.get(i).dot(edgeNormal))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + indexes.hashCode();
|
||||
result = prime * result + temporalMesh.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof Face)) {
|
||||
return false;
|
||||
}
|
||||
Face other = (Face) obj;
|
||||
if (!indexes.equals(other.indexes)) {
|
||||
return false;
|
||||
}
|
||||
return temporalMesh.equals(other.temporalMesh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all faces of a given mesh.
|
||||
* @param meshStructure
|
||||
* the mesh structure we read the faces from
|
||||
* @param userUVGroups
|
||||
* UV groups defined by the user
|
||||
* @param verticesColors
|
||||
* the vertices colors of the mesh
|
||||
* @param temporalMesh
|
||||
* the temporal mesh the faces will belong to
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return list of faces read from the given mesh structure
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with file reading occur
|
||||
*/
|
||||
public static List<Face> loadAll(Structure meshStructure, Map<String, List<Vector2f>> userUVGroups, List<byte[]> verticesColors, TemporalMesh temporalMesh, BlenderContext blenderContext) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading all faces from mesh: {0}", meshStructure.getName());
|
||||
List<Face> result = new ArrayList<Face>();
|
||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
|
||||
if (meshHelper.isBMeshCompatible(meshStructure)) {
|
||||
LOGGER.fine("Reading BMesh.");
|
||||
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
|
||||
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
|
||||
|
||||
if (pMPoly.isNotNull() && pMLoop.isNotNull()) {
|
||||
List<Structure> polys = pMPoly.fetchData();
|
||||
List<Structure> loops = pMLoop.fetchData();
|
||||
for (Structure poly : polys) {
|
||||
int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue();
|
||||
int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue();
|
||||
int totLoop = ((Number) poly.getFieldValue("totloop")).intValue();
|
||||
boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
|
||||
Integer[] vertexIndexes = new Integer[totLoop];
|
||||
|
||||
for (int i = loopStart; i < loopStart + totLoop; ++i) {
|
||||
vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue();
|
||||
}
|
||||
|
||||
// uvs always must be added wheater we have texture or not
|
||||
Map<String, List<Vector2f>> uvCoords = new HashMap<String, List<Vector2f>>();
|
||||
for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) {
|
||||
List<Vector2f> uvs = entry.getValue().subList(loopStart, loopStart + totLoop);
|
||||
uvCoords.put(entry.getKey(), new ArrayList<Vector2f>(uvs));
|
||||
}
|
||||
|
||||
List<byte[]> vertexColors = null;
|
||||
if (verticesColors != null && verticesColors.size() > 0) {
|
||||
vertexColors = new ArrayList<byte[]>(totLoop);
|
||||
for (int i = loopStart; i < loopStart + totLoop; ++i) {
|
||||
vertexColors.add(verticesColors.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
result.add(new Face(vertexIndexes, smooth, materialNumber, uvCoords, vertexColors, temporalMesh));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.fine("Reading traditional faces.");
|
||||
Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface");
|
||||
List<Structure> mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null;
|
||||
if (mFaces != null && mFaces.size() > 0) {
|
||||
// indicates if the material with the specified number should have a texture attached
|
||||
for (int i = 0; i < mFaces.size(); ++i) {
|
||||
Structure mFace = mFaces.get(i);
|
||||
int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue();
|
||||
boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00;
|
||||
|
||||
int v1 = ((Number) mFace.getFieldValue("v1")).intValue();
|
||||
int v2 = ((Number) mFace.getFieldValue("v2")).intValue();
|
||||
int v3 = ((Number) mFace.getFieldValue("v3")).intValue();
|
||||
int v4 = ((Number) mFace.getFieldValue("v4")).intValue();
|
||||
|
||||
int vertCount = v4 == 0 ? 3 : 4;
|
||||
|
||||
// uvs always must be added wheater we have texture or not
|
||||
Map<String, List<Vector2f>> faceUVCoords = new HashMap<String, List<Vector2f>>();
|
||||
for (Entry<String, List<Vector2f>> entry : userUVGroups.entrySet()) {
|
||||
List<Vector2f> uvCoordsForASingleFace = new ArrayList<Vector2f>(vertCount);
|
||||
for (int j = 0; j < vertCount; ++j) {
|
||||
uvCoordsForASingleFace.add(entry.getValue().get(i * 4 + j));
|
||||
}
|
||||
faceUVCoords.put(entry.getKey(), uvCoordsForASingleFace);
|
||||
}
|
||||
|
||||
List<byte[]> vertexColors = null;
|
||||
if (verticesColors != null && verticesColors.size() > 0) {
|
||||
vertexColors = new ArrayList<byte[]>(vertCount);
|
||||
|
||||
vertexColors.add(verticesColors.get(v1));
|
||||
vertexColors.add(verticesColors.get(v2));
|
||||
vertexColors.add(verticesColors.get(v3));
|
||||
if (vertCount == 4) {
|
||||
vertexColors.add(verticesColors.get(v4));
|
||||
}
|
||||
}
|
||||
|
||||
result.add(new Face(vertCount == 4 ? new Integer[] { v1, v2, v3, v4 } : new Integer[] { v1, v2, v3 }, smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh));
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.log(Level.FINE, "Loaded {0} faces.", result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Integer index1, Integer index2) {
|
||||
return indexes.indexOf(index1) - indexes.indexOf(index2);
|
||||
}
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.meshes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
|
||||
/**
|
||||
* This class represents the Face's indexes loop. It is a simplified implementation of directed graph.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class IndexesLoop implements Comparator<Integer>, Iterable<Integer> {
|
||||
public static final IndexPredicate INDEX_PREDICATE_USE_ALL = new IndexPredicate() {
|
||||
@Override
|
||||
public boolean execute(Integer index) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** The indexes. */
|
||||
private List<Integer> nodes;
|
||||
/** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */
|
||||
private Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
|
||||
|
||||
/**
|
||||
* The constructor uses the given nodes in their give order. Each neighbour indexes will form an edge.
|
||||
* @param nodes
|
||||
* the nodes for the loop
|
||||
*/
|
||||
public IndexesLoop(Integer[] nodes) {
|
||||
this.nodes = new ArrayList<Integer>(Arrays.asList(nodes));
|
||||
this.prepareEdges(this.nodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexesLoop clone() {
|
||||
return new IndexesLoop(nodes.toArray(new Integer[nodes.size()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* The method prepares edges for the given indexes.
|
||||
* @param nodes
|
||||
* the indexes
|
||||
*/
|
||||
private void prepareEdges(List<Integer> nodes) {
|
||||
for (int i = 0; i < nodes.size() - 1; ++i) {
|
||||
if (edges.containsKey(nodes.get(i))) {
|
||||
edges.get(nodes.get(i)).add(nodes.get(i + 1));
|
||||
} else {
|
||||
edges.put(nodes.get(i), new ArrayList<Integer>(Arrays.asList(nodes.get(i + 1))));
|
||||
}
|
||||
}
|
||||
edges.put(nodes.get(nodes.size() - 1), new ArrayList<Integer>(Arrays.asList(nodes.get(0))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the count of indexes
|
||||
*/
|
||||
public int size() {
|
||||
return nodes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds edge to the loop.
|
||||
* @param from
|
||||
* the start index
|
||||
* @param to
|
||||
* the end index
|
||||
*/
|
||||
public void addEdge(Integer from, Integer to) {
|
||||
if (nodes.contains(from) && nodes.contains(to)) {
|
||||
if (edges.containsKey(from) && !edges.get(from).contains(to)) {
|
||||
edges.get(from).add(to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes edge from the face. The edge is removed if it already exists in the face.
|
||||
* @param node1
|
||||
* the first index of the edge to be removed
|
||||
* @param node2
|
||||
* the second index of the edge to be removed
|
||||
* @return <b>true</b> if the edge was removed and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean removeEdge(Integer node1, Integer node2) {
|
||||
boolean edgeRemoved = false;
|
||||
if (nodes.contains(node1) && nodes.contains(node2)) {
|
||||
if (edges.containsKey(node1)) {
|
||||
edgeRemoved |= edges.get(node1).remove(node2);
|
||||
}
|
||||
if (edges.containsKey(node2)) {
|
||||
edgeRemoved |= edges.get(node2).remove(node1);
|
||||
}
|
||||
if (edgeRemoved) {
|
||||
if (this.getNeighbourCount(node1) == 0) {
|
||||
this.removeIndexes(node1);
|
||||
}
|
||||
if (this.getNeighbourCount(node2) == 0) {
|
||||
this.removeIndexes(node2);
|
||||
}
|
||||
}
|
||||
}
|
||||
return edgeRemoved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given indexes are neighbours.
|
||||
* @param index1
|
||||
* the first index
|
||||
* @param index2
|
||||
* the second index
|
||||
* @return <b>true</b> if the given indexes are neighbours and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean areNeighbours(Integer index1, Integer index2) {
|
||||
if (index1.equals(index2) || !edges.containsKey(index1) || !edges.containsKey(index2)) {
|
||||
return false;
|
||||
}
|
||||
return edges.get(index1).contains(index2) || edges.get(index2).contains(index1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the index located after the given one. Pointint the last index will return the first one.
|
||||
* @param index
|
||||
* the index value
|
||||
* @return the value of 'next' index
|
||||
*/
|
||||
public Integer getNextIndex(Integer index) {
|
||||
int i = nodes.indexOf(index);
|
||||
return i == nodes.size() - 1 ? nodes.get(0) : nodes.get(i + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the index located before the given one. Pointint the first index will return the last one.
|
||||
* @param index
|
||||
* the index value
|
||||
* @return the value of 'previous' index
|
||||
*/
|
||||
public Integer getPreviousIndex(Integer index) {
|
||||
int i = nodes.indexOf(index);
|
||||
return i == 0 ? nodes.get(nodes.size() - 1) : nodes.get(i - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method shifts all indexes by a given value.
|
||||
* @param shift
|
||||
* the value to shift all indexes
|
||||
* @param predicate
|
||||
* the predicate that verifies which indexes should be shifted; if null then all will be shifted
|
||||
*/
|
||||
public void shiftIndexes(int shift, IndexPredicate predicate) {
|
||||
if (predicate == null) {
|
||||
predicate = INDEX_PREDICATE_USE_ALL;
|
||||
}
|
||||
List<Integer> nodes = new ArrayList<Integer>(this.nodes.size());
|
||||
for (Integer node : this.nodes) {
|
||||
nodes.add(node + (predicate.execute(node) ? shift : 0));
|
||||
}
|
||||
|
||||
Map<Integer, List<Integer>> edges = new HashMap<Integer, List<Integer>>();
|
||||
for (Entry<Integer, List<Integer>> entry : this.edges.entrySet()) {
|
||||
List<Integer> neighbours = new ArrayList<Integer>(entry.getValue().size());
|
||||
for (Integer neighbour : entry.getValue()) {
|
||||
neighbours.add(neighbour + (predicate.execute(neighbour) ? shift : 0));
|
||||
}
|
||||
edges.put(entry.getKey() + shift, neighbours);
|
||||
}
|
||||
|
||||
this.nodes = nodes;
|
||||
this.edges = edges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the order of the indexes.
|
||||
*/
|
||||
public void reverse() {
|
||||
Collections.reverse(nodes);
|
||||
edges.clear();
|
||||
this.prepareEdges(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the neighbour count of the given index.
|
||||
* @param index
|
||||
* the index whose neighbour count will be checked
|
||||
* @return the count of neighbours of the given index
|
||||
*/
|
||||
private int getNeighbourCount(Integer index) {
|
||||
int result = 0;
|
||||
if (edges.containsKey(index)) {
|
||||
result = edges.get(index).size();
|
||||
for (List<Integer> neighbours : edges.values()) {
|
||||
if (neighbours.contains(index)) {
|
||||
++result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the given index in the loop.
|
||||
* @param index
|
||||
* the index of the face
|
||||
* @return the indexe's position in the loop
|
||||
*/
|
||||
public int indexOf(Integer index) {
|
||||
return nodes.indexOf(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index at the given position.
|
||||
* @param indexPosition
|
||||
* the position of the index
|
||||
* @return the index at a given position
|
||||
*/
|
||||
public Integer get(int indexPosition) {
|
||||
return nodes.get(indexPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all indexes of the face
|
||||
*/
|
||||
public List<Integer> getAll() {
|
||||
return new ArrayList<Integer>(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method removes all given indexes.
|
||||
* @param indexes
|
||||
* the indexes to be removed
|
||||
*/
|
||||
public void removeIndexes(Integer... indexes) {
|
||||
for (Integer index : indexes) {
|
||||
nodes.remove(index);
|
||||
edges.remove(index);
|
||||
for (List<Integer> neighbours : edges.values()) {
|
||||
neighbours.remove(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method finds the path between the given indexes.
|
||||
* @param start
|
||||
* the start index
|
||||
* @param end
|
||||
* the end index
|
||||
* @param result
|
||||
* a list containing indexes on the path from start to end (inclusive)
|
||||
* @throws IllegalStateException
|
||||
* an exception is thrown when the loop is not normalized (at least one
|
||||
* index has more than 2 neighbours)
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown if the vertices of a face create more than one loop; this is thrown
|
||||
* to prevent lack of memory errors during triangulation
|
||||
*/
|
||||
public void findPath(Integer start, Integer end, List<Integer> result) throws BlenderFileException {
|
||||
result.clear();
|
||||
Integer node = start;
|
||||
while (!node.equals(end)) {
|
||||
if (result.contains(node)) {
|
||||
throw new BlenderFileException("Indexes of face have infinite loops!");
|
||||
}
|
||||
result.add(node);
|
||||
List<Integer> nextSteps = edges.get(node);
|
||||
if (nextSteps == null || nextSteps.size() == 0) {
|
||||
result.clear();// no directed path from start to end
|
||||
return;
|
||||
} else if (nextSteps.size() == 1) {
|
||||
node = nextSteps.get(0);
|
||||
} else {
|
||||
throw new BlenderFileException("Triangulation failed. Face has ambiguous indexes loop. Please triangulate your model in Blender as a workaround.");
|
||||
}
|
||||
}
|
||||
result.add(end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexesLoop " + nodes.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Integer i1, Integer i2) {
|
||||
return nodes.indexOf(i1) - nodes.indexOf(i2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Integer> iterator() {
|
||||
return nodes.iterator();
|
||||
}
|
||||
|
||||
public static interface IndexPredicate {
|
||||
boolean execute(Integer index);
|
||||
}
|
||||
}
|
@ -1,364 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.meshes;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Format;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
/**
|
||||
* A class that aggregates the mesh data to prepare proper buffers. The buffers refer only to ONE material.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class MeshBuffers {
|
||||
private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4;
|
||||
|
||||
/** The material index. */
|
||||
private final int materialIndex;
|
||||
/** The vertices. */
|
||||
private List<Vector3f> verts = new ArrayList<Vector3f>();
|
||||
/** The normals. */
|
||||
private List<Vector3f> normals = new ArrayList<Vector3f>();
|
||||
/** The UV coordinate sets. */
|
||||
private Map<String, List<Vector2f>> uvCoords = new HashMap<String, List<Vector2f>>();
|
||||
/** The vertex colors. */
|
||||
private List<byte[]> vertColors = new ArrayList<byte[]>();
|
||||
/** The indexes. */
|
||||
private List<Integer> indexes = new ArrayList<Integer>();
|
||||
/** The maximum weights count assigned to a single vertex. Used during weights normalization. */
|
||||
private int maximumWeightsPerVertex;
|
||||
/** A list of mapping between weights and indexes. Each entry for the proper vertex. */
|
||||
private List<TreeMap<Float, Integer>> boneWeightAndIndexes = new ArrayList<TreeMap<Float, Integer>>();
|
||||
|
||||
/**
|
||||
* Constructor stores only the material index value.
|
||||
* @param materialIndex
|
||||
* the material index
|
||||
*/
|
||||
public MeshBuffers(int materialIndex) {
|
||||
this.materialIndex = materialIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the material index
|
||||
*/
|
||||
public int getMaterialIndex() {
|
||||
return materialIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indexes buffer
|
||||
*/
|
||||
public Buffer getIndexBuffer() {
|
||||
if (indexes.size() <= Short.MAX_VALUE) {
|
||||
short[] indices = new short[indexes.size()];
|
||||
for (int i = 0; i < indexes.size(); ++i) {
|
||||
indices[i] = indexes.get(i).shortValue();
|
||||
}
|
||||
return BufferUtils.createShortBuffer(indices);
|
||||
} else {
|
||||
int[] indices = new int[indexes.size()];
|
||||
for (int i = 0; i < indexes.size(); ++i) {
|
||||
indices[i] = indexes.get(i).intValue();
|
||||
}
|
||||
return BufferUtils.createIntBuffer(indices);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return positions buffer
|
||||
*/
|
||||
public VertexBuffer getPositionsBuffer() {
|
||||
VertexBuffer positionBuffer = new VertexBuffer(Type.Position);
|
||||
Vector3f[] data = verts.toArray(new Vector3f[verts.size()]);
|
||||
positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data));
|
||||
return positionBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return normals buffer
|
||||
*/
|
||||
public VertexBuffer getNormalsBuffer() {
|
||||
VertexBuffer positionBuffer = new VertexBuffer(Type.Normal);
|
||||
Vector3f[] data = normals.toArray(new Vector3f[normals.size()]);
|
||||
positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data));
|
||||
return positionBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bone buffers
|
||||
*/
|
||||
public BoneBuffersData getBoneBuffers() {
|
||||
BoneBuffersData result = null;
|
||||
if (maximumWeightsPerVertex > 0) {
|
||||
this.normalizeBoneBuffers(MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||
maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;
|
||||
|
||||
FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||
ByteBuffer indicesData = BufferUtils.createByteBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX);
|
||||
int index = 0;
|
||||
for (Map<Float, Integer> boneBuffersData : boneWeightAndIndexes) {
|
||||
if (boneBuffersData.size() > 0) {
|
||||
int count = 0;
|
||||
for (Entry<Float, Integer> entry : boneBuffersData.entrySet()) {
|
||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey());
|
||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue().byteValue());
|
||||
++count;
|
||||
}
|
||||
} else {
|
||||
// if no bone is assigned to this vertex then attach it to the 0-indexed root bone
|
||||
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
|
||||
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
|
||||
}
|
||||
++index;
|
||||
}
|
||||
VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
|
||||
verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
|
||||
|
||||
VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
|
||||
verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
|
||||
|
||||
result = new BoneBuffersData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UV coordinates sets
|
||||
*/
|
||||
public Map<String, List<Vector2f>> getUvCoords() {
|
||||
return uvCoords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if vertex colors are used and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean areVertexColorsUsed() {
|
||||
return vertColors.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return vertex colors buffer
|
||||
*/
|
||||
public ByteBuffer getVertexColorsBuffer() {
|
||||
ByteBuffer result = null;
|
||||
if (vertColors.size() > 0) {
|
||||
result = BufferUtils.createByteBuffer(4 * vertColors.size());
|
||||
for (byte[] v : vertColors) {
|
||||
if (v != null) {
|
||||
result.put(v[0]).put(v[1]).put(v[2]).put(v[3]);
|
||||
} else {
|
||||
result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0);
|
||||
}
|
||||
}
|
||||
result.flip();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if indexes can be shorts' and <b>false</b> if they need to be ints'
|
||||
*/
|
||||
public boolean isShortIndexBuffer() {
|
||||
return indexes.size() <= Short.MAX_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a vertex and normal to the buffers.
|
||||
* @param vert
|
||||
* vertex
|
||||
* @param normal
|
||||
* normal vector
|
||||
*/
|
||||
public void append(Vector3f vert, Vector3f normal) {
|
||||
int index = this.indexOf(vert, normal, null);
|
||||
if (index >= 0) {
|
||||
indexes.add(index);
|
||||
} else {
|
||||
indexes.add(verts.size());
|
||||
verts.add(vert);
|
||||
normals.add(normal);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the face data to the buffers.
|
||||
* @param smooth
|
||||
* tells if the face is smooth or flat
|
||||
* @param verts
|
||||
* the vertices
|
||||
* @param normals
|
||||
* the normals
|
||||
* @param uvCoords
|
||||
* the UV coordinates
|
||||
* @param vertColors
|
||||
* the vertex colors
|
||||
* @param vertexGroups
|
||||
* the vertex groups
|
||||
*/
|
||||
public void append(boolean smooth, Vector3f[] verts, Vector3f[] normals, Map<String, List<Vector2f>> uvCoords, byte[][] vertColors, List<Map<Float, Integer>> vertexGroups) {
|
||||
if (verts.length != normals.length) {
|
||||
throw new IllegalArgumentException("The amount of verts and normals MUST be equal!");
|
||||
}
|
||||
if (vertColors != null && vertColors.length != verts.length) {
|
||||
throw new IllegalArgumentException("The amount of vertex colors and vertices MUST be equal!");
|
||||
}
|
||||
if (vertexGroups.size() != 0 && vertexGroups.size() != verts.length) {
|
||||
throw new IllegalArgumentException("The amount of (if given) vertex groups and vertices MUST be equal!");
|
||||
}
|
||||
|
||||
if (!smooth) {
|
||||
// make the normals perpendicular to the face
|
||||
normals[0] = normals[1] = normals[2] = FastMath.computeNormal(verts[0], verts[1], verts[2]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < verts.length; ++i) {
|
||||
int index = -1;
|
||||
Map<String, Vector2f> uvCoordsForVertex = this.getUVsForVertex(i, uvCoords);
|
||||
if (smooth && (index = this.indexOf(verts[i], normals[i], uvCoordsForVertex)) >= 0) {
|
||||
indexes.add(index);
|
||||
} else {
|
||||
indexes.add(this.verts.size());
|
||||
this.verts.add(verts[i]);
|
||||
this.normals.add(normals[i]);
|
||||
this.vertColors.add(vertColors[i]);
|
||||
|
||||
if (uvCoords != null && uvCoords.size() > 0) {
|
||||
for (Entry<String, List<Vector2f>> entry : uvCoords.entrySet()) {
|
||||
if (this.uvCoords.containsKey(entry.getKey())) {
|
||||
this.uvCoords.get(entry.getKey()).add(entry.getValue().get(i));
|
||||
} else {
|
||||
List<Vector2f> uvs = new ArrayList<Vector2f>();
|
||||
uvs.add(entry.getValue().get(i));
|
||||
this.uvCoords.put(entry.getKey(), uvs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (vertexGroups != null && vertexGroups.size() > 0) {
|
||||
Map<Float, Integer> group = vertexGroups.get(i);
|
||||
maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, group.size());
|
||||
boneWeightAndIndexes.add(new TreeMap<Float, Integer>(group));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns UV coordinates assigned for the vertex with the proper index.
|
||||
* @param vertexIndex
|
||||
* the index of the vertex
|
||||
* @param uvs
|
||||
* all UV coordinates we search in
|
||||
* @return a set of UV coordinates assigned to the given vertex
|
||||
*/
|
||||
private Map<String, Vector2f> getUVsForVertex(int vertexIndex, Map<String, List<Vector2f>> uvs) {
|
||||
if (uvs == null || uvs.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Vector2f> result = new HashMap<String, Vector2f>(uvs.size());
|
||||
for (Entry<String, List<Vector2f>> entry : uvs.entrySet()) {
|
||||
result.put(entry.getKey(), entry.getValue().get(vertexIndex));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method returns an index of a vertex described by the given data.
|
||||
* The method tries to find a vertex that mathes the given data. If it does it means
|
||||
* that such vertex is already used.
|
||||
* @param vert
|
||||
* the vertex position coordinates
|
||||
* @param normal
|
||||
* the vertex's normal vector
|
||||
* @param uvCoords
|
||||
* the UV coords of the vertex
|
||||
* @return index of the found vertex of -1
|
||||
*/
|
||||
private int indexOf(Vector3f vert, Vector3f normal, Map<String, Vector2f> uvCoords) {
|
||||
for (int i = 0; i < verts.size(); ++i) {
|
||||
if (verts.get(i).equals(vert) && normals.get(i).equals(normal)) {
|
||||
if (uvCoords != null && uvCoords.size() > 0) {
|
||||
for (Entry<String, Vector2f> entry : uvCoords.entrySet()) {
|
||||
List<Vector2f> uvs = this.uvCoords.get(entry.getKey());
|
||||
if (uvs == null) {
|
||||
return -1;
|
||||
}
|
||||
if (!uvs.get(i).equals(entry.getValue())) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method normalizes the weights and bone indexes data.
|
||||
* First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
|
||||
* Next it normalizes the weights so that the sum of all verts is 1.
|
||||
* @param maximumSize
|
||||
* the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
|
||||
*/
|
||||
private void normalizeBoneBuffers(int maximumSize) {
|
||||
for (TreeMap<Float, Integer> group : boneWeightAndIndexes) {
|
||||
if (group.size() > maximumSize) {
|
||||
NavigableMap<Float, Integer> descendingWeights = group.descendingMap();
|
||||
while (descendingWeights.size() > maximumSize) {
|
||||
descendingWeights.pollLastEntry();
|
||||
}
|
||||
}
|
||||
|
||||
// normalizing the weights so that the sum of the values is equal to '1'
|
||||
TreeMap<Float, Integer> normalizedGroup = new TreeMap<Float, Integer>();
|
||||
float sum = 0;
|
||||
for (Entry<Float, Integer> entry : group.entrySet()) {
|
||||
sum += entry.getKey();
|
||||
}
|
||||
|
||||
if (sum != 0 && sum != 1) {
|
||||
for (Entry<Float, Integer> entry : group.entrySet()) {
|
||||
normalizedGroup.put(entry.getKey() / sum, entry.getValue());
|
||||
}
|
||||
group.clear();
|
||||
group.putAll(normalizedGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that gathers the data for mesh bone buffers.
|
||||
* Added to increase code readability.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public static class BoneBuffersData {
|
||||
public final int maximumWeightsPerVertex;
|
||||
public final VertexBuffer verticesWeights;
|
||||
public final VertexBuffer verticesWeightsIndices;
|
||||
|
||||
public BoneBuffersData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
|
||||
this.maximumWeightsPerVertex = maximumWeightsPerVertex;
|
||||
this.verticesWeights = verticesWeights;
|
||||
this.verticesWeightsIndices = verticesWeightsIndices;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2019 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.meshes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
||||
import com.jme3.scene.plugins.blender.objects.Properties;
|
||||
|
||||
/**
|
||||
* A class that is used in mesh calculations.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class MeshHelper extends AbstractBlenderHelper {
|
||||
private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName());
|
||||
|
||||
/** A type of UV data layer in traditional faced mesh (triangles or quads). */
|
||||
public static final int UV_DATA_LAYER_TYPE_FMESH = 5;
|
||||
/** A type of UV data layer in bmesh type. */
|
||||
public static final int UV_DATA_LAYER_TYPE_BMESH = 16;
|
||||
|
||||
/** A material used for single lines and points. */
|
||||
private Material blackUnshadedMaterial;
|
||||
|
||||
/**
|
||||
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
|
||||
* versions.
|
||||
*
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public MeshHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the mesh structure into temporal mesh.
|
||||
* The temporal mesh is stored in blender context and here always a clone is being returned because the mesh might
|
||||
* be modified by modifiers.
|
||||
*
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return temporal mesh read from the given structure
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with reading blend file occur
|
||||
*/
|
||||
public TemporalMesh toTemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading temporal mesh named: {0}.", meshStructure.getName());
|
||||
TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH);
|
||||
if (temporalMesh != null) {
|
||||
LOGGER.fine("The mesh is already loaded. Returning its clone.");
|
||||
return temporalMesh.clone();
|
||||
}
|
||||
|
||||
if ("ID".equals(meshStructure.getType())) {
|
||||
LOGGER.fine("Loading mesh from external blend file.");
|
||||
return (TemporalMesh) this.loadLibrary(meshStructure);
|
||||
}
|
||||
|
||||
String name = meshStructure.getName();
|
||||
LOGGER.log(Level.FINE, "Reading mesh: {0}.", name);
|
||||
temporalMesh = new TemporalMesh(meshStructure, blenderContext);
|
||||
|
||||
LOGGER.fine("Loading materials.");
|
||||
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
|
||||
temporalMesh.setMaterials(materialHelper.getMaterials(meshStructure, blenderContext));
|
||||
|
||||
LOGGER.fine("Reading custom properties.");
|
||||
Properties properties = this.loadProperties(meshStructure, blenderContext);
|
||||
temporalMesh.setProperties(properties);
|
||||
|
||||
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, meshStructure);
|
||||
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH, temporalMesh);
|
||||
return temporalMesh.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given mesh structure supports BMesh.
|
||||
*
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @return <b>true</b> if BMesh is supported and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isBMeshCompatible(Structure meshStructure) {
|
||||
Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop");
|
||||
Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly");
|
||||
return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the vertices: a list of vertex positions and a list
|
||||
* of vertex normals.
|
||||
*
|
||||
* @param meshStructure
|
||||
* the structure containing the mesh data
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blend file structure is somehow invalid or corrupted
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadVerticesAndNormals(Structure meshStructure, List<Vector3f> vertices, List<Vector3f> normals) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading vertices and normals from mesh: {0}.", meshStructure.getName());
|
||||
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
|
||||
if (count > 0) {
|
||||
Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert");
|
||||
List<Structure> mVerts = pMVert.fetchData();
|
||||
Vector3f co = null, no = null;
|
||||
if (fixUpAxis) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
|
||||
co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue());
|
||||
vertices.add(co);
|
||||
|
||||
DynamicArray<Number> norm = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
|
||||
no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f, -norm.get(1).shortValue() / 32767.0f);
|
||||
normals.add(no);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
DynamicArray<Number> coordinates = (DynamicArray<Number>) mVerts.get(i).getFieldValue("co");
|
||||
co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue());
|
||||
vertices.add(co);
|
||||
|
||||
DynamicArray<Number> norm = (DynamicArray<Number>) mVerts.get(i).getFieldValue("no");
|
||||
no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(1).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f);
|
||||
normals.add(no);
|
||||
}
|
||||
}
|
||||
}
|
||||
LOGGER.log(Level.FINE, "Loaded {0} vertices and normals.", vertices.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the vertices colors. Each vertex is stored in byte[4] array.
|
||||
*
|
||||
* @param meshStructure
|
||||
* the structure containing the mesh data
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return a list of vertices colors, each color belongs to a single vertex or empty list of colors are not specified
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blend file structure is somehow invalid or corrupted
|
||||
*/
|
||||
public List<byte[]> loadVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading vertices colors from mesh: {0}.", meshStructure.getName());
|
||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
|
||||
Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol");
|
||||
List<byte[]> verticesColors = new ArrayList<byte[]>();
|
||||
// it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure)
|
||||
// so we need to put them right
|
||||
boolean useBGRA = blenderContext.getBlenderVersion() < 263;
|
||||
if (pMCol.isNotNull()) {
|
||||
List<Structure> mCol = pMCol.fetchData();
|
||||
for (Structure color : mCol) {
|
||||
byte r = ((Number) color.getFieldValue("r")).byteValue();
|
||||
byte g = ((Number) color.getFieldValue("g")).byteValue();
|
||||
byte b = ((Number) color.getFieldValue("b")).byteValue();
|
||||
byte a = ((Number) color.getFieldValue("a")).byteValue();
|
||||
verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a });
|
||||
}
|
||||
}
|
||||
return verticesColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates.
|
||||
* But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning.
|
||||
* For bmesh they are enlisted just like they are stored in the blend file (in loops).
|
||||
* For traditional faces every 4 UV's should be assigned for a single face.
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @return a map that sorts UV coordinates between different UV sets
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with blend file occur
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public LinkedHashMap<String, List<Vector2f>> loadUVCoordinates(Structure meshStructure) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading UV coordinates from mesh: {0}.", meshStructure.getName());
|
||||
LinkedHashMap<String, List<Vector2f>> result = new LinkedHashMap<String, List<Vector2f>>();
|
||||
if (this.isBMeshCompatible(meshStructure)) {
|
||||
// in this case the UV's are assigned to vertices (an array is the same length as the vertex array)
|
||||
Structure loopData = (Structure) meshStructure.getFieldValue("ldata");
|
||||
Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers");
|
||||
List<Structure> loopDataLayers = pLoopDataLayers.fetchData();
|
||||
for (Structure structure : loopDataLayers) {
|
||||
Pointer p = (Pointer) structure.getFieldValue("data");
|
||||
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) {
|
||||
String uvSetName = structure.getFieldValue("name").toString();
|
||||
List<Structure> uvsStructures = p.fetchData();
|
||||
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
|
||||
for (Structure uvStructure : uvsStructures) {
|
||||
DynamicArray<Number> loopUVS = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
|
||||
uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue()));
|
||||
}
|
||||
result.put(uvSetName, uvs);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in this case UV's are assigned to faces (the array has the same length as the faces count)
|
||||
Structure facesData = (Structure) meshStructure.getFieldValue("fdata");
|
||||
Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers");
|
||||
if (pFacesDataLayers.isNotNull()) {
|
||||
List<Structure> facesDataLayers = pFacesDataLayers.fetchData();
|
||||
for (Structure structure : facesDataLayers) {
|
||||
Pointer p = (Pointer) structure.getFieldValue("data");
|
||||
if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) {
|
||||
String uvSetName = structure.getFieldValue("name").toString();
|
||||
List<Structure> uvsStructures = p.fetchData();
|
||||
List<Vector2f> uvs = new ArrayList<Vector2f>(uvsStructures.size());
|
||||
for (Structure uvStructure : uvsStructures) {
|
||||
DynamicArray<Number> mFaceUVs = (DynamicArray<Number>) uvStructure.getFieldValue("uv");
|
||||
uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue()));
|
||||
uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue()));
|
||||
uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue()));
|
||||
uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue()));
|
||||
}
|
||||
result.put(uvSetName, uvs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all vertices groups.
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @return a list of vertex groups for every vertex in the mesh
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with blend file occur
|
||||
*/
|
||||
public List<Map<String, Float>> loadVerticesGroups(Structure meshStructure) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading vertices groups from mesh: {0}.", meshStructure.getName());
|
||||
List<Map<String, Float>> result = new ArrayList<Map<String, Float>>();
|
||||
|
||||
Structure parent = blenderContext.peekParent();
|
||||
if(parent != null) {
|
||||
// the mesh might be saved without its parent (it is then unused)
|
||||
Structure defbase = (Structure) parent.getFieldValue("defbase");
|
||||
List<String> groupNames = new ArrayList<String>();
|
||||
List<Structure> defs = defbase.evaluateListBase();
|
||||
|
||||
if(!defs.isEmpty()) {
|
||||
for (Structure def : defs) {
|
||||
groupNames.add(def.getFieldValue("name").toString());
|
||||
}
|
||||
|
||||
Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
|
||||
if (pDvert.isNotNull()) {// assigning weights and bone indices
|
||||
List<Structure> dverts = pDvert.fetchData();
|
||||
for (Structure dvert : dverts) {
|
||||
Map<String, Float> weightsForVertex = new HashMap<String, Float>();
|
||||
Pointer pDW = (Pointer) dvert.getFieldValue("dw");
|
||||
if (pDW.isNotNull()) {
|
||||
List<Structure> dw = pDW.fetchData();
|
||||
for (Structure deformWeight : dw) {
|
||||
int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue();
|
||||
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
|
||||
String groupName = groupNames.get(groupIndex);
|
||||
|
||||
weightsForVertex.put(groupName, weight);
|
||||
}
|
||||
}
|
||||
result.add(weightsForVertex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the proper subsets of UV coordinates for the given sublist of indexes.
|
||||
* @param face
|
||||
* the face with the original UV sets
|
||||
* @param indexesSublist
|
||||
* the sub list of indexes
|
||||
* @return a map of UV coordinates subsets
|
||||
*/
|
||||
public Map<String, List<Vector2f>> selectUVSubset(Face face, Integer... indexesSublist) {
|
||||
Map<String, List<Vector2f>> result = null;
|
||||
if (face.getUvSets() != null) {
|
||||
result = new HashMap<String, List<Vector2f>>();
|
||||
for (Entry<String, List<Vector2f>> entry : face.getUvSets().entrySet()) {
|
||||
List<Vector2f> uvs = new ArrayList<Vector2f>(indexesSublist.length);
|
||||
for (Integer index : indexesSublist) {
|
||||
uvs.add(entry.getValue().get(face.getIndexes().indexOf(index)));
|
||||
}
|
||||
result.put(entry.getKey(), uvs);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the proper subsets of vertex colors for the given sublist of indexes.
|
||||
* @param face
|
||||
* the face with the original vertex colors
|
||||
* @param indexesSublist
|
||||
* the sub list of indexes
|
||||
* @return a sublist of vertex colors
|
||||
*/
|
||||
public List<byte[]> selectVertexColorSubset(Face face, Integer... indexesSublist) {
|
||||
List<byte[]> result = null;
|
||||
List<byte[]> vertexColors = face.getVertexColors();
|
||||
if (vertexColors != null) {
|
||||
result = new ArrayList<byte[]>(indexesSublist.length);
|
||||
for (Integer index : indexesSublist) {
|
||||
result.add(vertexColors.get(face.getIndexes().indexOf(index)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the black unshaded material. It is used for lines and points because that is how blender
|
||||
* renders it.
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @return black unshaded material
|
||||
*/
|
||||
public synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) {
|
||||
if (blackUnshadedMaterial == null) {
|
||||
blackUnshadedMaterial = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
blackUnshadedMaterial.setColor("Color", ColorRGBA.Black);
|
||||
}
|
||||
return blackUnshadedMaterial;
|
||||
}
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.meshes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
|
||||
|
||||
/**
|
||||
* A class that represents a single point on the scene that is not a part of an edge.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class Point {
|
||||
private static final Logger LOGGER = Logger.getLogger(Point.class.getName());
|
||||
|
||||
/** The point's index. */
|
||||
private int index;
|
||||
|
||||
/**
|
||||
* Constructs a point for a given index.
|
||||
* @param index
|
||||
* the index of the point
|
||||
*/
|
||||
public Point(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point clone() {
|
||||
return new Point(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the index of the point
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method shifts the index by a given value.
|
||||
* @param shift
|
||||
* the value to shift the index
|
||||
* @param predicate
|
||||
* the predicate that verifies which indexes should be shifted; if null then all will be shifted
|
||||
*/
|
||||
public void shiftIndexes(int shift, IndexPredicate predicate) {
|
||||
if (predicate == null || predicate.execute(index)) {
|
||||
index += shift;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all points of the mesh that do not belong to any edge.
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @return a list of points
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with file reading occur
|
||||
*/
|
||||
public static List<Point> loadAll(Structure meshStructure) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading all points that do not belong to any edge from mesh: {0}", meshStructure.getName());
|
||||
List<Point> result = new ArrayList<Point>();
|
||||
|
||||
Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge");
|
||||
if (pMEdge.isNotNull()) {
|
||||
int count = ((Number) meshStructure.getFieldValue("totvert")).intValue();
|
||||
Set<Integer> unusedVertices = new HashSet<Integer>(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
unusedVertices.add(i);
|
||||
}
|
||||
|
||||
List<Structure> edges = pMEdge.fetchData();
|
||||
for (Structure edge : edges) {
|
||||
unusedVertices.remove(((Number) edge.getFieldValue("v1")).intValue());
|
||||
unusedVertices.remove(((Number) edge.getFieldValue("v2")).intValue());
|
||||
}
|
||||
|
||||
for (Integer unusedIndex : unusedVertices) {
|
||||
result.add(new Point(unusedIndex));
|
||||
}
|
||||
}
|
||||
LOGGER.log(Level.FINE, "Loaded {0} points.", result.size());
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,767 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.meshes;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingVolume;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState.FaceCullMode;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Mesh.Mode;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.materials.MaterialContext;
|
||||
import com.jme3.scene.plugins.blender.meshes.Face.TriangulationWarning;
|
||||
import com.jme3.scene.plugins.blender.meshes.MeshBuffers.BoneBuffersData;
|
||||
import com.jme3.scene.plugins.blender.modifiers.Modifier;
|
||||
import com.jme3.scene.plugins.blender.objects.Properties;
|
||||
|
||||
/**
|
||||
* The class extends Geometry so that it can be temporalily added to the object's node.
|
||||
* Later each such node's child will be transformed into a list of geometries.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class TemporalMesh extends Geometry {
|
||||
private static final Logger LOGGER = Logger.getLogger(TemporalMesh.class.getName());
|
||||
/** A minimum weight value. */
|
||||
private static final double MINIMUM_BONE_WEIGHT = FastMath.DBL_EPSILON;
|
||||
|
||||
/** The blender context. */
|
||||
protected final BlenderContext blenderContext;
|
||||
|
||||
/** The mesh's structure. */
|
||||
protected final Structure meshStructure;
|
||||
|
||||
/** Loaded vertices. */
|
||||
protected List<Vector3f> vertices = new ArrayList<Vector3f>();
|
||||
/** Loaded normals. */
|
||||
protected List<Vector3f> normals = new ArrayList<Vector3f>();
|
||||
/** Loaded vertex groups. */
|
||||
protected List<Map<String, Float>> vertexGroups = new ArrayList<Map<String, Float>>();
|
||||
/** Loaded vertex colors. */
|
||||
protected List<byte[]> verticesColors = new ArrayList<byte[]>();
|
||||
|
||||
/** Materials used by the mesh. */
|
||||
protected MaterialContext[] materials;
|
||||
/** The properties of the mesh. */
|
||||
protected Properties properties;
|
||||
/** The bone indexes. */
|
||||
protected Map<String, Integer> boneIndexes = new HashMap<String, Integer>();
|
||||
/** The modifiers that should be applied after the mesh has been created. */
|
||||
protected List<Modifier> postMeshCreationModifiers = new ArrayList<Modifier>();
|
||||
|
||||
/** The faces of the mesh. */
|
||||
protected List<Face> faces = new ArrayList<Face>();
|
||||
/** The edges of the mesh. */
|
||||
protected List<Edge> edges = new ArrayList<Edge>();
|
||||
/** The points of the mesh. */
|
||||
protected List<Point> points = new ArrayList<Point>();
|
||||
/** A map between index and faces that contain it (for faster index - face queries). */
|
||||
protected Map<Integer, Set<Face>> indexToFaceMapping = new HashMap<Integer, Set<Face>>();
|
||||
/** A map between index and edges that contain it (for faster index - edge queries). */
|
||||
protected Map<Integer, Set<Edge>> indexToEdgeMapping = new HashMap<Integer, Set<Edge>>();
|
||||
|
||||
/** The bounding box of the temporal mesh. */
|
||||
protected BoundingBox boundingBox;
|
||||
|
||||
/**
|
||||
* Creates a temporal mesh based on the given mesh structure.
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with file reading occur
|
||||
*/
|
||||
public TemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
this(meshStructure, blenderContext, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporal mesh based on the given mesh structure.
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @param loadData
|
||||
* tells if the data should be loaded from the mesh structure
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with file reading occur
|
||||
*/
|
||||
protected TemporalMesh(Structure meshStructure, BlenderContext blenderContext, boolean loadData) throws BlenderFileException {
|
||||
this.blenderContext = blenderContext;
|
||||
this.meshStructure = meshStructure;
|
||||
|
||||
if (loadData) {
|
||||
name = meshStructure.getName();
|
||||
|
||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
|
||||
|
||||
meshHelper.loadVerticesAndNormals(meshStructure, vertices, normals);
|
||||
verticesColors = meshHelper.loadVerticesColors(meshStructure, blenderContext);
|
||||
LinkedHashMap<String, List<Vector2f>> userUVGroups = meshHelper.loadUVCoordinates(meshStructure);
|
||||
vertexGroups = meshHelper.loadVerticesGroups(meshStructure);
|
||||
|
||||
faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext);
|
||||
edges = Edge.loadAll(meshStructure, this);
|
||||
points = Point.loadAll(meshStructure);
|
||||
|
||||
this.rebuildIndexesMappings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the blender context
|
||||
*/
|
||||
public BlenderContext getBlenderContext() {
|
||||
return blenderContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vertices of the mesh
|
||||
*/
|
||||
public List<Vector3f> getVertices() {
|
||||
return vertices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the normals of the mesh
|
||||
*/
|
||||
public List<Vector3f> getNormals() {
|
||||
return normals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all faces
|
||||
*/
|
||||
public List<Face> getFaces() {
|
||||
return faces;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all edges
|
||||
*/
|
||||
public List<Edge> getEdges() {
|
||||
return edges;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all points (do not mistake it with vertices)
|
||||
*/
|
||||
public List<Point> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all vertices colors
|
||||
*/
|
||||
public List<byte[]> getVerticesColors() {
|
||||
return verticesColors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all vertex groups for the vertices (each map has groups for the proper vertex)
|
||||
*/
|
||||
public List<Map<String, Float>> getVertexGroups() {
|
||||
return vertexGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the faces that contain the given index or null if none contain it
|
||||
*/
|
||||
public Collection<Face> getAdjacentFaces(Integer index) {
|
||||
return indexToFaceMapping.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param edge the edge of the mesh
|
||||
* @return a list of faces that contain the given edge or an empty list
|
||||
*/
|
||||
public Collection<Face> getAdjacentFaces(Edge edge) {
|
||||
List<Face> result = new ArrayList<Face>(indexToFaceMapping.get(edge.getFirstIndex()));
|
||||
Set<Face> secondIndexAdjacentFaces = indexToFaceMapping.get(edge.getSecondIndex());
|
||||
if (secondIndexAdjacentFaces != null) {
|
||||
result.retainAll(indexToFaceMapping.get(edge.getSecondIndex()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index the index of the mesh
|
||||
* @return a list of edges that contain the index
|
||||
*/
|
||||
public Collection<Edge> getAdjacentEdges(Integer index) {
|
||||
return indexToEdgeMapping.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given edge is a boundary edge. The boundary edge means that it belongs to a single
|
||||
* face or to none.
|
||||
* @param edge the edge of the mesh
|
||||
* @return <b>true</b> if the edge is a boundary one and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isBoundary(Edge edge) {
|
||||
return this.getAdjacentFaces(edge).size() <= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method tells if the given index is a boundary index. A boundary index belongs to at least
|
||||
* one boundary edge.
|
||||
* @param index
|
||||
* the index of the mesh
|
||||
* @return <b>true</b> if the index is a boundary one and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isBoundary(Integer index) {
|
||||
Collection<Edge> adjacentEdges = this.getAdjacentEdges(index);
|
||||
for (Edge edge : adjacentEdges) {
|
||||
if (this.isBoundary(edge)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporalMesh clone() {
|
||||
try {
|
||||
TemporalMesh result = new TemporalMesh(meshStructure, blenderContext, false);
|
||||
result.name = name;
|
||||
for (Vector3f v : vertices) {
|
||||
result.vertices.add(v.clone());
|
||||
}
|
||||
for (Vector3f n : normals) {
|
||||
result.normals.add(n.clone());
|
||||
}
|
||||
for (Map<String, Float> group : vertexGroups) {
|
||||
result.vertexGroups.add(new HashMap<String, Float>(group));
|
||||
}
|
||||
for (byte[] vertColor : verticesColors) {
|
||||
result.verticesColors.add(vertColor.clone());
|
||||
}
|
||||
result.materials = materials;
|
||||
result.properties = properties;
|
||||
result.boneIndexes.putAll(boneIndexes);
|
||||
result.postMeshCreationModifiers.addAll(postMeshCreationModifiers);
|
||||
for (Face face : faces) {
|
||||
result.faces.add(face.clone());
|
||||
}
|
||||
for (Edge edge : edges) {
|
||||
result.edges.add(edge.clone());
|
||||
}
|
||||
for (Point point : points) {
|
||||
result.points.add(point.clone());
|
||||
}
|
||||
result.rebuildIndexesMappings();
|
||||
return result;
|
||||
} catch (BlenderFileException e) {
|
||||
LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method rebuilds the mappings between faces and edges. Should be called after
|
||||
* every major change of the temporal mesh done outside it.
|
||||
* <p>
|
||||
* Note: I will remove this method soon and cause the mappings to be done
|
||||
* automatically when the mesh is modified.
|
||||
*/
|
||||
public void rebuildIndexesMappings() {
|
||||
indexToEdgeMapping.clear();
|
||||
indexToFaceMapping.clear();
|
||||
for (Face face : faces) {
|
||||
for (Integer index : face.getIndexes()) {
|
||||
Set<Face> faces = indexToFaceMapping.get(index);
|
||||
if (faces == null) {
|
||||
faces = new HashSet<Face>();
|
||||
indexToFaceMapping.put(index, faces);
|
||||
}
|
||||
faces.add(face);
|
||||
}
|
||||
}
|
||||
for (Edge edge : edges) {
|
||||
Set<Edge> edges = indexToEdgeMapping.get(edge.getFirstIndex());
|
||||
if (edges == null) {
|
||||
edges = new HashSet<Edge>();
|
||||
indexToEdgeMapping.put(edge.getFirstIndex(), edges);
|
||||
}
|
||||
edges.add(edge);
|
||||
edges = indexToEdgeMapping.get(edge.getSecondIndex());
|
||||
if (edges == null) {
|
||||
edges = new HashSet<Edge>();
|
||||
indexToEdgeMapping.put(edge.getSecondIndex(), edges);
|
||||
}
|
||||
edges.add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateModelBound() {
|
||||
if (boundingBox == null) {
|
||||
boundingBox = new BoundingBox();
|
||||
}
|
||||
Vector3f min = new Vector3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);
|
||||
Vector3f max = new Vector3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE);
|
||||
for (Vector3f v : vertices) {
|
||||
min.set(Math.min(min.x, v.x), Math.min(min.y, v.y), Math.min(min.z, v.z));
|
||||
max.set(Math.max(max.x, v.x), Math.max(max.y, v.y), Math.max(max.z, v.z));
|
||||
}
|
||||
boundingBox.setMinMax(min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundingVolume getModelBound() {
|
||||
this.updateModelBound();
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BoundingVolume getWorldBound() {
|
||||
this.updateModelBound();
|
||||
Node parent = this.getParent();
|
||||
if (parent != null) {
|
||||
BoundingVolume bv = boundingBox.clone();
|
||||
bv.setCenter(parent.getWorldTranslation());
|
||||
return bv;
|
||||
} else {
|
||||
return boundingBox;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triangulates the mesh.
|
||||
*/
|
||||
public void triangulate() {
|
||||
Set<TriangulationWarning> warnings = new HashSet<>(TriangulationWarning.values().length - 1);
|
||||
LOGGER.fine("Triangulating temporal mesh.");
|
||||
for (Face face : faces) {
|
||||
TriangulationWarning warning = face.triangulate();
|
||||
if(warning != TriangulationWarning.NONE) {
|
||||
warnings.add(warning);
|
||||
}
|
||||
}
|
||||
|
||||
if(warnings.size() > 0 && LOGGER.isLoggable(Level.WARNING)) {
|
||||
StringBuilder sb = new StringBuilder(512);
|
||||
sb.append("There were problems with triangulating the faces of a mesh: ").append(name);
|
||||
for(TriangulationWarning w : warnings) {
|
||||
sb.append("\n\t").append(w);
|
||||
}
|
||||
LOGGER.warning(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method appends the given mesh to the current one. New faces and vertices and indexes are added.
|
||||
* @param mesh
|
||||
* the mesh to be appended
|
||||
*/
|
||||
public void append(TemporalMesh mesh) {
|
||||
if (mesh != null) {
|
||||
// we need to shift the indexes in faces, lines and points
|
||||
int shift = vertices.size();
|
||||
if (shift > 0) {
|
||||
for (Face face : mesh.faces) {
|
||||
face.getIndexes().shiftIndexes(shift, null);
|
||||
face.setTemporalMesh(this);
|
||||
}
|
||||
for (Edge edge : mesh.edges) {
|
||||
edge.shiftIndexes(shift, null);
|
||||
}
|
||||
for (Point point : mesh.points) {
|
||||
point.shiftIndexes(shift, null);
|
||||
}
|
||||
}
|
||||
|
||||
faces.addAll(mesh.faces);
|
||||
edges.addAll(mesh.edges);
|
||||
points.addAll(mesh.points);
|
||||
|
||||
vertices.addAll(mesh.vertices);
|
||||
normals.addAll(mesh.normals);
|
||||
vertexGroups.addAll(mesh.vertexGroups);
|
||||
verticesColors.addAll(mesh.verticesColors);
|
||||
boneIndexes.putAll(mesh.boneIndexes);
|
||||
|
||||
this.rebuildIndexesMappings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the properties of the mesh.
|
||||
* @param properties
|
||||
* the properties of the mesh
|
||||
*/
|
||||
public void setProperties(Properties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the materials of the mesh.
|
||||
* @param materials
|
||||
* the materials of the mesh
|
||||
*/
|
||||
public void setMaterials(MaterialContext[] materials) {
|
||||
this.materials = materials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds bone index to the mesh.
|
||||
* @param boneName
|
||||
* the name of the bone
|
||||
* @param boneIndex
|
||||
* the index of the bone
|
||||
*/
|
||||
public void addBoneIndex(String boneName, Integer boneIndex) {
|
||||
boneIndexes.put(boneName, boneIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* The modifier to be applied after the geometries are created.
|
||||
* @param modifier
|
||||
* the modifier to be applied
|
||||
*/
|
||||
public void applyAfterMeshCreate(Modifier modifier) {
|
||||
postMeshCreationModifiers.add(modifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVertexCount() {
|
||||
return vertices.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all vertices from the mesh.
|
||||
*/
|
||||
public void clear() {
|
||||
vertices.clear();
|
||||
normals.clear();
|
||||
vertexGroups.clear();
|
||||
verticesColors.clear();
|
||||
faces.clear();
|
||||
edges.clear();
|
||||
points.clear();
|
||||
indexToEdgeMapping.clear();
|
||||
indexToFaceMapping.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* The mesh builds geometries from the mesh. The result is stored in the blender context
|
||||
* under the mesh's OMA.
|
||||
*/
|
||||
public void toGeometries() {
|
||||
LOGGER.log(Level.FINE, "Converting temporal mesh {0} to jme geometries.", name);
|
||||
List<Geometry> result = new ArrayList<Geometry>();
|
||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
|
||||
Node parent = this.getParent();
|
||||
parent.detachChild(this);
|
||||
|
||||
this.prepareFacesGeometry(result, meshHelper);
|
||||
this.prepareLinesGeometry(result, meshHelper);
|
||||
this.preparePointsGeometry(result, meshHelper);
|
||||
|
||||
blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
|
||||
|
||||
for (Geometry geometry : result) {
|
||||
parent.attachChild(geometry);
|
||||
}
|
||||
|
||||
for (Modifier modifier : postMeshCreationModifiers) {
|
||||
modifier.postMeshCreationApply(parent, blenderContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method creates geometries from faces.
|
||||
* @param result
|
||||
* the list where new geometries will be appended
|
||||
* @param meshHelper
|
||||
* the mesh helper
|
||||
*/
|
||||
protected void prepareFacesGeometry(List<Geometry> result, MeshHelper meshHelper) {
|
||||
LOGGER.fine("Preparing faces geometries.");
|
||||
this.triangulate();
|
||||
|
||||
Vector3f[] tempVerts = new Vector3f[3];
|
||||
Vector3f[] tempNormals = new Vector3f[3];
|
||||
byte[][] tempVertColors = new byte[3][];
|
||||
List<Map<Float, Integer>> boneBuffers = new ArrayList<Map<Float, Integer>>(3);
|
||||
|
||||
LOGGER.log(Level.FINE, "Appending {0} faces to mesh buffers.", faces.size());
|
||||
Map<Integer, MeshBuffers> faceMeshes = new HashMap<Integer, MeshBuffers>();
|
||||
for (Face face : faces) {
|
||||
MeshBuffers meshBuffers = faceMeshes.get(face.getMaterialNumber());
|
||||
if (meshBuffers == null) {
|
||||
meshBuffers = new MeshBuffers(face.getMaterialNumber());
|
||||
faceMeshes.put(face.getMaterialNumber(), meshBuffers);
|
||||
}
|
||||
|
||||
List<List<Integer>> triangulatedIndexes = face.getCurrentIndexes();
|
||||
List<byte[]> vertexColors = face.getVertexColors();
|
||||
|
||||
for (List<Integer> indexes : triangulatedIndexes) {
|
||||
assert indexes.size() == 3 : "The mesh has not been properly triangulated!";
|
||||
|
||||
Vector3f normal = null;
|
||||
if(!face.isSmooth()) {
|
||||
normal = FastMath.computeNormal(vertices.get(indexes.get(0)), vertices.get(indexes.get(1)), vertices.get(indexes.get(2)));
|
||||
}
|
||||
|
||||
boneBuffers.clear();
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int vertIndex = indexes.get(i);
|
||||
tempVerts[i] = vertices.get(vertIndex);
|
||||
tempNormals[i] = normal != null ? normal : normals.get(vertIndex);
|
||||
tempVertColors[i] = vertexColors != null ? vertexColors.get(face.getIndexes().indexOf(vertIndex)) : null;
|
||||
|
||||
if (boneIndexes.size() > 0 && vertexGroups.size() > 0) {
|
||||
Map<Float, Integer> boneBuffersForVertex = new HashMap<Float, Integer>();
|
||||
Map<String, Float> vertexGroupsForVertex = vertexGroups.get(vertIndex);
|
||||
for (Entry<String, Integer> entry : boneIndexes.entrySet()) {
|
||||
if (vertexGroupsForVertex.containsKey(entry.getKey())) {
|
||||
float weight = vertexGroupsForVertex.get(entry.getKey());
|
||||
if (weight > MINIMUM_BONE_WEIGHT) {
|
||||
// only values of weight greater than MINIMUM_BONE_WEIGHT are used
|
||||
// if all non zero weights were used, and they were samm enough, problems with normalisation would occur
|
||||
// because adding a very small value to 1.0 will give 1.0
|
||||
// so in order to avoid such errors, which can cause severe animation artifacts we need to use some minimum weight value
|
||||
boneBuffersForVertex.put(weight, entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boneBuffersForVertex.size() == 0) {// attach the vertex to zero-indexed bone so that it does not collapse to (0, 0, 0)
|
||||
boneBuffersForVertex.put(1.0f, 0);
|
||||
}
|
||||
boneBuffers.add(boneBuffersForVertex);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, List<Vector2f>> uvs = meshHelper.selectUVSubset(face, indexes.toArray(new Integer[indexes.size()]));
|
||||
meshBuffers.append(face.isSmooth(), tempVerts, tempNormals, uvs, tempVertColors, boneBuffers);
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.fine("Converting mesh buffers to geometries.");
|
||||
Map<Geometry, MeshBuffers> geometryToBuffersMap = new HashMap<Geometry, MeshBuffers>();
|
||||
for (Entry<Integer, MeshBuffers> entry : faceMeshes.entrySet()) {
|
||||
MeshBuffers meshBuffers = entry.getValue();
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
|
||||
if (meshBuffers.isShortIndexBuffer()) {
|
||||
mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer());
|
||||
} else {
|
||||
mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer());
|
||||
}
|
||||
mesh.setBuffer(meshBuffers.getPositionsBuffer());
|
||||
mesh.setBuffer(meshBuffers.getNormalsBuffer());
|
||||
if (meshBuffers.areVertexColorsUsed()) {
|
||||
mesh.setBuffer(Type.Color, 4, meshBuffers.getVertexColorsBuffer());
|
||||
mesh.getBuffer(Type.Color).setNormalized(true);
|
||||
}
|
||||
|
||||
BoneBuffersData boneBuffersData = meshBuffers.getBoneBuffers();
|
||||
if (boneBuffersData != null) {
|
||||
mesh.setMaxNumWeights(boneBuffersData.maximumWeightsPerVertex);
|
||||
mesh.setBuffer(boneBuffersData.verticesWeights);
|
||||
mesh.setBuffer(boneBuffersData.verticesWeightsIndices);
|
||||
|
||||
LOGGER.fine("Generating bind pose and normal buffers.");
|
||||
mesh.generateBindPose(true);
|
||||
|
||||
// change the usage type of vertex and normal buffers from Static to Stream
|
||||
mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
|
||||
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
|
||||
|
||||
// creating empty buffers for HW skinning; the buffers will be setup if ever used
|
||||
VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
|
||||
VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
|
||||
mesh.setBuffer(verticesWeightsHW);
|
||||
mesh.setBuffer(verticesWeightsIndicesHW);
|
||||
}
|
||||
|
||||
Geometry geometry = new Geometry(name + (result.size() + 1), mesh);
|
||||
if (properties != null && properties.getValue() != null) {
|
||||
meshHelper.applyProperties(geometry, properties);
|
||||
}
|
||||
result.add(geometry);
|
||||
|
||||
geometryToBuffersMap.put(geometry, meshBuffers);
|
||||
}
|
||||
|
||||
LOGGER.fine("Applying materials to geometries.");
|
||||
for (Entry<Geometry, MeshBuffers> entry : geometryToBuffersMap.entrySet()) {
|
||||
int materialIndex = entry.getValue().getMaterialIndex();
|
||||
Geometry geometry = entry.getKey();
|
||||
if (materialIndex >= 0 && materials != null && materials.length > materialIndex && materials[materialIndex] != null) {
|
||||
materials[materialIndex].applyMaterial(geometry, meshStructure.getOldMemoryAddress(), entry.getValue().getUvCoords(), blenderContext);
|
||||
} else {
|
||||
Material defaultMaterial = blenderContext.getDefaultMaterial().clone();
|
||||
defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
|
||||
geometry.setMaterial(defaultMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method creates geometries from lines.
|
||||
* @param result
|
||||
* the list where new geometries will be appended
|
||||
* @param meshHelper
|
||||
* the mesh helper
|
||||
*/
|
||||
protected void prepareLinesGeometry(List<Geometry> result, MeshHelper meshHelper) {
|
||||
if (edges.size() > 0) {
|
||||
LOGGER.fine("Preparing lines geometries.");
|
||||
|
||||
List<List<Integer>> separateEdges = new ArrayList<List<Integer>>();
|
||||
List<Edge> edges = new ArrayList<Edge>(this.edges.size());
|
||||
for (Edge edge : this.edges) {
|
||||
if (!edge.isInFace()) {
|
||||
edges.add(edge);
|
||||
}
|
||||
}
|
||||
while (edges.size() > 0) {
|
||||
boolean edgeAppended = false;
|
||||
int edgeIndex = 0;
|
||||
for (List<Integer> list : separateEdges) {
|
||||
for (edgeIndex = 0; edgeIndex < edges.size() && !edgeAppended; ++edgeIndex) {
|
||||
Edge edge = edges.get(edgeIndex);
|
||||
if (list.get(0).equals(edge.getFirstIndex())) {
|
||||
list.add(0, edge.getSecondIndex());
|
||||
--edgeIndex;
|
||||
edgeAppended = true;
|
||||
} else if (list.get(0).equals(edge.getSecondIndex())) {
|
||||
list.add(0, edge.getFirstIndex());
|
||||
--edgeIndex;
|
||||
edgeAppended = true;
|
||||
} else if (list.get(list.size() - 1).equals(edge.getFirstIndex())) {
|
||||
list.add(edge.getSecondIndex());
|
||||
--edgeIndex;
|
||||
edgeAppended = true;
|
||||
} else if (list.get(list.size() - 1).equals(edge.getSecondIndex())) {
|
||||
list.add(edge.getFirstIndex());
|
||||
--edgeIndex;
|
||||
edgeAppended = true;
|
||||
}
|
||||
}
|
||||
if (edgeAppended) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Edge edge = edges.remove(edgeAppended ? edgeIndex : 0);
|
||||
if (!edgeAppended) {
|
||||
separateEdges.add(new ArrayList<Integer>(Arrays.asList(edge.getFirstIndex(), edge.getSecondIndex())));
|
||||
}
|
||||
}
|
||||
|
||||
for (List<Integer> list : separateEdges) {
|
||||
MeshBuffers meshBuffers = new MeshBuffers(0);
|
||||
for (int index : list) {
|
||||
meshBuffers.append(vertices.get(index), normals.get(index));
|
||||
}
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setLineWidth(blenderContext.getBlenderKey().getLinesWidth());
|
||||
mesh.setMode(Mode.LineStrip);
|
||||
if (meshBuffers.isShortIndexBuffer()) {
|
||||
mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer());
|
||||
} else {
|
||||
mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer());
|
||||
}
|
||||
mesh.setBuffer(meshBuffers.getPositionsBuffer());
|
||||
mesh.setBuffer(meshBuffers.getNormalsBuffer());
|
||||
|
||||
Geometry geometry = new Geometry(meshStructure.getName() + (result.size() + 1), mesh);
|
||||
geometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext));
|
||||
if (properties != null && properties.getValue() != null) {
|
||||
meshHelper.applyProperties(geometry, properties);
|
||||
}
|
||||
result.add(geometry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method creates geometries from points.
|
||||
* @param result
|
||||
* the list where new geometries will be appended
|
||||
* @param meshHelper
|
||||
* the mesh helper
|
||||
*/
|
||||
protected void preparePointsGeometry(List<Geometry> result, MeshHelper meshHelper) {
|
||||
if (points.size() > 0) {
|
||||
LOGGER.fine("Preparing point geometries.");
|
||||
|
||||
MeshBuffers pointBuffers = new MeshBuffers(0);
|
||||
for (Point point : points) {
|
||||
pointBuffers.append(vertices.get(point.getIndex()), normals.get(point.getIndex()));
|
||||
}
|
||||
Mesh pointsMesh = new Mesh();
|
||||
pointsMesh.setMode(Mode.Points);
|
||||
pointsMesh.setPointSize(blenderContext.getBlenderKey().getPointsSize());
|
||||
if (pointBuffers.isShortIndexBuffer()) {
|
||||
pointsMesh.setBuffer(Type.Index, 1, (ShortBuffer) pointBuffers.getIndexBuffer());
|
||||
} else {
|
||||
pointsMesh.setBuffer(Type.Index, 1, (IntBuffer) pointBuffers.getIndexBuffer());
|
||||
}
|
||||
pointsMesh.setBuffer(pointBuffers.getPositionsBuffer());
|
||||
pointsMesh.setBuffer(pointBuffers.getNormalsBuffer());
|
||||
|
||||
Geometry pointsGeometry = new Geometry(meshStructure.getName() + (result.size() + 1), pointsMesh);
|
||||
pointsGeometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext));
|
||||
if (properties != null && properties.getValue() != null) {
|
||||
meshHelper.applyProperties(pointsGeometry, properties);
|
||||
}
|
||||
result.add(pointsGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (meshStructure == null ? 0 : meshStructure.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof TemporalMesh)) {
|
||||
return false;
|
||||
}
|
||||
TemporalMesh other = (TemporalMesh) obj;
|
||||
return meshStructure.getOldMemoryAddress().equals(other.meshStructure.getOldMemoryAddress());
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.modifiers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.animation.Bone;
|
||||
import com.jme3.animation.Skeleton;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
|
||||
/**
|
||||
* This modifier allows to add bone animation to the object.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ArmatureModifier extends Modifier {
|
||||
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName());
|
||||
|
||||
private static final int FLAG_VERTEX_GROUPS = 0x01;
|
||||
private static final int FLAG_BONE_ENVELOPES = 0x02;
|
||||
|
||||
private Skeleton skeleton;
|
||||
|
||||
/**
|
||||
* This constructor reads animation data from the object structore. The
|
||||
* stored data is the AnimData and additional data is armature's OMA.
|
||||
*
|
||||
* @param objectStructure
|
||||
* the structure of the object
|
||||
* @param modifierStructure
|
||||
* the structure of the modifier
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blender file is somehow
|
||||
* corrupted
|
||||
*/
|
||||
public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
if (this.validate(modifierStructure, blenderContext)) {
|
||||
Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
|
||||
if (pArmatureObject.isNotNull()) {
|
||||
int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
|
||||
boolean useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
|
||||
boolean useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
|
||||
modifying = useBoneEnvelopes || useVertexGroups;
|
||||
if (modifying) {// if neither option is used the modifier will not modify anything anyway
|
||||
Structure armatureObject = pArmatureObject.fetchData().get(0);
|
||||
if(blenderContext.getSkeleton(armatureObject.getOldMemoryAddress()) == null) {
|
||||
LOGGER.fine("Creating new skeleton for armature modifier.");
|
||||
Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
|
||||
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
|
||||
List<Bone> bonesList = new ArrayList<Bone>();
|
||||
for (int i = 0; i < bonebase.size(); ++i) {
|
||||
this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext);
|
||||
}
|
||||
bonesList.add(0, new Bone(""));
|
||||
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
|
||||
skeleton = new Skeleton(bones);
|
||||
blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
|
||||
} else {
|
||||
skeleton = blenderContext.getSkeleton(armatureObject.getOldMemoryAddress());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
modifying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
|
||||
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
|
||||
bc.buildBone(result, spatialOMA, blenderContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
|
||||
LOGGER.fine("Applying armature modifier after mesh has been created.");
|
||||
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
|
||||
animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
|
||||
node.updateModelBound();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Node node, BlenderContext blenderContext) {
|
||||
if (invalid) {
|
||||
LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
|
||||
}
|
||||
|
||||
if (modifying) {
|
||||
TemporalMesh temporalMesh = this.getTemporalMesh(node);
|
||||
if (temporalMesh != null) {
|
||||
LOGGER.log(Level.FINE, "Applying armature modifier to: {0}", temporalMesh);
|
||||
|
||||
LOGGER.fine("Creating map between bone name and its index.");
|
||||
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
|
||||
Bone bone = skeleton.getBone(i);
|
||||
temporalMesh.addBoneIndex(bone.getName(), i);
|
||||
}
|
||||
temporalMesh.applyAfterMeshCreate(this);
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,241 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.modifiers;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.bounding.BoundingBox;
|
||||
import com.jme3.bounding.BoundingSphere;
|
||||
import com.jme3.bounding.BoundingVolume;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
import com.jme3.scene.shape.Curve;
|
||||
|
||||
/**
|
||||
* This modifier allows to array modifier to the object.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class ArrayModifier extends Modifier {
|
||||
private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName());
|
||||
|
||||
private int fittype;
|
||||
private int count;
|
||||
private float length;
|
||||
private float[] offset;
|
||||
private float[] scale;
|
||||
private Pointer pOffsetObject;
|
||||
private Pointer pStartCap;
|
||||
private Pointer pEndCap;
|
||||
|
||||
/**
|
||||
* This constructor reads array data from the modifier structure. The
|
||||
* stored data is a map of parameters for array modifier. No additional data
|
||||
* is loaded.
|
||||
*
|
||||
* @param objectStructure
|
||||
* the structure of the object
|
||||
* @param modifierStructure
|
||||
* the structure of the modifier
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blender file is somehow
|
||||
* corrupted
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
if (this.validate(modifierStructure, blenderContext)) {
|
||||
fittype = ((Number) modifierStructure.getFieldValue("fit_type")).intValue();
|
||||
switch (fittype) {
|
||||
case 0:// FIXED COUNT
|
||||
count = ((Number) modifierStructure.getFieldValue("count")).intValue();
|
||||
break;
|
||||
case 1:// FIXED LENGTH
|
||||
length = ((Number) modifierStructure.getFieldValue("length")).floatValue();
|
||||
break;
|
||||
case 2:// FITCURVE
|
||||
Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob");
|
||||
if (pCurveOb.isNotNull()) {
|
||||
Structure curveStructure = pCurveOb.fetchData().get(0);
|
||||
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
||||
Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext);
|
||||
Set<Number> referencesToCurveLengths = new HashSet<Number>(curveObject.getChildren().size());
|
||||
for (Spatial spatial : curveObject.getChildren()) {
|
||||
if (spatial instanceof Geometry) {
|
||||
Mesh mesh = ((Geometry) spatial).getMesh();
|
||||
if (mesh instanceof Curve) {
|
||||
length += ((Curve) mesh).getLength();
|
||||
} else {
|
||||
// if bevel object has several parts then each mesh will have the same reference
|
||||
// to length value (and we should use only one)
|
||||
Number curveLength = spatial.getUserData("curveLength");
|
||||
if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) {
|
||||
length += curveLength.floatValue();
|
||||
referencesToCurveLengths.add(curveLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fittype = 1;// treat it like FIXED LENGTH
|
||||
break;
|
||||
default:
|
||||
assert false : "Unknown array modifier fit type: " + fittype;
|
||||
}
|
||||
|
||||
// offset parameters
|
||||
int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue();
|
||||
if ((offsettype & 0x01) != 0) {// Constant offset
|
||||
DynamicArray<Number> offsetArray = (DynamicArray<Number>) modifierStructure.getFieldValue("offset");
|
||||
offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() };
|
||||
}
|
||||
if ((offsettype & 0x02) != 0) {// Relative offset
|
||||
DynamicArray<Number> scaleArray = (DynamicArray<Number>) modifierStructure.getFieldValue("scale");
|
||||
scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() };
|
||||
}
|
||||
if ((offsettype & 0x04) != 0) {// Object offset
|
||||
pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob");
|
||||
}
|
||||
|
||||
// start cap and end cap
|
||||
pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap");
|
||||
pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Node node, BlenderContext blenderContext) {
|
||||
if (invalid) {
|
||||
LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName());
|
||||
} else {
|
||||
TemporalMesh temporalMesh = this.getTemporalMesh(node);
|
||||
if (temporalMesh != null) {
|
||||
LOGGER.log(Level.FINE, "Applying array modifier to: {0}", temporalMesh);
|
||||
if (offset == null) {// the node will be repeated several times in the same place
|
||||
offset = new float[] { 0.0f, 0.0f, 0.0f };
|
||||
}
|
||||
if (scale == null) {// the node will be repeated several times in the same place
|
||||
scale = new float[] { 0.0f, 0.0f, 0.0f };
|
||||
} else {
|
||||
// getting bounding box
|
||||
temporalMesh.updateModelBound();
|
||||
BoundingVolume boundingVolume = temporalMesh.getWorldBound();
|
||||
if (boundingVolume instanceof BoundingBox) {
|
||||
scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f;
|
||||
scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f;
|
||||
scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f;
|
||||
} else if (boundingVolume instanceof BoundingSphere) {
|
||||
float radius = ((BoundingSphere) boundingVolume).getRadius();
|
||||
scale[0] *= radius * 2.0f;
|
||||
scale[1] *= radius * 2.0f;
|
||||
scale[2] *= radius * 2.0f;
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// adding object's offset
|
||||
float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f };
|
||||
if (pOffsetObject != null && pOffsetObject.isNotNull()) {
|
||||
FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress());
|
||||
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
||||
try {// we take the structure in case the object was not yet loaded
|
||||
Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext);
|
||||
Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation();
|
||||
objectOffset[0] = translation.x;
|
||||
objectOffset[1] = translation.y;
|
||||
objectOffset[2] = translation.z;
|
||||
} catch (BlenderFileException e) {
|
||||
LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// getting start and end caps
|
||||
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
|
||||
TemporalMesh[] caps = new TemporalMesh[] { null, null };
|
||||
Pointer[] pCaps = new Pointer[] { pStartCap, pEndCap };
|
||||
for (int i = 0; i < pCaps.length; ++i) {
|
||||
if (pCaps[i].isNotNull()) {
|
||||
FileBlockHeader capBlock = blenderContext.getFileBlock(pCaps[i].getOldMemoryAddress());
|
||||
try {// we take the structure in case the object was not yet loaded
|
||||
Structure capStructure = capBlock.getStructure(blenderContext);
|
||||
Pointer pMesh = (Pointer) capStructure.getFieldValue("data");
|
||||
List<Structure> meshesArray = pMesh.fetchData();
|
||||
caps[i] = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext);
|
||||
} catch (BlenderFileException e) {
|
||||
LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]);
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
float y = translationVector.y;
|
||||
translationVector.y = translationVector.z;
|
||||
translationVector.z = y == 0 ? 0 : -y;
|
||||
}
|
||||
|
||||
// getting/calculating repeats amount
|
||||
int count = 0;
|
||||
if (fittype == 0) {// Fixed count
|
||||
count = this.count - 1;
|
||||
} else if (fittype == 1) {// Fixed length
|
||||
float length = this.length;
|
||||
if (translationVector.length() > 0.0f) {
|
||||
count = (int) (length / translationVector.length()) - 1;
|
||||
}
|
||||
} else if (fittype == 2) {// Fit curve
|
||||
throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!");
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown fit type: " + fittype);
|
||||
}
|
||||
|
||||
// adding translated nodes and caps
|
||||
Vector3f totalTranslation = new Vector3f(translationVector);
|
||||
if (count > 0) {
|
||||
TemporalMesh originalMesh = temporalMesh.clone();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
TemporalMesh clone = originalMesh.clone();
|
||||
for (Vector3f v : clone.getVertices()) {
|
||||
v.addLocal(totalTranslation);
|
||||
}
|
||||
temporalMesh.append(clone);
|
||||
totalTranslation.addLocal(translationVector);
|
||||
}
|
||||
}
|
||||
if (caps[0] != null) {
|
||||
translationVector.multLocal(-1);
|
||||
TemporalMesh capsClone = caps[0].clone();
|
||||
for (Vector3f v : capsClone.getVertices()) {
|
||||
v.addLocal(translationVector);
|
||||
}
|
||||
temporalMesh.append(capsClone);
|
||||
}
|
||||
if (caps[1] != null) {
|
||||
TemporalMesh capsClone = caps[1].clone();
|
||||
for (Vector3f v : capsClone.getVertices()) {
|
||||
v.addLocal(totalTranslation);
|
||||
}
|
||||
temporalMesh.append(capsClone);
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,200 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.modifiers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.animations.BoneContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.meshes.Edge;
|
||||
import com.jme3.scene.plugins.blender.meshes.Face;
|
||||
import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
|
||||
import com.jme3.scene.plugins.blender.meshes.Point;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
|
||||
/**
|
||||
* This modifier allows to use mask modifier on the object.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class MaskModifier extends Modifier {
|
||||
private static final Logger LOGGER = Logger.getLogger(MaskModifier.class.getName());
|
||||
|
||||
private static final int FLAG_INVERT_MASK = 0x01;
|
||||
|
||||
private static final int MODE_VERTEX_GROUP = 0;
|
||||
private static final int MODE_ARMATURE = 1;
|
||||
|
||||
private Pointer pArmatureObject;
|
||||
private String vertexGroupName;
|
||||
private boolean invertMask;
|
||||
|
||||
public MaskModifier(Structure modifierStructure, BlenderContext blenderContext) {
|
||||
if (this.validate(modifierStructure, blenderContext)) {
|
||||
int flag = ((Number) modifierStructure.getFieldValue("flag")).intValue();
|
||||
invertMask = (flag & FLAG_INVERT_MASK) != 0;
|
||||
|
||||
int mode = ((Number) modifierStructure.getFieldValue("mode")).intValue();
|
||||
if (mode == MODE_VERTEX_GROUP) {
|
||||
vertexGroupName = modifierStructure.getFieldValue("vgroup").toString();
|
||||
if (vertexGroupName != null && vertexGroupName.length() == 0) {
|
||||
vertexGroupName = null;
|
||||
}
|
||||
} else if (mode == MODE_ARMATURE) {
|
||||
pArmatureObject = (Pointer) modifierStructure.getFieldValue("ob_arm");
|
||||
} else {
|
||||
LOGGER.log(Level.SEVERE, "Unknown mode type: {0}. Cannot apply modifier: {1}.", new Object[] { mode, modifierStructure.getName() });
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Node node, BlenderContext blenderContext) {
|
||||
if (invalid) {
|
||||
LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName());
|
||||
} else {
|
||||
TemporalMesh temporalMesh = this.getTemporalMesh(node);
|
||||
if (temporalMesh != null) {
|
||||
List<String> vertexGroupsToRemove = new ArrayList<String>();
|
||||
if (vertexGroupName != null) {
|
||||
vertexGroupsToRemove.add(vertexGroupName);
|
||||
} else if (pArmatureObject != null && pArmatureObject.isNotNull()) {
|
||||
try {
|
||||
Structure armatureObject = pArmatureObject.fetchData().get(0);
|
||||
|
||||
Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
|
||||
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
|
||||
vertexGroupsToRemove.addAll(this.readBoneNames(bonebase));
|
||||
} catch (BlenderFileException e) {
|
||||
LOGGER.log(Level.SEVERE, "Cannot load armature object for the mask modifier. Cause: {0}", e.getLocalizedMessage());
|
||||
LOGGER.log(Level.SEVERE, "Mask modifier will NOT be applied to node named: {0}", node.getName());
|
||||
}
|
||||
} else {
|
||||
// if the mesh has no vertex groups then remove all verts
|
||||
// if the mesh has at least one vertex group - then do nothing
|
||||
// I have no idea why we should do that, but blender works this way
|
||||
Set<String> vertexGroupNames = new HashSet<String>();
|
||||
for (Map<String, Float> groups : temporalMesh.getVertexGroups()) {
|
||||
vertexGroupNames.addAll(groups.keySet());
|
||||
}
|
||||
if (vertexGroupNames.size() == 0 && !invertMask || vertexGroupNames.size() > 0 && invertMask) {
|
||||
temporalMesh.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (vertexGroupsToRemove.size() > 0) {
|
||||
List<Integer> vertsToBeRemoved = new ArrayList<Integer>();
|
||||
for (int i = 0; i < temporalMesh.getVertexCount(); ++i) {
|
||||
Map<String, Float> vertexGroups = temporalMesh.getVertexGroups().get(i);
|
||||
boolean hasVertexGroup = false;
|
||||
if (vertexGroups != null) {
|
||||
for (String groupName : vertexGroupsToRemove) {
|
||||
Float weight = vertexGroups.get(groupName);
|
||||
if (weight != null && weight > 0) {
|
||||
hasVertexGroup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasVertexGroup && !invertMask || hasVertexGroup && invertMask) {
|
||||
vertsToBeRemoved.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(vertsToBeRemoved);
|
||||
for (Integer vertexIndex : vertsToBeRemoved) {
|
||||
this.removeVertexAt(vertexIndex, temporalMesh);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every face, edge and point that contains
|
||||
* the vertex will be removed.
|
||||
* @param index
|
||||
* the index of a vertex to be removed
|
||||
* @throws IndexOutOfBoundsException
|
||||
* thrown when given index is negative or beyond the count of vertices
|
||||
*/
|
||||
private void removeVertexAt(final int index, TemporalMesh temporalMesh) {
|
||||
if (index < 0 || index >= temporalMesh.getVertexCount()) {
|
||||
throw new IndexOutOfBoundsException("The given index is out of bounds: " + index);
|
||||
}
|
||||
|
||||
temporalMesh.getVertices().remove(index);
|
||||
temporalMesh.getNormals().remove(index);
|
||||
if (temporalMesh.getVertexGroups().size() > 0) {
|
||||
temporalMesh.getVertexGroups().remove(index);
|
||||
}
|
||||
if (temporalMesh.getVerticesColors().size() > 0) {
|
||||
temporalMesh.getVerticesColors().remove(index);
|
||||
}
|
||||
|
||||
IndexPredicate shiftPredicate = new IndexPredicate() {
|
||||
@Override
|
||||
public boolean execute(Integer i) {
|
||||
return i > index;
|
||||
}
|
||||
};
|
||||
for (int i = temporalMesh.getFaces().size() - 1; i >= 0; --i) {
|
||||
Face face = temporalMesh.getFaces().get(i);
|
||||
if (face.getIndexes().indexOf(index) >= 0) {
|
||||
temporalMesh.getFaces().remove(i);
|
||||
} else {
|
||||
face.getIndexes().shiftIndexes(-1, shiftPredicate);
|
||||
}
|
||||
}
|
||||
for (int i = temporalMesh.getEdges().size() - 1; i >= 0; --i) {
|
||||
Edge edge = temporalMesh.getEdges().get(i);
|
||||
if (edge.getFirstIndex() == index || edge.getSecondIndex() == index) {
|
||||
temporalMesh.getEdges().remove(i);
|
||||
} else {
|
||||
edge.shiftIndexes(-1, shiftPredicate);
|
||||
}
|
||||
}
|
||||
for (int i = temporalMesh.getPoints().size() - 1; i >= 0; --i) {
|
||||
Point point = temporalMesh.getPoints().get(i);
|
||||
if (point.getIndex() == index) {
|
||||
temporalMesh.getPoints().remove(i);
|
||||
} else {
|
||||
point.shiftIndexes(-1, shiftPredicate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the names of the bones from the given bone base.
|
||||
* @param boneBase
|
||||
* the list of bone base structures
|
||||
* @return a list of bones' names
|
||||
* @throws BlenderFileException
|
||||
* is thrown if problems with reading the child bones' bases occur
|
||||
*/
|
||||
private List<String> readBoneNames(List<Structure> boneBase) throws BlenderFileException {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Structure boneStructure : boneBase) {
|
||||
int flag = ((Number) boneStructure.getFieldValue("flag")).intValue();
|
||||
if ((flag & BoneContext.SELECTED) != 0) {
|
||||
result.add(boneStructure.getFieldValue("name").toString());
|
||||
}
|
||||
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase();
|
||||
result.addAll(this.readBoneNames(childbase));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.modifiers;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.meshes.Edge;
|
||||
import com.jme3.scene.plugins.blender.meshes.Face;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
||||
|
||||
/**
|
||||
* This modifier allows to array modifier to the object.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
/* package */class MirrorModifier extends Modifier {
|
||||
private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName());
|
||||
|
||||
private static final int FLAG_MIRROR_X = 0x08;
|
||||
private static final int FLAG_MIRROR_Y = 0x10;
|
||||
private static final int FLAG_MIRROR_Z = 0x20;
|
||||
private static final int FLAG_MIRROR_U = 0x02;
|
||||
private static final int FLAG_MIRROR_V = 0x04;
|
||||
private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40;
|
||||
private static final int FLAG_MIRROR_MERGE = 0x80;
|
||||
|
||||
private boolean[] isMirrored;
|
||||
private boolean mirrorU, mirrorV;
|
||||
private boolean merge;
|
||||
private float tolerance;
|
||||
private Pointer pMirrorObject;
|
||||
private boolean mirrorVGroup;
|
||||
|
||||
/**
|
||||
* This constructor reads mirror data from the modifier structure. The
|
||||
* stored data is a map of parameters for mirror modifier. No additional data
|
||||
* is loaded.
|
||||
* When the modifier is applied it is necessary to get the newly created node.
|
||||
*
|
||||
* @param objectStructure
|
||||
* the structure of the object
|
||||
* @param modifierStructure
|
||||
* the structure of the modifier
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blender file is somehow
|
||||
* corrupted
|
||||
*/
|
||||
public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) {
|
||||
if (this.validate(modifierStructure, blenderContext)) {
|
||||
int flag = ((Number) modifierStructure.getFieldValue("flag")).intValue();
|
||||
|
||||
isMirrored = new boolean[] { (flag & FLAG_MIRROR_X) != 0, (flag & FLAG_MIRROR_Y) != 0, (flag & FLAG_MIRROR_Z) != 0 };
|
||||
if (blenderContext.getBlenderKey().isFixUpAxis()) {
|
||||
boolean temp = isMirrored[1];
|
||||
isMirrored[1] = isMirrored[2];
|
||||
isMirrored[2] = temp;
|
||||
}
|
||||
mirrorU = (flag & FLAG_MIRROR_U) != 0;
|
||||
mirrorV = (flag & FLAG_MIRROR_V) != 0;
|
||||
mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0;
|
||||
merge = (flag & FLAG_MIRROR_MERGE) == 0;// in this case we use == instead of != (this is not a mistake)
|
||||
|
||||
tolerance = ((Number) modifierStructure.getFieldValue("tolerance")).floatValue();
|
||||
pMirrorObject = (Pointer) modifierStructure.getFieldValue("mirror_ob");
|
||||
|
||||
if (mirrorVGroup) {
|
||||
LOGGER.warning("Mirroring vertex groups is currently not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(Node node, BlenderContext blenderContext) {
|
||||
if (invalid) {
|
||||
LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName());
|
||||
} else {
|
||||
TemporalMesh temporalMesh = this.getTemporalMesh(node);
|
||||
if (temporalMesh != null) {
|
||||
LOGGER.log(Level.FINE, "Applying mirror modifier to: {0}", temporalMesh);
|
||||
Vector3f mirrorPlaneCenter = new Vector3f();
|
||||
if (pMirrorObject.isNotNull()) {
|
||||
Structure objectStructure;
|
||||
try {
|
||||
objectStructure = pMirrorObject.fetchData().get(0);
|
||||
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
|
||||
Node object = (Node) objectHelper.toObject(objectStructure, blenderContext);
|
||||
if (object != null) {
|
||||
// compute the mirror object coordinates in node's local space
|
||||
mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation());
|
||||
}
|
||||
} catch (BlenderFileException e) {
|
||||
LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage());
|
||||
LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.finest("Allocating temporal variables.");
|
||||
float d;
|
||||
Vector3f mirrorPlaneNormal = new Vector3f();
|
||||
Vector3f shiftVector = new Vector3f();
|
||||
|
||||
LOGGER.fine("Mirroring mesh.");
|
||||
for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) {
|
||||
if (isMirrored[mirrorIndex]) {
|
||||
boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0;
|
||||
if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space
|
||||
mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex)));
|
||||
}
|
||||
|
||||
TemporalMesh mirror = temporalMesh.clone();
|
||||
for (int i = 0; i < mirror.getVertexCount(); ++i) {
|
||||
Vector3f vertex = mirror.getVertices().get(i);
|
||||
Vector3f normal = mirror.getNormals().get(i);
|
||||
|
||||
if (mirrorAtPoint0) {
|
||||
d = Math.abs(vertex.get(mirrorIndex));
|
||||
shiftVector.set(0, 0, 0).set(mirrorIndex, -vertex.get(mirrorIndex));
|
||||
} else {
|
||||
d = this.computeDistanceFromPlane(vertex, mirrorPlaneCenter, mirrorPlaneNormal);
|
||||
mirrorPlaneNormal.mult(d, shiftVector);
|
||||
}
|
||||
|
||||
if (merge && d <= tolerance) {
|
||||
vertex.addLocal(shiftVector);
|
||||
normal.set(mirrorIndex, 0);
|
||||
temporalMesh.getVertices().get(i).addLocal(shiftVector);
|
||||
temporalMesh.getNormals().get(i).set(mirrorIndex, 0);
|
||||
} else {
|
||||
vertex.addLocal(shiftVector.multLocal(2));
|
||||
normal.set(mirrorIndex, -normal.get(mirrorIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// flipping the indexes
|
||||
for (Face face : mirror.getFaces()) {
|
||||
face.flipIndexes();
|
||||
}
|
||||
for (Edge edge : mirror.getEdges()) {
|
||||
edge.flipIndexes();
|
||||
}
|
||||
Collections.reverse(mirror.getPoints());
|
||||
|
||||
if (mirrorU || mirrorV) {
|
||||
for (Face face : mirror.getFaces()) {
|
||||
face.flipUV(mirrorU, mirrorV);
|
||||
}
|
||||
}
|
||||
|
||||
temporalMesh.append(mirror);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the world matrix transformation of the given node.
|
||||
* @param node
|
||||
* the node
|
||||
* @return the node's world transformation matrix
|
||||
*/
|
||||
private Matrix4f getWorldMatrix(Node node) {
|
||||
Matrix4f result = new Matrix4f();
|
||||
result.setTranslation(node.getWorldTranslation());
|
||||
result.setRotationQuaternion(node.getWorldRotation());
|
||||
result.setScale(node.getWorldScale());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method computes the distance between a point and a plane (described by point in space and normal vector).
|
||||
* @param p
|
||||
* the point in the space
|
||||
* @param c
|
||||
* mirror's plane center
|
||||
* @param n
|
||||
* mirror's plane normal (should be normalized)
|
||||
* @return the minimum distance from point to plane
|
||||
*/
|
||||
private float computeDistanceFromPlane(Vector3f p, Vector3f c, Vector3f n) {
|
||||
return Math.abs(n.dot(p) - c.dot(n));
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package com.jme3.scene.plugins.blender.modifiers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
|
||||
|
||||
/**
|
||||
* This class represents an object's modifier. The modifier object can be varied
|
||||
* and the user needs to know what is the type of it for the specified type
|
||||
* name. For example "ArmatureModifierData" type specified in blender is
|
||||
* represented by AnimData object from jMonkeyEngine.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public abstract class Modifier {
|
||||
public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData";
|
||||
public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData";
|
||||
public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData";
|
||||
public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData";
|
||||
public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData";
|
||||
public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData";
|
||||
|
||||
/** This variable indicates if the modifier is invalid (<b>true</b>) or not (<b>false</b>). */
|
||||
protected boolean invalid;
|
||||
/**
|
||||
* A variable that tells if the modifier causes modification. Some modifiers like ArmatureModifier might have no
|
||||
* Armature object attached and thus not really modifying the feature. In such cases it is good to know if it is
|
||||
* sense to add the modifier to the list of object's modifiers.
|
||||
*/
|
||||
protected boolean modifying = true;
|
||||
|
||||
/**
|
||||
* This method applies the modifier to the given node.
|
||||
*
|
||||
* @param node
|
||||
* the node that will have modifier applied
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public abstract void apply(Node node, BlenderContext blenderContext);
|
||||
|
||||
/**
|
||||
* The method that is called when geometries are already created.
|
||||
* @param node
|
||||
* the node that will have the modifier applied
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public void postMeshCreationApply(Node node, BlenderContext blenderContext) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the modifier can be applied multiple times over one mesh.
|
||||
* At this moment only armature and object animation modifiers cannot be
|
||||
* applied multiple times.
|
||||
*
|
||||
* @param modifierType
|
||||
* the type name of the modifier
|
||||
* @return <b>true</b> if the modifier can be applied many times and
|
||||
* <b>false</b> otherwise
|
||||
*/
|
||||
public static boolean canBeAppliedMultipleTimes(String modifierType) {
|
||||
return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType));
|
||||
}
|
||||
|
||||
protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) {
|
||||
Structure modifierData = (Structure) modifierStructure.getFieldValue("modifier");
|
||||
Pointer pError = (Pointer) modifierData.getFieldValue("error");
|
||||
invalid = pError.isNotNull();
|
||||
return !invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <b>true</b> if the modifier causes feature's modification or <b>false</b> if not
|
||||
*/
|
||||
public boolean isModifying() {
|
||||
return modifying;
|
||||
}
|
||||
|
||||
protected TemporalMesh getTemporalMesh(Node node) {
|
||||
List<Spatial> children = node.getChildren();
|
||||
if (children != null && children.size() == 1 && children.get(0) instanceof TemporalMesh) {
|
||||
return (TemporalMesh) children.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.scene.plugins.blender.modifiers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Structure;
|
||||
|
||||
/**
|
||||
* A class that is used in modifiers calculations.
|
||||
*
|
||||
* @author Marcin Roguski
|
||||
*/
|
||||
public class ModifierHelper extends AbstractBlenderHelper {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName());
|
||||
|
||||
/**
|
||||
* This constructor parses the given blender version and stores the result.
|
||||
* Some functionalities may differ in different blender versions.
|
||||
*
|
||||
* @param blenderVersion
|
||||
* the version read from the blend file
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
*/
|
||||
public ModifierHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||
super(blenderVersion, blenderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method reads the given object's modifiers.
|
||||
*
|
||||
* @param objectStructure
|
||||
* the object structure
|
||||
* @param blenderContext
|
||||
* the blender context
|
||||
* @throws BlenderFileException
|
||||
* this exception is thrown when the blender file is somehow
|
||||
* corrupted
|
||||
*/
|
||||
public Collection<Modifier> readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
|
||||
Set<String> alreadyReadModifiers = new HashSet<String>();
|
||||
Collection<Modifier> result = new ArrayList<Modifier>();
|
||||
Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers");
|
||||
List<Structure> modifiers = modifiersListBase.evaluateListBase();
|
||||
for (Structure modifierStructure : modifiers) {
|
||||
String modifierType = modifierStructure.getType();
|
||||
if (!Modifier.canBeAppliedMultipleTimes(modifierType) && alreadyReadModifiers.contains(modifierType)) {
|
||||
LOGGER.log(Level.WARNING, "Modifier {0} can only be applied once to object: {1}", new Object[] { modifierType, objectStructure.getName() });
|
||||
} else {
|
||||
Modifier modifier = null;
|
||||
if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) {
|
||||
modifier = new ArrayModifier(modifierStructure, blenderContext);
|
||||
} else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) {
|
||||
modifier = new MirrorModifier(modifierStructure, blenderContext);
|
||||
} else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) {
|
||||
modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext);
|
||||
} else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) {
|
||||
modifier = new ParticlesModifier(modifierStructure, blenderContext);
|
||||
} else if(Modifier.SUBSURF_MODIFIER_DATA.equals(modifierStructure.getType())) {
|
||||
modifier = new SubdivisionSurfaceModifier(modifierStructure, blenderContext);
|
||||
}
|
||||
|
||||
if (modifier != null) {
|
||||
if (modifier.isModifying()) {
|
||||
result.add(modifier);
|
||||
alreadyReadModifiers.add(modifierType);
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName());
|
||||
}
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user