Merge branch 'master' of https://github.com/jMonkeyEngine/jmonkeyengine
commit
da8bd08aa1
@ -0,0 +1,122 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.renderer.opengl; |
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.lang.reflect.Proxy; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
public class GLTiming implements InvocationHandler { |
||||||
|
|
||||||
|
private final Object obj; |
||||||
|
private final GLTimingState state; |
||||||
|
|
||||||
|
public GLTiming(Object obj, GLTimingState state) { |
||||||
|
this.obj = obj; |
||||||
|
this.state = state; |
||||||
|
} |
||||||
|
|
||||||
|
public static Object createGLTiming(Object glInterface, GLTimingState state, Class<?> ... glInterfaceClasses) { |
||||||
|
return Proxy.newProxyInstance(glInterface.getClass().getClassLoader(), |
||||||
|
glInterfaceClasses, |
||||||
|
new GLTiming(glInterface, state)); |
||||||
|
} |
||||||
|
|
||||||
|
private static class CallTimingComparator implements Comparator<Map.Entry<String, Long>> { |
||||||
|
@Override |
||||||
|
public int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) { |
||||||
|
return (int) (o2.getValue() - o1.getValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
||||||
|
String methodName = method.getName(); |
||||||
|
if (methodName.equals("resetStats")) { |
||||||
|
if (state.lastPrintOutTime + 1000000000 <= System.nanoTime() && state.sampleCount > 0) { |
||||||
|
state.timeSpentInGL /= state.sampleCount; |
||||||
|
System.out.println("--- TOTAL TIME SPENT IN GL CALLS: " + (state.timeSpentInGL/1000) + "us"); |
||||||
|
|
||||||
|
Map.Entry<String, Long>[] callTimes = new Map.Entry[state.callTiming.size()]; |
||||||
|
int i = 0; |
||||||
|
for (Map.Entry<String, Long> callTime : state.callTiming.entrySet()) { |
||||||
|
callTimes[i++] = callTime; |
||||||
|
} |
||||||
|
Arrays.sort(callTimes, new CallTimingComparator()); |
||||||
|
int limit = 10; |
||||||
|
for (Map.Entry<String, Long> callTime : callTimes) { |
||||||
|
long val = callTime.getValue() / state.sampleCount; |
||||||
|
String name = callTime.getKey(); |
||||||
|
String pad = " ".substring(0, 30 - name.length()); |
||||||
|
System.out.println("\t" + callTime.getKey() + pad + (val/1000) + "us"); |
||||||
|
if (limit-- == 0) break; |
||||||
|
} |
||||||
|
for (Map.Entry<String, Long> callTime : callTimes) { |
||||||
|
state.callTiming.put(callTime.getKey(), Long.valueOf(0)); |
||||||
|
} |
||||||
|
|
||||||
|
state.sampleCount = 0; |
||||||
|
state.timeSpentInGL = 0; |
||||||
|
state.lastPrintOutTime = System.nanoTime(); |
||||||
|
} else { |
||||||
|
state.sampleCount++; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
Long currentTimeObj = state.callTiming.get(methodName); |
||||||
|
long currentTime = 0; |
||||||
|
if (currentTimeObj != null) currentTime = currentTimeObj; |
||||||
|
|
||||||
|
|
||||||
|
long startTime = System.nanoTime(); |
||||||
|
Object result = method.invoke(obj, args); |
||||||
|
long delta = System.nanoTime() - startTime; |
||||||
|
|
||||||
|
currentTime += delta; |
||||||
|
state.timeSpentInGL += delta; |
||||||
|
|
||||||
|
state.callTiming.put(methodName, currentTime); |
||||||
|
|
||||||
|
if (delta > 1000000 && !methodName.equals("glClear")) { |
||||||
|
// More than 1ms
|
||||||
|
// Ignore glClear as it cannot be avoided.
|
||||||
|
System.out.println("GL call " + methodName + " took " + (delta/1000) + "us to execute!"); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.renderer.opengl; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
|
||||||
|
public class GLTimingState { |
||||||
|
long timeSpentInGL = 0; |
||||||
|
int sampleCount = 0; |
||||||
|
long lastPrintOutTime = 0; |
||||||
|
final HashMap<String, Long> callTiming = new HashMap<String, Long>(); |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -1,67 +0,0 @@ |
|||||||
package com.jme3.audio.android; |
|
||||||
|
|
||||||
import com.jme3.asset.AssetKey; |
|
||||||
import com.jme3.audio.AudioData; |
|
||||||
import com.jme3.audio.AudioRenderer; |
|
||||||
import com.jme3.util.NativeObject; |
|
||||||
|
|
||||||
public class AndroidAudioData extends AudioData { |
|
||||||
|
|
||||||
protected AssetKey<?> assetKey; |
|
||||||
protected float currentVolume = 0f; |
|
||||||
|
|
||||||
public AndroidAudioData(){ |
|
||||||
super(); |
|
||||||
} |
|
||||||
|
|
||||||
protected AndroidAudioData(int id){ |
|
||||||
super(id); |
|
||||||
} |
|
||||||
|
|
||||||
public AssetKey<?> getAssetKey() { |
|
||||||
return assetKey; |
|
||||||
} |
|
||||||
|
|
||||||
public void setAssetKey(AssetKey<?> assetKey) { |
|
||||||
this.assetKey = assetKey; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public DataType getDataType() { |
|
||||||
return DataType.Buffer; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public float getDuration() { |
|
||||||
return 0; // TODO: ???
|
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void resetObject() { |
|
||||||
this.id = -1; |
|
||||||
setUpdateNeeded(); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void deleteObject(Object rendererObject) { |
|
||||||
((AudioRenderer)rendererObject).deleteAudioData(this); |
|
||||||
} |
|
||||||
|
|
||||||
public float getCurrentVolume() { |
|
||||||
return currentVolume; |
|
||||||
} |
|
||||||
|
|
||||||
public void setCurrentVolume(float currentVolume) { |
|
||||||
this.currentVolume = currentVolume; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public NativeObject createDestructableClone() { |
|
||||||
return new AndroidAudioData(id); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public long getUniqueId() { |
|
||||||
return ((long)OBJTYPE_AUDIOBUFFER << 32) | ((long)id); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,53 @@ |
|||||||
|
package com.jme3.audio.ios; |
||||||
|
|
||||||
|
import com.jme3.audio.openal.AL; |
||||||
|
import java.nio.ByteBuffer; |
||||||
|
import java.nio.FloatBuffer; |
||||||
|
import java.nio.IntBuffer; |
||||||
|
|
||||||
|
public final class IosAL implements AL { |
||||||
|
|
||||||
|
public IosAL() { |
||||||
|
} |
||||||
|
|
||||||
|
public native String alGetString(int parameter); |
||||||
|
|
||||||
|
public native int alGenSources(); |
||||||
|
|
||||||
|
public native int alGetError(); |
||||||
|
|
||||||
|
public native void alDeleteSources(int numSources, IntBuffer sources); |
||||||
|
|
||||||
|
public native void alGenBuffers(int numBuffers, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alDeleteBuffers(int numBuffers, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alSourceStop(int source); |
||||||
|
|
||||||
|
public native void alSourcei(int source, int param, int value); |
||||||
|
|
||||||
|
public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency); |
||||||
|
|
||||||
|
public native void alSourcePlay(int source); |
||||||
|
|
||||||
|
public native void alSourcePause(int source); |
||||||
|
|
||||||
|
public native void alSourcef(int source, int param, float value); |
||||||
|
|
||||||
|
public native void alSource3f(int source, int param, float value1, float value2, float value3); |
||||||
|
|
||||||
|
public native int alGetSourcei(int source, int param); |
||||||
|
|
||||||
|
public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alListener(int param, FloatBuffer data); |
||||||
|
|
||||||
|
public native void alListenerf(int param, float value); |
||||||
|
|
||||||
|
public native void alListener3f(int param, float value1, float value2, float value3); |
||||||
|
|
||||||
|
public native void alSource3i(int source, int param, int value1, int value2, int value3); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
package com.jme3.audio.ios; |
||||||
|
|
||||||
|
import com.jme3.audio.openal.ALC; |
||||||
|
import java.nio.IntBuffer; |
||||||
|
|
||||||
|
public final class IosALC implements ALC { |
||||||
|
|
||||||
|
public IosALC() { |
||||||
|
} |
||||||
|
|
||||||
|
public native void createALC(); |
||||||
|
|
||||||
|
public native void destroyALC(); |
||||||
|
|
||||||
|
public native boolean isCreated(); |
||||||
|
|
||||||
|
public native String alcGetString(int parameter); |
||||||
|
|
||||||
|
public native boolean alcIsExtensionPresent(String extension); |
||||||
|
|
||||||
|
public native void alcGetInteger(int param, IntBuffer buffer, int size); |
||||||
|
|
||||||
|
public native void alcDevicePauseSOFT(); |
||||||
|
|
||||||
|
public native void alcDeviceResumeSOFT(); |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
package com.jme3.audio.ios; |
||||||
|
|
||||||
|
import com.jme3.audio.openal.EFX; |
||||||
|
import java.nio.IntBuffer; |
||||||
|
|
||||||
|
public class IosEFX implements EFX { |
||||||
|
|
||||||
|
public IosEFX() { |
||||||
|
} |
||||||
|
|
||||||
|
public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alGenEffects(int numEffects, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alEffecti(int effect, int param, int value); |
||||||
|
|
||||||
|
public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value); |
||||||
|
|
||||||
|
public native void alDeleteEffects(int numEffects, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alGenFilters(int numFilters, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alFilteri(int filter, int param, int value); |
||||||
|
|
||||||
|
public native void alFilterf(int filter, int param, float value); |
||||||
|
|
||||||
|
public native void alDeleteFilters(int numFilters, IntBuffer buffers); |
||||||
|
|
||||||
|
public native void alEffectf(int effect, int param, float value); |
||||||
|
} |
@ -1,20 +0,0 @@ |
|||||||
package com.jme3.audio.plugins; |
|
||||||
|
|
||||||
import com.jme3.asset.AssetInfo; |
|
||||||
import com.jme3.asset.AssetLoader; |
|
||||||
import com.jme3.audio.android.AndroidAudioData; |
|
||||||
import java.io.IOException; |
|
||||||
|
|
||||||
/** |
|
||||||
* <code>AndroidAudioLoader</code> will create an |
|
||||||
* {@link AndroidAudioData} object with the specified asset key. |
|
||||||
*/ |
|
||||||
public class AndroidAudioLoader implements AssetLoader { |
|
||||||
|
|
||||||
@Override |
|
||||||
public Object load(AssetInfo assetInfo) throws IOException { |
|
||||||
AndroidAudioData result = new AndroidAudioData(); |
|
||||||
result.setAssetKey(assetInfo.getKey()); |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -1,576 +0,0 @@ |
|||||||
package com.jme3.renderer.ios; |
|
||||||
|
|
||||||
//import android.graphics.Bitmap;
|
|
||||||
//import android.opengl.ETC1;
|
|
||||||
//import android.opengl.ETC1Util.ETC1Texture;
|
|
||||||
//import android.opengl.JmeIosGLES;
|
|
||||||
//import android.opengl.GLUtils;
|
|
||||||
//import com.jme3.asset.AndroidImageInfo;
|
|
||||||
import com.jme3.renderer.ios.JmeIosGLES; |
|
||||||
import com.jme3.math.FastMath; |
|
||||||
import com.jme3.renderer.RendererException; |
|
||||||
import com.jme3.texture.Image; |
|
||||||
import com.jme3.texture.Image.Format; |
|
||||||
import com.jme3.util.BufferUtils; |
|
||||||
import java.nio.ByteBuffer; |
|
||||||
import java.util.logging.Level; |
|
||||||
import java.util.logging.Logger; |
|
||||||
|
|
||||||
public class TextureUtil { |
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(TextureUtil.class.getName()); |
|
||||||
//TODO Make this configurable through appSettings
|
|
||||||
public static boolean ENABLE_COMPRESSION = true; |
|
||||||
private static boolean NPOT = false; |
|
||||||
private static boolean ETC1support = false; |
|
||||||
private static boolean DXT1 = false; |
|
||||||
private static boolean PVRTC = false; |
|
||||||
private static boolean DEPTH24_STENCIL8 = false; |
|
||||||
private static boolean DEPTH_TEXTURE = false; |
|
||||||
private static boolean RGBA8 = false; |
|
||||||
|
|
||||||
// Same constant used by both GL_ARM_rgba8 and GL_OES_rgb8_rgba8.
|
|
||||||
private static final int GL_RGBA8 = 0x8058; |
|
||||||
|
|
||||||
private static final int GL_DXT1 = 0x83F0; |
|
||||||
private static final int GL_DXT1A = 0x83F1; |
|
||||||
|
|
||||||
private static final int GL_DEPTH_STENCIL_OES = 0x84F9; |
|
||||||
private static final int GL_UNSIGNED_INT_24_8_OES = 0x84FA; |
|
||||||
private static final int GL_DEPTH24_STENCIL8_OES = 0x88F0; |
|
||||||
|
|
||||||
public static void loadTextureFeatures(String extensionString) { |
|
||||||
ETC1support = extensionString.contains("GL_OES_compressed_ETC1_RGB8_texture"); |
|
||||||
DEPTH24_STENCIL8 = extensionString.contains("GL_OES_packed_depth_stencil"); |
|
||||||
NPOT = extensionString.contains("GL_IMG_texture_npot") |
|
||||||
|| extensionString.contains("GL_OES_texture_npot") |
|
||||||
|| extensionString.contains("GL_NV_texture_npot_2D_mipmap"); |
|
||||||
|
|
||||||
PVRTC = extensionString.contains("GL_IMG_texture_compression_pvrtc"); |
|
||||||
|
|
||||||
DXT1 = extensionString.contains("GL_EXT_texture_compression_dxt1"); |
|
||||||
DEPTH_TEXTURE = extensionString.contains("GL_OES_depth_texture"); |
|
||||||
|
|
||||||
RGBA8 = extensionString.contains("GL_ARM_rgba8") || |
|
||||||
extensionString.contains("GL_OES_rgb8_rgba8"); |
|
||||||
|
|
||||||
logger.log(Level.FINE, "Supports ETC1? {0}", ETC1support); |
|
||||||
logger.log(Level.FINE, "Supports DEPTH24_STENCIL8? {0}", DEPTH24_STENCIL8); |
|
||||||
logger.log(Level.FINE, "Supports NPOT? {0}", NPOT); |
|
||||||
logger.log(Level.FINE, "Supports PVRTC? {0}", PVRTC); |
|
||||||
logger.log(Level.FINE, "Supports DXT1? {0}", DXT1); |
|
||||||
logger.log(Level.FINE, "Supports DEPTH_TEXTURE? {0}", DEPTH_TEXTURE); |
|
||||||
logger.log(Level.FINE, "Supports RGBA8? {0}", RGBA8); |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
private static void buildMipmap(Bitmap bitmap, boolean compress) { |
|
||||||
int level = 0; |
|
||||||
int height = bitmap.getHeight(); |
|
||||||
int width = bitmap.getWidth(); |
|
||||||
|
|
||||||
logger.log(Level.FINEST, " - Generating mipmaps for bitmap using SOFTWARE"); |
|
||||||
|
|
||||||
JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); |
|
||||||
|
|
||||||
while (height >= 1 || width >= 1) { |
|
||||||
//First of all, generate the texture from our bitmap and set it to the according level
|
|
||||||
if (compress) { |
|
||||||
logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) with compression.", new Object[]{level, width, height}); |
|
||||||
uploadBitmapAsCompressed(JmeIosGLES.GL_TEXTURE_2D, level, bitmap, false, 0, 0); |
|
||||||
} else { |
|
||||||
logger.log(Level.FINEST, " - Uploading LOD level {0} ({1}x{2}) directly.", new Object[]{level, width, height}); |
|
||||||
GLUtils.texImage2D(JmeIosGLES.GL_TEXTURE_2D, level, bitmap, 0); |
|
||||||
} |
|
||||||
|
|
||||||
if (height == 1 || width == 1) { |
|
||||||
break; |
|
||||||
} |
|
||||||
|
|
||||||
//Increase the mipmap level
|
|
||||||
height /= 2; |
|
||||||
width /= 2; |
|
||||||
Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, width, height, true); |
|
||||||
|
|
||||||
// Recycle any bitmaps created as a result of scaling the bitmap.
|
|
||||||
// Do not recycle the original image (mipmap level 0)
|
|
||||||
if (level != 0) { |
|
||||||
bitmap.recycle(); |
|
||||||
} |
|
||||||
|
|
||||||
bitmap = bitmap2; |
|
||||||
|
|
||||||
level++; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private static void uploadBitmapAsCompressed(int target, int level, Bitmap bitmap, boolean subTexture, int x, int y) { |
|
||||||
if (bitmap.hasAlpha()) { |
|
||||||
logger.log(Level.FINEST, " - Uploading bitmap directly. Cannot compress as alpha present."); |
|
||||||
if (subTexture) { |
|
||||||
GLUtils.texSubImage2D(target, level, x, y, bitmap); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} else { |
|
||||||
GLUtils.texImage2D(target, level, bitmap, 0); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
} else { |
|
||||||
// Convert to RGB565
|
|
||||||
int bytesPerPixel = 2; |
|
||||||
Bitmap rgb565 = bitmap.copy(Bitmap.Config.RGB_565, true); |
|
||||||
|
|
||||||
// Put texture data into ByteBuffer
|
|
||||||
ByteBuffer inputImage = BufferUtils.createByteBuffer(bitmap.getRowBytes() * bitmap.getHeight()); |
|
||||||
rgb565.copyPixelsToBuffer(inputImage); |
|
||||||
inputImage.position(0); |
|
||||||
|
|
||||||
// Delete the copied RGB565 image
|
|
||||||
rgb565.recycle(); |
|
||||||
|
|
||||||
// Encode the image into the output bytebuffer
|
|
||||||
int encodedImageSize = ETC1.getEncodedDataSize(bitmap.getWidth(), bitmap.getHeight()); |
|
||||||
ByteBuffer compressedImage = BufferUtils.createByteBuffer(encodedImageSize); |
|
||||||
ETC1.encodeImage(inputImage, bitmap.getWidth(), |
|
||||||
bitmap.getHeight(), |
|
||||||
bytesPerPixel, |
|
||||||
bytesPerPixel * bitmap.getWidth(), |
|
||||||
compressedImage); |
|
||||||
|
|
||||||
// Delete the input image buffer
|
|
||||||
BufferUtils.destroyDirectBuffer(inputImage); |
|
||||||
|
|
||||||
// Create an ETC1Texture from the compressed image data
|
|
||||||
ETC1Texture etc1tex = new ETC1Texture(bitmap.getWidth(), bitmap.getHeight(), compressedImage); |
|
||||||
|
|
||||||
// Upload the ETC1Texture
|
|
||||||
if (bytesPerPixel == 2) { |
|
||||||
int oldSize = (bitmap.getRowBytes() * bitmap.getHeight()); |
|
||||||
int newSize = compressedImage.capacity(); |
|
||||||
logger.log(Level.FINEST, " - Uploading compressed image to GL, oldSize = {0}, newSize = {1}, ratio = {2}", new Object[]{oldSize, newSize, (float) oldSize / newSize}); |
|
||||||
if (subTexture) { |
|
||||||
JmeIosGLES.glCompressedTexSubImage2D(target, |
|
||||||
level, |
|
||||||
x, y, |
|
||||||
bitmap.getWidth(), |
|
||||||
bitmap.getHeight(), |
|
||||||
ETC1.ETC1_RGB8_OES, |
|
||||||
etc1tex.getData().capacity(), |
|
||||||
etc1tex.getData()); |
|
||||||
|
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} else { |
|
||||||
JmeIosGLES.glCompressedTexImage2D(target, |
|
||||||
level, |
|
||||||
ETC1.ETC1_RGB8_OES, |
|
||||||
bitmap.getWidth(), |
|
||||||
bitmap.getHeight(), |
|
||||||
0, |
|
||||||
etc1tex.getData().capacity(), |
|
||||||
etc1tex.getData()); |
|
||||||
|
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
|
|
||||||
// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB,
|
|
||||||
// JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5, etc1Texture);
|
|
||||||
// } else if (bytesPerPixel == 3) {
|
|
||||||
// ETC1Util.loadTexture(target, level, 0, JmeIosGLES.GL_RGB,
|
|
||||||
// JmeIosGLES.GL_UNSIGNED_BYTE, etc1Texture);
|
|
||||||
} |
|
||||||
|
|
||||||
BufferUtils.destroyDirectBuffer(compressedImage); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* <code>uploadTextureBitmap</code> uploads a native android bitmap |
|
||||||
*/ |
|
||||||
/* |
|
||||||
public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips) { |
|
||||||
uploadTextureBitmap(target, bitmap, needMips, false, 0, 0); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* <code>uploadTextureBitmap</code> uploads a native android bitmap |
|
||||||
*/ |
|
||||||
/* |
|
||||||
public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips, boolean subTexture, int x, int y) { |
|
||||||
boolean recycleBitmap = false; |
|
||||||
//TODO, maybe this should raise an exception when NPOT is not supported
|
|
||||||
|
|
||||||
boolean willCompress = ENABLE_COMPRESSION && ETC1support && !bitmap.hasAlpha(); |
|
||||||
if (needMips && willCompress) { |
|
||||||
// Image is compressed and mipmaps are desired, generate them
|
|
||||||
// using software.
|
|
||||||
buildMipmap(bitmap, willCompress); |
|
||||||
} else { |
|
||||||
if (willCompress) { |
|
||||||
// Image is compressed but mipmaps are not desired, upload directly.
|
|
||||||
logger.log(Level.FINEST, " - Uploading compressed bitmap. Mipmaps are not generated."); |
|
||||||
uploadBitmapAsCompressed(target, 0, bitmap, subTexture, x, y); |
|
||||||
|
|
||||||
} else { |
|
||||||
// Image is not compressed, mipmaps may or may not be desired.
|
|
||||||
logger.log(Level.FINEST, " - Uploading bitmap directly.{0}", |
|
||||||
(needMips |
|
||||||
? " Mipmaps will be generated in HARDWARE" |
|
||||||
: " Mipmaps are not generated.")); |
|
||||||
if (subTexture) { |
|
||||||
System.err.println("x : " + x + " y :" + y + " , " + bitmap.getWidth() + "/" + bitmap.getHeight()); |
|
||||||
GLUtils.texSubImage2D(target, 0, x, y, bitmap); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} else { |
|
||||||
GLUtils.texImage2D(target, 0, bitmap, 0); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
|
|
||||||
if (needMips) { |
|
||||||
// No pregenerated mips available,
|
|
||||||
// generate from base level if required
|
|
||||||
JmeIosGLES.glGenerateMipmap(target); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (recycleBitmap) { |
|
||||||
bitmap.recycle(); |
|
||||||
} |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
public static void uploadTextureAny(Image img, int target, int index, boolean needMips) { |
|
||||||
/* |
|
||||||
if (img.getEfficentData() instanceof AndroidImageInfo) { |
|
||||||
logger.log(Level.FINEST, " === Uploading image {0}. Using BITMAP PATH === ", img); |
|
||||||
// If image was loaded from asset manager, use fast path
|
|
||||||
AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); |
|
||||||
uploadTextureBitmap(target, imageInfo.getBitmap(), needMips); |
|
||||||
} else { |
|
||||||
*/ |
|
||||||
logger.log(Level.FINEST, " === Uploading image {0}. Using BUFFER PATH === ", img); |
|
||||||
boolean wantGeneratedMips = needMips && !img.hasMipmaps(); |
|
||||||
if (wantGeneratedMips && img.getFormat().isCompressed()) { |
|
||||||
logger.log(Level.WARNING, "Generating mipmaps is only" |
|
||||||
+ " supported for Bitmap based or non-compressed images!"); |
|
||||||
} |
|
||||||
|
|
||||||
// Upload using slower path
|
|
||||||
logger.log(Level.FINEST, " - Uploading bitmap directly.{0}", |
|
||||||
(wantGeneratedMips |
|
||||||
? " Mipmaps will be generated in HARDWARE" |
|
||||||
: " Mipmaps are not generated.")); |
|
||||||
|
|
||||||
uploadTexture(img, target, index); |
|
||||||
|
|
||||||
// Image was uploaded using slower path, since it is not compressed,
|
|
||||||
// then compress it
|
|
||||||
if (wantGeneratedMips) { |
|
||||||
// No pregenerated mips available,
|
|
||||||
// generate from base level if required
|
|
||||||
JmeIosGLES.glGenerateMipmap(target); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
//}
|
|
||||||
} |
|
||||||
|
|
||||||
private static void unsupportedFormat(Format fmt) { |
|
||||||
throw new UnsupportedOperationException("The image format '" + fmt + "' is unsupported by the video hardware."); |
|
||||||
} |
|
||||||
|
|
||||||
public static IosGLImageFormat getImageFormat(Format fmt) throws UnsupportedOperationException { |
|
||||||
IosGLImageFormat imageFormat = new IosGLImageFormat(); |
|
||||||
switch (fmt) { |
|
||||||
case Depth32: |
|
||||||
case Depth32F: |
|
||||||
throw new UnsupportedOperationException("The image format '" |
|
||||||
+ fmt + "' is not supported by OpenGL ES 2.0 specification."); |
|
||||||
case Alpha8: |
|
||||||
imageFormat.format = JmeIosGLES.GL_ALPHA; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
if (RGBA8) { |
|
||||||
imageFormat.renderBufferStorageFormat = GL_RGBA8; |
|
||||||
} else { |
|
||||||
// Highest precision alpha supported by vanilla OGLES2
|
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; |
|
||||||
} |
|
||||||
break; |
|
||||||
case Luminance8: |
|
||||||
imageFormat.format = JmeIosGLES.GL_LUMINANCE; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
if (RGBA8) { |
|
||||||
imageFormat.renderBufferStorageFormat = GL_RGBA8; |
|
||||||
} else { |
|
||||||
// Highest precision luminance supported by vanilla OGLES2
|
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; |
|
||||||
} |
|
||||||
break; |
|
||||||
case Luminance8Alpha8: |
|
||||||
imageFormat.format = JmeIosGLES.GL_LUMINANCE_ALPHA; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
if (RGBA8) { |
|
||||||
imageFormat.renderBufferStorageFormat = GL_RGBA8; |
|
||||||
} else { |
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; |
|
||||||
} |
|
||||||
break; |
|
||||||
case RGB565: |
|
||||||
imageFormat.format = JmeIosGLES.GL_RGB; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_6_5; |
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; |
|
||||||
break; |
|
||||||
case RGB5A1: |
|
||||||
imageFormat.format = JmeIosGLES.GL_RGBA; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT_5_5_5_1; |
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB5_A1; |
|
||||||
break; |
|
||||||
case RGB8: |
|
||||||
imageFormat.format = JmeIosGLES.GL_RGB; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
if (RGBA8) { |
|
||||||
imageFormat.renderBufferStorageFormat = GL_RGBA8; |
|
||||||
} else { |
|
||||||
// Fallback: Use RGB565 if RGBA8 is not available.
|
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; |
|
||||||
} |
|
||||||
break; |
|
||||||
case BGR8: |
|
||||||
imageFormat.format = JmeIosGLES.GL_RGB; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
if (RGBA8) { |
|
||||||
imageFormat.renderBufferStorageFormat = GL_RGBA8; |
|
||||||
} else { |
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGB565; |
|
||||||
} |
|
||||||
break; |
|
||||||
case RGBA8: |
|
||||||
imageFormat.format = JmeIosGLES.GL_RGBA; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
if (RGBA8) { |
|
||||||
imageFormat.renderBufferStorageFormat = GL_RGBA8; |
|
||||||
} else { |
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_RGBA4; |
|
||||||
} |
|
||||||
break; |
|
||||||
case Depth: |
|
||||||
case Depth16: |
|
||||||
if (!DEPTH_TEXTURE) { |
|
||||||
unsupportedFormat(fmt); |
|
||||||
} |
|
||||||
imageFormat.format = JmeIosGLES.GL_DEPTH_COMPONENT; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT; |
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_DEPTH_COMPONENT16; |
|
||||||
break; |
|
||||||
case Depth24: |
|
||||||
case Depth24Stencil8: |
|
||||||
if (!DEPTH_TEXTURE) { |
|
||||||
unsupportedFormat(fmt); |
|
||||||
} |
|
||||||
if (DEPTH24_STENCIL8) { |
|
||||||
// NEW: True Depth24 + Stencil8 format.
|
|
||||||
imageFormat.format = GL_DEPTH_STENCIL_OES; |
|
||||||
imageFormat.dataType = GL_UNSIGNED_INT_24_8_OES; |
|
||||||
imageFormat.renderBufferStorageFormat = GL_DEPTH24_STENCIL8_OES; |
|
||||||
} else { |
|
||||||
// Vanilla OGLES2, only Depth16 available.
|
|
||||||
imageFormat.format = JmeIosGLES.GL_DEPTH_COMPONENT; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_SHORT; |
|
||||||
imageFormat.renderBufferStorageFormat = JmeIosGLES.GL_DEPTH_COMPONENT16; |
|
||||||
} |
|
||||||
break; |
|
||||||
case DXT1: |
|
||||||
if (!DXT1) { |
|
||||||
unsupportedFormat(fmt); |
|
||||||
} |
|
||||||
imageFormat.format = GL_DXT1; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
imageFormat.compress = true; |
|
||||||
break; |
|
||||||
case DXT1A: |
|
||||||
if (!DXT1) { |
|
||||||
unsupportedFormat(fmt); |
|
||||||
} |
|
||||||
imageFormat.format = GL_DXT1A; |
|
||||||
imageFormat.dataType = JmeIosGLES.GL_UNSIGNED_BYTE; |
|
||||||
imageFormat.compress = true; |
|
||||||
break; |
|
||||||
default: |
|
||||||
throw new UnsupportedOperationException("Unrecognized format: " + fmt); |
|
||||||
} |
|
||||||
return imageFormat; |
|
||||||
} |
|
||||||
|
|
||||||
public static class IosGLImageFormat { |
|
||||||
|
|
||||||
boolean compress = false; |
|
||||||
int format = -1; |
|
||||||
int renderBufferStorageFormat = -1; |
|
||||||
int dataType = -1; |
|
||||||
} |
|
||||||
|
|
||||||
private static void uploadTexture(Image img, |
|
||||||
int target, |
|
||||||
int index) { |
|
||||||
|
|
||||||
/* |
|
||||||
if (img.getEfficentData() instanceof AndroidImageInfo) { |
|
||||||
throw new RendererException("This image uses efficient data. " |
|
||||||
+ "Use uploadTextureBitmap instead."); |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
// Otherwise upload image directly.
|
|
||||||
// Prefer to only use power of 2 textures here to avoid errors.
|
|
||||||
Image.Format fmt = img.getFormat(); |
|
||||||
ByteBuffer data; |
|
||||||
if (index >= 0 || img.getData() != null && img.getData().size() > 0) { |
|
||||||
data = img.getData(index); |
|
||||||
} else { |
|
||||||
data = null; |
|
||||||
} |
|
||||||
|
|
||||||
int width = img.getWidth(); |
|
||||||
int height = img.getHeight(); |
|
||||||
|
|
||||||
if (!NPOT && img.isNPOT()) { |
|
||||||
// Check if texture is POT
|
|
||||||
throw new RendererException("Non-power-of-2 textures " |
|
||||||
+ "are not supported by the video hardware " |
|
||||||
+ "and no scaling path available for image: " + img); |
|
||||||
} |
|
||||||
IosGLImageFormat imageFormat = getImageFormat(fmt); |
|
||||||
|
|
||||||
if (data != null) { |
|
||||||
JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
|
|
||||||
int[] mipSizes = img.getMipMapSizes(); |
|
||||||
int pos = 0; |
|
||||||
if (mipSizes == null) { |
|
||||||
if (data != null) { |
|
||||||
mipSizes = new int[]{data.capacity()}; |
|
||||||
} else { |
|
||||||
mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for (int i = 0; i < mipSizes.length; i++) { |
|
||||||
int mipWidth = Math.max(1, width >> i); |
|
||||||
int mipHeight = Math.max(1, height >> i); |
|
||||||
|
|
||||||
if (data != null) { |
|
||||||
data.position(pos); |
|
||||||
data.limit(pos + mipSizes[i]); |
|
||||||
} |
|
||||||
|
|
||||||
if (imageFormat.compress && data != null) { |
|
||||||
JmeIosGLES.glCompressedTexImage2D(target, |
|
||||||
i, |
|
||||||
imageFormat.format, |
|
||||||
mipWidth, |
|
||||||
mipHeight, |
|
||||||
0, |
|
||||||
data.remaining(), |
|
||||||
data); |
|
||||||
} else { |
|
||||||
JmeIosGLES.glTexImage2D(target, |
|
||||||
i, |
|
||||||
imageFormat.format, |
|
||||||
mipWidth, |
|
||||||
mipHeight, |
|
||||||
0, |
|
||||||
imageFormat.format, |
|
||||||
imageFormat.dataType, |
|
||||||
data); |
|
||||||
} |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
|
|
||||||
pos += mipSizes[i]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Update the texture currently bound to target at with data from the given |
|
||||||
* Image at position x and y. The parameter index is used as the zoffset in |
|
||||||
* case a 3d texture or texture 2d array is being updated. |
|
||||||
* |
|
||||||
* @param image Image with the source data (this data will be put into the |
|
||||||
* texture) |
|
||||||
* @param target the target texture |
|
||||||
* @param index the mipmap level to update |
|
||||||
* @param x the x position where to put the image in the texture |
|
||||||
* @param y the y position where to put the image in the texture |
|
||||||
*/ |
|
||||||
public static void uploadSubTexture( |
|
||||||
Image img, |
|
||||||
int target, |
|
||||||
int index, |
|
||||||
int x, |
|
||||||
int y) { |
|
||||||
//TODO:
|
|
||||||
/* |
|
||||||
if (img.getEfficentData() instanceof AndroidImageInfo) { |
|
||||||
AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); |
|
||||||
uploadTextureBitmap(target, imageInfo.getBitmap(), true, true, x, y); |
|
||||||
return; |
|
||||||
} |
|
||||||
*/ |
|
||||||
|
|
||||||
// Otherwise upload image directly.
|
|
||||||
// Prefer to only use power of 2 textures here to avoid errors.
|
|
||||||
Image.Format fmt = img.getFormat(); |
|
||||||
ByteBuffer data; |
|
||||||
if (index >= 0 || img.getData() != null && img.getData().size() > 0) { |
|
||||||
data = img.getData(index); |
|
||||||
} else { |
|
||||||
data = null; |
|
||||||
} |
|
||||||
|
|
||||||
int width = img.getWidth(); |
|
||||||
int height = img.getHeight(); |
|
||||||
|
|
||||||
if (!NPOT && img.isNPOT()) { |
|
||||||
// Check if texture is POT
|
|
||||||
throw new RendererException("Non-power-of-2 textures " |
|
||||||
+ "are not supported by the video hardware " |
|
||||||
+ "and no scaling path available for image: " + img); |
|
||||||
} |
|
||||||
IosGLImageFormat imageFormat = getImageFormat(fmt); |
|
||||||
|
|
||||||
if (data != null) { |
|
||||||
JmeIosGLES.glPixelStorei(JmeIosGLES.GL_UNPACK_ALIGNMENT, 1); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
|
|
||||||
int[] mipSizes = img.getMipMapSizes(); |
|
||||||
int pos = 0; |
|
||||||
if (mipSizes == null) { |
|
||||||
if (data != null) { |
|
||||||
mipSizes = new int[]{data.capacity()}; |
|
||||||
} else { |
|
||||||
mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8}; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for (int i = 0; i < mipSizes.length; i++) { |
|
||||||
int mipWidth = Math.max(1, width >> i); |
|
||||||
int mipHeight = Math.max(1, height >> i); |
|
||||||
|
|
||||||
if (data != null) { |
|
||||||
data.position(pos); |
|
||||||
data.limit(pos + mipSizes[i]); |
|
||||||
} |
|
||||||
|
|
||||||
if (imageFormat.compress && data != null) { |
|
||||||
JmeIosGLES.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, data.remaining(), data); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} else { |
|
||||||
JmeIosGLES.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, imageFormat.dataType, data); |
|
||||||
JmeIosGLES.checkGLError(); |
|
||||||
} |
|
||||||
|
|
||||||
pos += mipSizes[i]; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,10 @@ |
|||||||
|
INCLUDE com/jme3/asset/General.cfg |
||||||
|
|
||||||
|
# IOS specific loaders |
||||||
|
LOADER com.jme3.system.ios.IosImageLoader : jpg, bmp, gif, png, jpeg |
||||||
|
LOADER com.jme3.audio.plugins.OGGLoader : ogg |
||||||
|
LOADER com.jme3.material.plugins.J3MLoader : j3m |
||||||
|
LOADER com.jme3.material.plugins.J3MLoader : j3md |
||||||
|
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib |
||||||
|
LOADER com.jme3.export.binary.BinaryImporter : j3o |
||||||
|
LOADER com.jme3.font.plugins.BitmapFontLoader : fnt |
@ -0,0 +1,98 @@ |
|||||||
|
package com.jme3.renderer.lwjgl; |
||||||
|
|
||||||
|
import com.jme3.renderer.RendererException; |
||||||
|
import com.jme3.renderer.opengl.GLFbo; |
||||||
|
import java.nio.Buffer; |
||||||
|
import java.nio.IntBuffer; |
||||||
|
import org.lwjgl.opengl.EXTFramebufferBlit; |
||||||
|
import org.lwjgl.opengl.EXTFramebufferMultisample; |
||||||
|
import org.lwjgl.opengl.EXTFramebufferObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements GLFbo via GL_EXT_framebuffer_object. |
||||||
|
* |
||||||
|
* @author Kirill Vainer |
||||||
|
*/ |
||||||
|
public class LwjglGLFboEXT implements GLFbo { |
||||||
|
|
||||||
|
private static void checkLimit(Buffer buffer) { |
||||||
|
if (buffer == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (buffer.limit() == 0) { |
||||||
|
throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error"); |
||||||
|
} |
||||||
|
if (buffer.remaining() == 0) { |
||||||
|
throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { |
||||||
|
EXTFramebufferBlit.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { |
||||||
|
EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glBindFramebufferEXT(int param1, int param2) { |
||||||
|
EXTFramebufferObject.glBindFramebufferEXT(param1, param2); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glBindRenderbufferEXT(int param1, int param2) { |
||||||
|
EXTFramebufferObject.glBindRenderbufferEXT(param1, param2); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int glCheckFramebufferStatusEXT(int param1) { |
||||||
|
return EXTFramebufferObject.glCheckFramebufferStatusEXT(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glDeleteFramebuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
EXTFramebufferObject.glDeleteFramebuffersEXT(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glDeleteRenderbuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
EXTFramebufferObject.glDeleteRenderbuffersEXT(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { |
||||||
|
EXTFramebufferObject.glFramebufferRenderbufferEXT(param1, param2, param3, param4); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { |
||||||
|
EXTFramebufferObject.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glGenFramebuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
EXTFramebufferObject.glGenFramebuffersEXT(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glGenRenderbuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
EXTFramebufferObject.glGenRenderbuffersEXT(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glGenerateMipmapEXT(int param1) { |
||||||
|
EXTFramebufferObject.glGenerateMipmapEXT(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { |
||||||
|
EXTFramebufferObject.glRenderbufferStorageEXT(param1, param2, param3, param4); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
package com.jme3.renderer.lwjgl; |
||||||
|
|
||||||
|
import com.jme3.renderer.RendererException; |
||||||
|
import com.jme3.renderer.opengl.GLFbo; |
||||||
|
import java.nio.Buffer; |
||||||
|
import java.nio.IntBuffer; |
||||||
|
import org.lwjgl.opengl.GL30; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements GLFbo via OpenGL3+. |
||||||
|
* |
||||||
|
* @author Kirill Vainer |
||||||
|
*/ |
||||||
|
public class LwjglGLFboGL3 implements GLFbo { |
||||||
|
|
||||||
|
private static void checkLimit(Buffer buffer) { |
||||||
|
if (buffer == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (buffer.limit() == 0) { |
||||||
|
throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error"); |
||||||
|
} |
||||||
|
if (buffer.remaining() == 0) { |
||||||
|
throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { |
||||||
|
GL30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { |
||||||
|
GL30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glBindFramebufferEXT(int param1, int param2) { |
||||||
|
GL30.glBindFramebuffer(param1, param2); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glBindRenderbufferEXT(int param1, int param2) { |
||||||
|
GL30.glBindRenderbuffer(param1, param2); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int glCheckFramebufferStatusEXT(int param1) { |
||||||
|
return GL30.glCheckFramebufferStatus(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glDeleteFramebuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
GL30.glDeleteFramebuffers(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glDeleteRenderbuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
GL30.glDeleteRenderbuffers(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { |
||||||
|
GL30.glFramebufferRenderbuffer(param1, param2, param3, param4); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { |
||||||
|
GL30.glFramebufferTexture2D(param1, param2, param3, param4, param5); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glGenFramebuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
GL30.glGenFramebuffers(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glGenRenderbuffersEXT(IntBuffer param1) { |
||||||
|
checkLimit(param1); |
||||||
|
GL30.glGenRenderbuffers(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glGenerateMipmapEXT(int param1) { |
||||||
|
GL30.glGenerateMipmap(param1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { |
||||||
|
GL30.glRenderbufferStorage(param1, param2, param3, param4); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.system.lwjgl; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import org.lwjgl.opengl.ARBDebugOutput; |
||||||
|
import org.lwjgl.opengl.ARBDebugOutputCallback; |
||||||
|
|
||||||
|
class LwjglGLDebugOutputHandler implements ARBDebugOutputCallback.Handler { |
||||||
|
|
||||||
|
private static final HashMap<Integer, String> constMap = new HashMap<Integer, String>(); |
||||||
|
private static final String MESSAGE_FORMAT = |
||||||
|
"[JME3] OpenGL debug message\r\n" + |
||||||
|
" ID: %d\r\n" + |
||||||
|
" Source: %s\r\n" + |
||||||
|
" Type: %s\r\n" + |
||||||
|
" Severity: %s\r\n" + |
||||||
|
" Message: %s"; |
||||||
|
|
||||||
|
static { |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_API_ARB, "API"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_APPLICATION_ARB, "APPLICATION"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_OTHER_ARB, "OTHER"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_SHADER_COMPILER_ARB, "SHADER_COMPILER"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_THIRD_PARTY_ARB, "THIRD_PARTY"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB, "WINDOW_SYSTEM"); |
||||||
|
|
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB, "DEPRECATED_BEHAVIOR"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_ERROR_ARB, "ERROR"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_OTHER_ARB, "OTHER"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PERFORMANCE_ARB, "PERFORMANCE"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_PORTABILITY_ARB, "PORTABILITY"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB, "UNDEFINED_BEHAVIOR"); |
||||||
|
|
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_HIGH_ARB, "HIGH"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_MEDIUM_ARB, "MEDIUM"); |
||||||
|
constMap.put(ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB, "LOW"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void handleMessage(int source, int type, int id, int severity, String message) { |
||||||
|
String sourceStr = constMap.get(source); |
||||||
|
String typeStr = constMap.get(type); |
||||||
|
String severityStr = constMap.get(severity); |
||||||
|
|
||||||
|
System.err.println(String.format(MESSAGE_FORMAT, id, sourceStr, typeStr, severityStr, message)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,188 @@ |
|||||||
|
/* |
||||||
|
* $Id: SerializerRegistrationsMessage.java 3829 2014-11-24 07:25:43Z pspeed $ |
||||||
|
* |
||||||
|
* Copyright (c) 2012, Paul Speed |
||||||
|
* All rights reserved. |
||||||
|
*/ |
||||||
|
|
||||||
|
package com.jme3.network.message; |
||||||
|
|
||||||
|
import com.jme3.network.AbstractMessage; |
||||||
|
import com.jme3.network.serializing.Serializable; |
||||||
|
import com.jme3.network.serializing.Serializer; |
||||||
|
import com.jme3.network.serializing.SerializerRegistration; |
||||||
|
import com.jme3.network.serializing.serializers.FieldSerializer; |
||||||
|
import java.util.*; |
||||||
|
import java.util.jar.Attributes; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Holds a compiled set of message registration information that |
||||||
|
* can be sent over the wire. The received message can then be |
||||||
|
* used to register all of the classes using the same IDs and |
||||||
|
* same ordering, etc.. The intent is that the server compiles |
||||||
|
* this message once it is sure that all serializable classes have |
||||||
|
* been registered. It can then send this to each new client and |
||||||
|
* they can use it to register all of the classes without requiring |
||||||
|
* exactly reproducing the same calls that the server did to register |
||||||
|
* messages. |
||||||
|
* |
||||||
|
* <p>Normally, JME recommends that apps have a common utility method |
||||||
|
* that they call on both client and server. However, this makes |
||||||
|
* pluggable services nearly impossible as some central class has to |
||||||
|
* know about all registered serializers. This message implementation |
||||||
|
* gets around by only requiring registration on the server.</p> |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
@Serializable |
||||||
|
public class SerializerRegistrationsMessage extends AbstractMessage { |
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); |
||||||
|
|
||||||
|
public static final Set<Class> ignore = new HashSet<Class>(); |
||||||
|
static { |
||||||
|
// We could build this automatically but then we
|
||||||
|
// risk making a client and server out of date simply because
|
||||||
|
// their JME versions are out of date.
|
||||||
|
ignore.add(Boolean.class); |
||||||
|
ignore.add(Float.class); |
||||||
|
ignore.add(Boolean.class); |
||||||
|
ignore.add(Byte.class); |
||||||
|
ignore.add(Character.class); |
||||||
|
ignore.add(Short.class); |
||||||
|
ignore.add(Integer.class); |
||||||
|
ignore.add(Long.class); |
||||||
|
ignore.add(Float.class); |
||||||
|
ignore.add(Double.class); |
||||||
|
ignore.add(String.class); |
||||||
|
|
||||||
|
ignore.add(DisconnectMessage.class); |
||||||
|
ignore.add(ClientRegistrationMessage.class); |
||||||
|
|
||||||
|
ignore.add(Date.class); |
||||||
|
ignore.add(AbstractCollection.class); |
||||||
|
ignore.add(AbstractList.class); |
||||||
|
ignore.add(AbstractSet.class); |
||||||
|
ignore.add(ArrayList.class); |
||||||
|
ignore.add(HashSet.class); |
||||||
|
ignore.add(LinkedHashSet.class); |
||||||
|
ignore.add(LinkedList.class); |
||||||
|
ignore.add(TreeSet.class); |
||||||
|
ignore.add(Vector.class); |
||||||
|
ignore.add(AbstractMap.class); |
||||||
|
ignore.add(Attributes.class); |
||||||
|
ignore.add(HashMap.class); |
||||||
|
ignore.add(Hashtable.class); |
||||||
|
ignore.add(IdentityHashMap.class); |
||||||
|
ignore.add(TreeMap.class); |
||||||
|
ignore.add(WeakHashMap.class); |
||||||
|
ignore.add(Enum.class); |
||||||
|
|
||||||
|
ignore.add(GZIPCompressedMessage.class); |
||||||
|
ignore.add(ZIPCompressedMessage.class); |
||||||
|
|
||||||
|
ignore.add(ChannelInfoMessage.class); |
||||||
|
|
||||||
|
ignore.add(SerializerRegistrationsMessage.class); |
||||||
|
ignore.add(SerializerRegistrationsMessage.Registration.class); |
||||||
|
} |
||||||
|
|
||||||
|
public static SerializerRegistrationsMessage INSTANCE; |
||||||
|
public static Registration[] compiled; |
||||||
|
private static final Serializer fieldSerializer = new FieldSerializer(); |
||||||
|
|
||||||
|
private Registration[] registrations; |
||||||
|
|
||||||
|
public SerializerRegistrationsMessage() { |
||||||
|
setReliable(true); |
||||||
|
} |
||||||
|
|
||||||
|
public SerializerRegistrationsMessage( Registration... registrations ) { |
||||||
|
setReliable(true); |
||||||
|
this.registrations = registrations; |
||||||
|
} |
||||||
|
|
||||||
|
public static void compile() { |
||||||
|
|
||||||
|
// Let's just see what they are here
|
||||||
|
List<Registration> list = new ArrayList<Registration>(); |
||||||
|
for( SerializerRegistration reg : Serializer.getSerializerRegistrations() ) { |
||||||
|
Class type = reg.getType(); |
||||||
|
if( ignore.contains(type) ) |
||||||
|
continue; |
||||||
|
if( type.isPrimitive() ) |
||||||
|
continue; |
||||||
|
|
||||||
|
list.add(new Registration(reg)); |
||||||
|
} |
||||||
|
|
||||||
|
if( log.isLoggable(Level.FINE) ) { |
||||||
|
log.log( Level.FINE, "Number of registered classes:{0}", list.size()); |
||||||
|
for( Registration reg : list ) { |
||||||
|
log.log( Level.FINE, " {0}", reg); |
||||||
|
} |
||||||
|
} |
||||||
|
compiled = list.toArray(new Registration[list.size()]); |
||||||
|
|
||||||
|
INSTANCE = new SerializerRegistrationsMessage(compiled); |
||||||
|
} |
||||||
|
|
||||||
|
public void registerAll() { |
||||||
|
for( Registration reg : registrations ) { |
||||||
|
log.log( Level.INFO, "Registering:{0}", reg); |
||||||
|
reg.register(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Serializable |
||||||
|
public static final class Registration { |
||||||
|
|
||||||
|
private short id; |
||||||
|
private String className; |
||||||
|
private String serializerClassName; |
||||||
|
|
||||||
|
public Registration() { |
||||||
|
} |
||||||
|
|
||||||
|
public Registration( SerializerRegistration reg ) { |
||||||
|
|
||||||
|
this.id = reg.getId(); |
||||||
|
this.className = reg.getType().getName(); |
||||||
|
if( reg.getSerializer().getClass() != FieldSerializer.class ) { |
||||||
|
this.serializerClassName = reg.getSerializer().getClass().getName(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void register() { |
||||||
|
try { |
||||||
|
Class type = Class.forName(className); |
||||||
|
Serializer serializer; |
||||||
|
if( serializerClassName == null ) { |
||||||
|
serializer = fieldSerializer; |
||||||
|
} else { |
||||||
|
Class serializerType = Class.forName(serializerClassName); |
||||||
|
serializer = (Serializer)serializerType.newInstance(); |
||||||
|
} |
||||||
|
SerializerRegistration result = Serializer.registerClassForId(id, type, serializer); |
||||||
|
log.log( Level.FINE, " result:{0}", result); |
||||||
|
} catch( ClassNotFoundException e ) { |
||||||
|
throw new RuntimeException( "Class not found attempting to register:" + this, e ); |
||||||
|
} catch( InstantiationException e ) { |
||||||
|
throw new RuntimeException( "Error instantiating serializer registering:" + this, e ); |
||||||
|
} catch( IllegalAccessException e ) { |
||||||
|
throw new RuntimeException( "Error instantiating serializer registering:" + this, e ); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "Registration[" + id + " = " + className + ", serializer=" + serializerClassName + "]"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,51 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Convenient base class for ClientServices providing some default ClientService |
||||||
|
* interface implementations as well as a few convenience methods |
||||||
|
* such as getServiceManager() and getService(type). Subclasses |
||||||
|
* must at least override the onInitialize() method to handle |
||||||
|
* service initialization. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public abstract class AbstractClientService extends AbstractService<ClientServiceManager> |
||||||
|
implements ClientService { |
||||||
|
|
||||||
|
protected AbstractClientService() { |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
import com.jme3.network.HostedConnection; |
||||||
|
import com.jme3.network.Server; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Convenient base class for HostedServices providing some default HostedService |
||||||
|
* interface implementations as well as a few convenience methods |
||||||
|
* such as getServiceManager() and getService(type). Subclasses |
||||||
|
* must at least override the onInitialize() method to handle |
||||||
|
* service initialization. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public abstract class AbstractHostedService extends AbstractService<HostedServiceManager> |
||||||
|
implements HostedService { |
||||||
|
|
||||||
|
protected AbstractHostedService() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Default implementation does nothing. Implementations can |
||||||
|
* override this to peform custom new connection behavior. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void connectionAdded(Server server, HostedConnection hc) { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Default implementation does nothing. Implementations can |
||||||
|
* override this to peform custom leaving connection behavior. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void connectionRemoved(Server server, HostedConnection hc) { |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Base class providing some default Service interface implementations |
||||||
|
* as well as a few convenience methods such as getServiceManager() |
||||||
|
* and getService(type). Subclasses must at least override the |
||||||
|
* onInitialize() method to handle service initialization. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public abstract class AbstractService<S extends ServiceManager> implements Service<S> { |
||||||
|
|
||||||
|
private S serviceManager; |
||||||
|
|
||||||
|
protected AbstractService() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the ServiceManager that was passed to |
||||||
|
* initialize() during service initialization. |
||||||
|
*/ |
||||||
|
protected S getServiceManager() { |
||||||
|
return serviceManager; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the first sibling service of the specified |
||||||
|
* type. |
||||||
|
*/ |
||||||
|
protected <T extends Service<S>> T getService( Class<T> type ) { |
||||||
|
return type.cast(serviceManager.getService(type)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes this service by keeping a reference to |
||||||
|
* the service manager and calling onInitialize(). |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public final void initialize( S serviceManager ) { |
||||||
|
this.serviceManager = serviceManager; |
||||||
|
onInitialize(serviceManager); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called during initialize() for the subclass to perform |
||||||
|
* implementation specific initialization. |
||||||
|
*/ |
||||||
|
protected abstract void onInitialize( S serviceManager ); |
||||||
|
|
||||||
|
/** |
||||||
|
* Default implementation does nothing. Implementations can |
||||||
|
* override this to peform custom startup behavior. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void start() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Default implementation does nothing. Implementations can |
||||||
|
* override this to peform custom stop behavior. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void stop() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Default implementation does nothing. Implementations can |
||||||
|
* override this to peform custom termination behavior. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void terminate( S serviceManager ) { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return getClass().getName() + "[serviceManager=" + serviceManager + "]"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Interface implemented by Client-side services that augment |
||||||
|
* a network Client's functionality. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public interface ClientService extends Service<ClientServiceManager> { |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service is first attached to the service |
||||||
|
* manager. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void initialize( ClientServiceManager serviceManager ); |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service manager is started or if the |
||||||
|
* service is added to an already started service manager. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void start(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service is shutting down. All services |
||||||
|
* are stopped and any service manager resources are closed |
||||||
|
* before the services are terminated. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void stop(); |
||||||
|
|
||||||
|
/** |
||||||
|
* The service manager is fully shutting down. All services |
||||||
|
* have been stopped and related connections closed. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void terminate( ClientServiceManager serviceManager ); |
||||||
|
} |
@ -0,0 +1,100 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
import com.jme3.network.Client; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Manages ClientServices on behalf of a network Client object. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class ClientServiceManager extends ServiceManager<ClientServiceManager> { |
||||||
|
|
||||||
|
private Client client; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new ClientServiceManager for the specified network Client. |
||||||
|
*/ |
||||||
|
public ClientServiceManager( Client client ) { |
||||||
|
this.client = client; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the network Client associated with this ClientServiceManager. |
||||||
|
*/ |
||||||
|
public Client getClient() { |
||||||
|
return client; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns 'this' and is what is passed to ClientService.initialize() |
||||||
|
* and ClientService.termnate(); |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected final ClientServiceManager getParent() { |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds the specified ClientService and initializes it. If the service manager |
||||||
|
* has already been started then the service will also be started. |
||||||
|
*/ |
||||||
|
public void addService( ClientService s ) { |
||||||
|
super.addService(s); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds all of the specified ClientServices and initializes them. If the service manager |
||||||
|
* has already been started then the services will also be started. |
||||||
|
* This is a convenience method that delegates to addService(), thus each |
||||||
|
* service will be initialized (and possibly started) in sequence rather |
||||||
|
* than doing them all at the end. |
||||||
|
*/ |
||||||
|
public void addServices( ClientService... services ) { |
||||||
|
for( ClientService s : services ) { |
||||||
|
super.addService(s); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes the specified ClientService from this service manager, stopping |
||||||
|
* and terminating it as required. If this service manager is in a |
||||||
|
* started state then the service will be stopped. After removal, |
||||||
|
* the service will be terminated. |
||||||
|
*/ |
||||||
|
public void removeService( ClientService s ) { |
||||||
|
super.removeService(s); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,74 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
import com.jme3.network.ConnectionListener; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Interface implemented by Server-side services that augment |
||||||
|
* a network Server's functionality. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public interface HostedService extends Service<HostedServiceManager>, ConnectionListener { |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service is first attached to the service |
||||||
|
* manager. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void initialize( HostedServiceManager serviceManager ); |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service manager is started or if the |
||||||
|
* service is added to an already started service manager. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void start(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service is shutting down. All services |
||||||
|
* are stopped and any service manager resources are closed |
||||||
|
* before the services are terminated. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void stop(); |
||||||
|
|
||||||
|
/** |
||||||
|
* The service manager is fully shutting down. All services |
||||||
|
* have been stopped and related connections closed. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void terminate( HostedServiceManager serviceManager ); |
||||||
|
} |
@ -0,0 +1,140 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
import com.jme3.network.ConnectionListener; |
||||||
|
import com.jme3.network.HostedConnection; |
||||||
|
import com.jme3.network.Server; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Manages HostedServices on behalf of a network Server object. |
||||||
|
* All HostedServices are automatically informed about new and |
||||||
|
* leaving connections. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class HostedServiceManager extends ServiceManager<HostedServiceManager> { |
||||||
|
|
||||||
|
private Server server; |
||||||
|
private ConnectionObserver connectionObserver; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a HostedServiceManager for the specified network Server. |
||||||
|
*/ |
||||||
|
public HostedServiceManager( Server server ) { |
||||||
|
this.server = server; |
||||||
|
this.connectionObserver = new ConnectionObserver(); |
||||||
|
server.addConnectionListener(connectionObserver); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the network Server associated with this HostedServiceManager. |
||||||
|
*/ |
||||||
|
public Server getServer() { |
||||||
|
return server; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns 'this' and is what is passed to HostedService.initialize() |
||||||
|
* and HostedService.termnate(); |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected final HostedServiceManager getParent() { |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds the specified HostedService and initializes it. If the service manager |
||||||
|
* has already been started then the service will also be started. |
||||||
|
*/ |
||||||
|
public void addService( HostedService s ) { |
||||||
|
super.addService(s); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds all of the specified HostedServices and initializes them. If the service manager |
||||||
|
* has already been started then the services will also be started. |
||||||
|
* This is a convenience method that delegates to addService(), thus each |
||||||
|
* service will be initialized (and possibly started) in sequence rather |
||||||
|
* than doing them all at the end. |
||||||
|
*/ |
||||||
|
public void addServices( HostedService... services ) { |
||||||
|
for( HostedService s : services ) { |
||||||
|
super.addService(s); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes the specified HostedService from this service manager, stopping |
||||||
|
* and terminating it as required. If this service manager is in a |
||||||
|
* started state then the service will be stopped. After removal, |
||||||
|
* the service will be terminated. |
||||||
|
*/ |
||||||
|
public void removeService( HostedService s ) { |
||||||
|
super.removeService(s); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called internally when a new connection has been added so that the |
||||||
|
* services can be notified. |
||||||
|
*/ |
||||||
|
protected void addConnection( HostedConnection hc ) { |
||||||
|
for( Service s : getServices() ) { |
||||||
|
((HostedService)s).connectionAdded(server, hc); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called internally when a connection has been removed so that the |
||||||
|
* services can be notified. |
||||||
|
*/ |
||||||
|
protected void removeConnection( HostedConnection hc ) { |
||||||
|
for( Service s : getServices() ) { |
||||||
|
((HostedService)s).connectionRemoved(server, hc); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected class ConnectionObserver implements ConnectionListener { |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectionAdded(Server server, HostedConnection hc) { |
||||||
|
addConnection(hc); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectionRemoved(Server server, HostedConnection hc) { |
||||||
|
removeConnection(hc); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* The base interface for managed services. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public interface Service<S> { |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service is first attached to the service |
||||||
|
* manager. |
||||||
|
*/ |
||||||
|
public void initialize( S serviceManager ); |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service manager is started or if the |
||||||
|
* service is added to an already started service manager. |
||||||
|
*/ |
||||||
|
public void start(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when the service manager is shutting down. All services |
||||||
|
* are stopped and any service manager resources are closed |
||||||
|
* before the services are terminated. |
||||||
|
*/ |
||||||
|
public void stop(); |
||||||
|
|
||||||
|
/** |
||||||
|
* The service manager is fully shutting down. All services |
||||||
|
* have been stopped and related connections closed. |
||||||
|
*/ |
||||||
|
public void terminate( S serviceManager ); |
||||||
|
} |
@ -0,0 +1,160 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
import java.util.concurrent.CopyOnWriteArrayList; |
||||||
|
|
||||||
|
/** |
||||||
|
* The base service manager class from which the HostedServiceManager |
||||||
|
* and ClientServiceManager classes are derived. This manages the |
||||||
|
* the underlying services and their life cycles. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public abstract class ServiceManager<T> { |
||||||
|
|
||||||
|
private List<Service<T>> services = new CopyOnWriteArrayList<Service<T>>(); |
||||||
|
private volatile boolean started = false; |
||||||
|
|
||||||
|
protected ServiceManager() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retreives the 'parent' of this service manager, usually |
||||||
|
* a more specifically typed version of 'this' but it can be |
||||||
|
* anything the seervices are expecting. |
||||||
|
*/ |
||||||
|
protected abstract T getParent(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the complete list of services managed by this |
||||||
|
* service manager. This list is thread safe following the |
||||||
|
* CopyOnWriteArrayList semantics. |
||||||
|
*/ |
||||||
|
protected List<Service<T>> getServices() { |
||||||
|
return services; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Starts this service manager and all services that it contains. |
||||||
|
* Any services added after the service manager has started will have |
||||||
|
* their start() methods called. |
||||||
|
*/ |
||||||
|
public void start() { |
||||||
|
if( started ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for( Service<T> s : services ) { |
||||||
|
s.start(); |
||||||
|
} |
||||||
|
started = true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if this service manager has been started. |
||||||
|
*/ |
||||||
|
public boolean isStarted() { |
||||||
|
return started; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Stops all services and puts the service manager into a stopped state. |
||||||
|
*/ |
||||||
|
public void stop() { |
||||||
|
if( !started ) { |
||||||
|
throw new IllegalStateException(getClass().getSimpleName() + " not started."); |
||||||
|
} |
||||||
|
for( Service<T> s : services ) { |
||||||
|
s.stop(); |
||||||
|
} |
||||||
|
started = false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds the specified service and initializes it. If the service manager |
||||||
|
* has already been started then the service will also be started. |
||||||
|
*/ |
||||||
|
public <S extends Service<T>> void addService( S s ) { |
||||||
|
services.add(s); |
||||||
|
s.initialize(getParent()); |
||||||
|
if( started ) { |
||||||
|
s.start(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes the specified service from this service manager, stopping |
||||||
|
* and terminating it as required. If this service manager is in a |
||||||
|
* started state then the service will be stopped. After removal, |
||||||
|
* the service will be terminated. |
||||||
|
*/ |
||||||
|
public <S extends Service<T>> void removeService( S s ) { |
||||||
|
if( started ) { |
||||||
|
s.stop(); |
||||||
|
} |
||||||
|
services.remove(s); |
||||||
|
s.terminate(getParent()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Terminates all services. If the service manager has not been |
||||||
|
* stopped yet then it will be stopped. |
||||||
|
*/ |
||||||
|
public void terminate() { |
||||||
|
if( started ) { |
||||||
|
stop(); |
||||||
|
} |
||||||
|
for( Service<T> s : services ) { |
||||||
|
s.terminate(getParent()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the first service of the specified type. |
||||||
|
*/ |
||||||
|
public <S extends Service<T>> S getService( Class<S> type ) { |
||||||
|
for( Service s : services ) { |
||||||
|
if( type.isInstance(s) ) { |
||||||
|
return type.cast(s); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return getClass().getName() + "[services=" + services + "]"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,123 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.rpc; |
||||||
|
|
||||||
|
import com.jme3.network.Client; |
||||||
|
import com.jme3.network.util.ObjectMessageDelegator; |
||||||
|
import com.jme3.network.service.AbstractClientService; |
||||||
|
import com.jme3.network.service.ClientServiceManager; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* RPC service that can be added to a network Client to |
||||||
|
* add RPC send/receive capabilities. Remote procedure |
||||||
|
* calls can be made to the server and responses retrieved. |
||||||
|
* Any remote procedure calls that the server performs for |
||||||
|
* this connection will be received by this service and delegated |
||||||
|
* to the appropriate RpcHandlers. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class RpcClientService extends AbstractClientService { |
||||||
|
|
||||||
|
private RpcConnection rpc; |
||||||
|
private ObjectMessageDelegator delegator; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new RpcClientService that can be registered |
||||||
|
* with the network Client object. |
||||||
|
*/ |
||||||
|
public RpcClientService() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used internally to setup the RpcConnection and MessageDelegator. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void onInitialize( ClientServiceManager serviceManager ) { |
||||||
|
Client client = serviceManager.getClient(); |
||||||
|
this.rpc = new RpcConnection(client); |
||||||
|
|
||||||
|
delegator = new ObjectMessageDelegator(rpc, true); |
||||||
|
client.addMessageListener(delegator, delegator.getMessageTypes()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used internally to unregister the RPC MessageDelegator that |
||||||
|
* was previously added to the network Client. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void terminate( ClientServiceManager serviceManager ) { |
||||||
|
Client client = serviceManager.getClient(); |
||||||
|
client.removeMessageListener(delegator, delegator.getMessageTypes()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs a synchronous call on the server against the specified |
||||||
|
* object using the specified procedure ID. Both inboud and outbound |
||||||
|
* communication is done on the specified channel. |
||||||
|
*/ |
||||||
|
public Object callAndWait( byte channel, short objId, short procId, Object... args ) { |
||||||
|
return rpc.callAndWait(channel, objId, procId, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs an asynchronous call on the server against the specified |
||||||
|
* object using the specified procedure ID. Communication is done |
||||||
|
* over the specified channel. No responses are received and none |
||||||
|
* are waited for. |
||||||
|
*/ |
||||||
|
public void callAsync( byte channel, short objId, short procId, Object... args ) { |
||||||
|
rpc.callAsync(channel, objId, procId, args); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Register a handler that will be called when the server |
||||||
|
* performs a remove procedure call against this client. |
||||||
|
* Only one handler per object ID can be registered at any given time, |
||||||
|
* though the same handler can be registered for multiple object |
||||||
|
* IDs. |
||||||
|
*/ |
||||||
|
public void registerHandler( short objId, RpcHandler handler ) { |
||||||
|
rpc.registerHandler(objId, handler); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes a previously registered handler for the specified |
||||||
|
* object ID. |
||||||
|
*/ |
||||||
|
public void removeHandler( short objId, RpcHandler handler ) { |
||||||
|
rpc.removeHandler(objId, handler); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,260 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.rpc; |
||||||
|
|
||||||
|
import com.jme3.network.MessageConnection; |
||||||
|
import com.jme3.network.service.rpc.msg.RpcCallMessage; |
||||||
|
import com.jme3.network.service.rpc.msg.RpcResponseMessage; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
import java.util.concurrent.atomic.AtomicLong; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Wraps a message connection to provide RPC call support. This |
||||||
|
* is used internally by the RpcClientService and RpcHostedService to manage |
||||||
|
* network messaging. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class RpcConnection { |
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(RpcConnection.class.getName()); |
||||||
|
|
||||||
|
/** |
||||||
|
* The underlying connection upon which RPC call messages are sent |
||||||
|
* and RPC response messages are received. It can be a Client or |
||||||
|
* a HostedConnection depending on the mode of the RPC service. |
||||||
|
*/ |
||||||
|
private MessageConnection connection; |
||||||
|
|
||||||
|
/** |
||||||
|
* The objectId index of RpcHandler objects that are used to perform the |
||||||
|
* RPC calls for a particular object. |
||||||
|
*/ |
||||||
|
private Map<Short, RpcHandler> handlers = new ConcurrentHashMap<Short, RpcHandler>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides unique messages IDs for outbound synchronous call |
||||||
|
* messages. These are then used in the responses index to |
||||||
|
* locate the proper ResponseHolder objects. |
||||||
|
*/ |
||||||
|
private AtomicLong sequenceNumber = new AtomicLong(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Tracks the ResponseHolder objects for sent message IDs. When the |
||||||
|
* response is received, the appropriate handler is found here and the |
||||||
|
* response or error set, thus releasing the waiting caller. |
||||||
|
*/ |
||||||
|
private Map<Long, ResponseHolder> responses = new ConcurrentHashMap<Long, ResponseHolder>(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new RpcConnection for the specified network connection. |
||||||
|
*/ |
||||||
|
public RpcConnection( MessageConnection connection ) { |
||||||
|
this.connection = connection; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Clears any pending synchronous calls causing them to |
||||||
|
* throw an exception with the message "Closing connection". |
||||||
|
*/ |
||||||
|
public void close() { |
||||||
|
// Let any pending waits go free
|
||||||
|
for( ResponseHolder holder : responses.values() ) { |
||||||
|
holder.release(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs a remote procedure call with the specified arguments and waits |
||||||
|
* for the response. Both the outbound message and inbound response will |
||||||
|
* be sent on the specified channel. |
||||||
|
*/ |
||||||
|
public Object callAndWait( byte channel, short objId, short procId, Object... args ) { |
||||||
|
|
||||||
|
RpcCallMessage msg = new RpcCallMessage(sequenceNumber.getAndIncrement(), |
||||||
|
channel, objId, procId, args); |
||||||
|
|
||||||
|
// Need to register an object so we can wait for the response.
|
||||||
|
// ...before we send it. Just in case.
|
||||||
|
ResponseHolder holder = new ResponseHolder(msg); |
||||||
|
responses.put(msg.getMessageId(), holder); |
||||||
|
|
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); |
||||||
|
} |
||||||
|
if( channel >= 0 ) { |
||||||
|
connection.send(channel, msg); |
||||||
|
} else { |
||||||
|
connection.send(msg); |
||||||
|
} |
||||||
|
|
||||||
|
return holder.getResponse(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs a remote procedure call with the specified arguments but does |
||||||
|
* not wait for a response. The outbound message is sent on the specified channel. |
||||||
|
* There is no inbound response message. |
||||||
|
*/ |
||||||
|
public void callAsync( byte channel, short objId, short procId, Object... args ) { |
||||||
|
|
||||||
|
RpcCallMessage msg = new RpcCallMessage(-1, channel, objId, procId, args); |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); |
||||||
|
} |
||||||
|
connection.send(channel, msg); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Register a handler that can be called by the other end |
||||||
|
* of the connection using the specified object ID. Only one |
||||||
|
* handler per object ID can be registered at any given time, |
||||||
|
* though the same handler can be registered for multiple object |
||||||
|
* IDs. |
||||||
|
*/ |
||||||
|
public void registerHandler( short objId, RpcHandler handler ) { |
||||||
|
handlers.put(objId, handler); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes a previously registered handler for the specified |
||||||
|
* object ID. |
||||||
|
*/ |
||||||
|
public void removeHandler( short objId, RpcHandler handler ) { |
||||||
|
RpcHandler removing = handlers.get(objId); |
||||||
|
if( handler != removing ) { |
||||||
|
throw new IllegalArgumentException("Handler not registered for object ID:" |
||||||
|
+ objId + ", handler:" + handler ); |
||||||
|
} |
||||||
|
handlers.remove(objId); |
||||||
|
} |
||||||
|
|
||||||
|
protected void send( byte channel, RpcResponseMessage msg ) { |
||||||
|
if( channel >= 0 ) { |
||||||
|
connection.send(channel, msg); |
||||||
|
} else { |
||||||
|
connection.send(msg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called internally when an RpcCallMessage is received from |
||||||
|
* the remote connection. |
||||||
|
*/ |
||||||
|
public void handleMessage( RpcCallMessage msg ) { |
||||||
|
|
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "handleMessage({0})", msg); |
||||||
|
} |
||||||
|
RpcHandler handler = handlers.get(msg.getObjectId()); |
||||||
|
try { |
||||||
|
if( handler == null ) { |
||||||
|
throw new RuntimeException("Handler not found for objectID:" + msg.getObjectId()); |
||||||
|
} |
||||||
|
Object result = handler.call(this, msg.getObjectId(), msg.getProcedureId(), msg.getArguments()); |
||||||
|
if( !msg.isAsync() ) { |
||||||
|
send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), result)); |
||||||
|
} |
||||||
|
} catch( Exception e ) { |
||||||
|
if( !msg.isAsync() ) { |
||||||
|
send(msg.getChannel(), new RpcResponseMessage(msg.getMessageId(), e)); |
||||||
|
} else { |
||||||
|
log.log(Level.SEVERE, "Error invoking async call for:" + msg, e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called internally when an RpcResponseMessage is received from |
||||||
|
* the remote connection. |
||||||
|
*/ |
||||||
|
public void handleMessage( RpcResponseMessage msg ) { |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "handleMessage({0})", msg); |
||||||
|
} |
||||||
|
ResponseHolder holder = responses.remove(msg.getMessageId()); |
||||||
|
if( holder == null ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
holder.setResponse(msg); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sort of like a Future, holds a locked reference to a response |
||||||
|
* until the remote call has completed and returned a response. |
||||||
|
*/ |
||||||
|
private class ResponseHolder { |
||||||
|
private Object response; |
||||||
|
private String error; |
||||||
|
private RpcCallMessage msg; |
||||||
|
boolean received = false; |
||||||
|
|
||||||
|
public ResponseHolder( RpcCallMessage msg ) { |
||||||
|
this.msg = msg; |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void setResponse( RpcResponseMessage msg ) { |
||||||
|
this.response = msg.getResult(); |
||||||
|
this.error = msg.getError(); |
||||||
|
this.received = true; |
||||||
|
notifyAll(); |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized Object getResponse() { |
||||||
|
try { |
||||||
|
while(!received) { |
||||||
|
wait(); |
||||||
|
} |
||||||
|
} catch( InterruptedException e ) { |
||||||
|
throw new RuntimeException("Interrupted waiting for respone to:" + msg, e); |
||||||
|
} |
||||||
|
if( error != null ) { |
||||||
|
throw new RuntimeException("Error calling remote procedure:" + msg + "\n" + error); |
||||||
|
} |
||||||
|
return response; |
||||||
|
} |
||||||
|
|
||||||
|
public synchronized void release() { |
||||||
|
if( received ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// Else signal an error for the callers
|
||||||
|
this.error = "Closing connection"; |
||||||
|
this.received = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.rpc; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Implementations of this interface can be registered with |
||||||
|
* the RpcClientService or RpcHostService to handle the |
||||||
|
* remote procedure calls for a given object or objects. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public interface RpcHandler { |
||||||
|
|
||||||
|
/** |
||||||
|
* Called when a remote procedure call request is received for a particular |
||||||
|
* object from the other end of the network connection. |
||||||
|
*/ |
||||||
|
public Object call( RpcConnection conn, short objectId, short procId, Object... args ); |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,227 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.rpc; |
||||||
|
|
||||||
|
import com.jme3.network.HostedConnection; |
||||||
|
import com.jme3.network.Server; |
||||||
|
import com.jme3.network.serializing.Serializer; |
||||||
|
import com.jme3.network.util.SessionDataDelegator; |
||||||
|
import com.jme3.network.service.AbstractHostedService; |
||||||
|
import com.jme3.network.service.HostedServiceManager; |
||||||
|
import com.jme3.network.service.rpc.msg.RpcCallMessage; |
||||||
|
import com.jme3.network.service.rpc.msg.RpcResponseMessage; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* RPC service that can be added to a network Server to |
||||||
|
* add RPC send/receive capabilities. For a particular |
||||||
|
* HostedConnection, Remote procedure calls can be made to the |
||||||
|
* associated Client and responses retrieved. Any remote procedure |
||||||
|
* calls that the Client performs for this connection will be |
||||||
|
* received by this service and delegated to the appropriate RpcHandlers. |
||||||
|
* |
||||||
|
* Note: it can be dangerous for a server to perform synchronous |
||||||
|
* RPC calls to a client but especially so if not done as part |
||||||
|
* of the response to some other message. ie: iterating over all |
||||||
|
* or some HostedConnections to perform synchronous RPC calls |
||||||
|
* will be slow and potentially block the server's threads in ways |
||||||
|
* that can cause deadlocks or odd contention. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class RpcHostedService extends AbstractHostedService { |
||||||
|
|
||||||
|
private static final String ATTRIBUTE_NAME = "rpcSession"; |
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); |
||||||
|
|
||||||
|
private boolean autoHost; |
||||||
|
private SessionDataDelegator delegator; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new RPC host service that can be registered |
||||||
|
* with the Network server and will automatically 'host' |
||||||
|
* RPC services and each new network connection. |
||||||
|
*/ |
||||||
|
public RpcHostedService() { |
||||||
|
this(true); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new RPC host service that can be registered |
||||||
|
* with the Network server and will optionally 'host' |
||||||
|
* RPC services and each new network connection depending |
||||||
|
* on the specified 'autoHost' flag. |
||||||
|
*/ |
||||||
|
public RpcHostedService( boolean autoHost ) { |
||||||
|
this.autoHost = autoHost; |
||||||
|
|
||||||
|
// This works for me... has to be different in
|
||||||
|
// the general case
|
||||||
|
Serializer.registerClasses(RpcCallMessage.class, RpcResponseMessage.class); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used internally to setup the message delegator that will |
||||||
|
* handle HostedConnection specific messages and forward them |
||||||
|
* to that connection's RpcConnection. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
protected void onInitialize( HostedServiceManager serviceManager ) { |
||||||
|
Server server = serviceManager.getServer(); |
||||||
|
|
||||||
|
// A general listener for forwarding the messages
|
||||||
|
// to the client-specific handler
|
||||||
|
this.delegator = new SessionDataDelegator(RpcConnection.class, |
||||||
|
ATTRIBUTE_NAME, |
||||||
|
true); |
||||||
|
server.addMessageListener(delegator, delegator.getMessageTypes()); |
||||||
|
|
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "Registered delegator for message types:{0}", Arrays.asList(delegator.getMessageTypes())); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* When set to true, all new connections will automatically have |
||||||
|
* RPC hosting services attached to them, meaning they can send |
||||||
|
* and receive RPC calls. If this is set to false then it is up |
||||||
|
* to other services to eventually call startHostingOnConnection(). |
||||||
|
* |
||||||
|
* <p>Reasons for doing this vary but usually would be because |
||||||
|
* the client shouldn't be allowed to perform any RPC calls until |
||||||
|
* it has provided more information. In general, this is unnecessary |
||||||
|
* because the RpcHandler registries are not shared. Each client |
||||||
|
* gets their own and RPC calls will fail until the appropriate |
||||||
|
* objects have been registtered.</p> |
||||||
|
*/ |
||||||
|
public void setAutoHost( boolean b ) { |
||||||
|
this.autoHost = b; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if this service automatically attaches RPC |
||||||
|
* hosting capabilities to new connections. |
||||||
|
*/ |
||||||
|
public boolean getAutoHost() { |
||||||
|
return autoHost; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the RpcConnection for the specified HostedConnection |
||||||
|
* if that HostedConnection has had RPC services started using |
||||||
|
* startHostingOnConnection() (or via autohosting). Returns null |
||||||
|
* if the connection currently doesn't have RPC hosting services |
||||||
|
* attached. |
||||||
|
*/ |
||||||
|
public RpcConnection getRpcConnection( HostedConnection hc ) { |
||||||
|
return hc.getAttribute(ATTRIBUTE_NAME); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets up RPC hosting services for the hosted connection allowing |
||||||
|
* getRpcConnection() to return a valid RPC connection object. |
||||||
|
* This method is called automatically for all new connections if |
||||||
|
* autohost is set to true. |
||||||
|
*/ |
||||||
|
public void startHostingOnConnection( HostedConnection hc ) { |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "startHostingOnConnection:{0}", hc); |
||||||
|
} |
||||||
|
hc.setAttribute(ATTRIBUTE_NAME, new RpcConnection(hc)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes any RPC hosting services associated with the specified |
||||||
|
* connection. Calls to getRpcConnection() will return null for |
||||||
|
* this connection. The connection's RpcConnection is also closed, |
||||||
|
* releasing any waiting synchronous calls with a "Connection closing" |
||||||
|
* error. |
||||||
|
* This method is called automatically for all leaving connections if |
||||||
|
* autohost is set to true. |
||||||
|
*/ |
||||||
|
public void stopHostingOnConnection( HostedConnection hc ) { |
||||||
|
RpcConnection rpc = hc.getAttribute(ATTRIBUTE_NAME); |
||||||
|
if( rpc == null ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc); |
||||||
|
} |
||||||
|
hc.setAttribute(ATTRIBUTE_NAME, null); |
||||||
|
rpc.close(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Used internally to remove the message delegator from the |
||||||
|
* server. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void terminate(HostedServiceManager serviceManager) { |
||||||
|
Server server = serviceManager.getServer(); |
||||||
|
server.removeMessageListener(delegator, delegator.getMessageTypes()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called internally when a new connection is detected for |
||||||
|
* the server. If the current autoHost property is true then |
||||||
|
* startHostingOnConnection(hc) is called. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void connectionAdded(Server server, HostedConnection hc) { |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc}); |
||||||
|
} |
||||||
|
if( autoHost ) { |
||||||
|
startHostingOnConnection(hc); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called internally when an existing connection is leaving |
||||||
|
* the server. If the current autoHost property is true then |
||||||
|
* stopHostingOnConnection(hc) is called. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void connectionRemoved(Server server, HostedConnection hc) { |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc}); |
||||||
|
} |
||||||
|
stopHostingOnConnection(hc); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
@ -0,0 +1,98 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.rpc.msg; |
||||||
|
|
||||||
|
import com.jme3.network.AbstractMessage; |
||||||
|
import com.jme3.network.serializing.Serializable; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Used internally to send RPC call information to |
||||||
|
* the other end of a connection for execution. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
@Serializable |
||||||
|
public class RpcCallMessage extends AbstractMessage { |
||||||
|
|
||||||
|
private long msgId; |
||||||
|
private byte channel; |
||||||
|
private short objId; |
||||||
|
private short procId; |
||||||
|
private Object[] args; |
||||||
|
|
||||||
|
public RpcCallMessage() { |
||||||
|
} |
||||||
|
|
||||||
|
public RpcCallMessage( long msgId, byte channel, short objId, short procId, Object... args ) { |
||||||
|
this.msgId = msgId; |
||||||
|
this.channel = channel; |
||||||
|
this.objId = objId; |
||||||
|
this.procId = procId; |
||||||
|
this.args = args; |
||||||
|
} |
||||||
|
|
||||||
|
public long getMessageId() { |
||||||
|
return msgId; |
||||||
|
} |
||||||
|
|
||||||
|
public byte getChannel() { |
||||||
|
return channel; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean isAsync() { |
||||||
|
return msgId == -1; |
||||||
|
} |
||||||
|
|
||||||
|
public short getObjectId() { |
||||||
|
return objId; |
||||||
|
} |
||||||
|
|
||||||
|
public short getProcedureId() { |
||||||
|
return procId; |
||||||
|
} |
||||||
|
|
||||||
|
public Object[] getArguments() { |
||||||
|
return args; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return getClass().getSimpleName() + "[#" + msgId + ", channel=" + channel |
||||||
|
+ (isAsync() ? ", async" : ", sync") |
||||||
|
+ ", objId=" + objId |
||||||
|
+ ", procId=" + procId |
||||||
|
+ ", args.length=" + args.length |
||||||
|
+ "]"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,89 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.rpc.msg; |
||||||
|
|
||||||
|
import com.jme3.network.AbstractMessage; |
||||||
|
import com.jme3.network.serializing.Serializable; |
||||||
|
import java.io.PrintWriter; |
||||||
|
import java.io.StringWriter; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Used internally to send an RPC call's response back to |
||||||
|
* the caller. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
@Serializable |
||||||
|
public class RpcResponseMessage extends AbstractMessage { |
||||||
|
|
||||||
|
private long msgId; |
||||||
|
private Object result; |
||||||
|
private String error; |
||||||
|
|
||||||
|
public RpcResponseMessage() { |
||||||
|
} |
||||||
|
|
||||||
|
public RpcResponseMessage( long msgId, Object result ) { |
||||||
|
this.msgId = msgId; |
||||||
|
this.result = result; |
||||||
|
} |
||||||
|
|
||||||
|
public RpcResponseMessage( long msgId, Throwable t ) { |
||||||
|
this.msgId = msgId; |
||||||
|
|
||||||
|
StringWriter sOut = new StringWriter(); |
||||||
|
PrintWriter out = new PrintWriter(sOut); |
||||||
|
t.printStackTrace(out); |
||||||
|
out.close(); |
||||||
|
this.error = sOut.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
public long getMessageId() { |
||||||
|
return msgId; |
||||||
|
} |
||||||
|
|
||||||
|
public Object getResult() { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public String getError() { |
||||||
|
return error; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return getClass().getSimpleName() + "[#" + msgId + ", result=" + result |
||||||
|
+ "]"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.serializer; |
||||||
|
|
||||||
|
import com.jme3.network.Client; |
||||||
|
import com.jme3.network.Message; |
||||||
|
import com.jme3.network.MessageListener; |
||||||
|
import com.jme3.network.message.SerializerRegistrationsMessage; |
||||||
|
import com.jme3.network.serializing.Serializer; |
||||||
|
import com.jme3.network.service.AbstractClientService; |
||||||
|
import com.jme3.network.service.ClientServiceManager; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class ClientSerializerRegistrationsService extends AbstractClientService |
||||||
|
implements MessageListener<Client> { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onInitialize( ClientServiceManager serviceManager ) { |
||||||
|
// Make sure our message type is registered
|
||||||
|
// This is the minimum we'd need just to be able to register
|
||||||
|
// the rest... otherwise we can't even receive this message.
|
||||||
|
Serializer.registerClass(SerializerRegistrationsMessage.class); |
||||||
|
Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); |
||||||
|
|
||||||
|
// Add our listener for that message type
|
||||||
|
serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class); |
||||||
|
} |
||||||
|
|
||||||
|
public void messageReceived( Client source, Message m ) { |
||||||
|
// We only wait for one kind of message...
|
||||||
|
SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m; |
||||||
|
msg.registerAll(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.service.serializer; |
||||||
|
|
||||||
|
import com.jme3.network.HostedConnection; |
||||||
|
import com.jme3.network.Server; |
||||||
|
import com.jme3.network.message.SerializerRegistrationsMessage; |
||||||
|
import com.jme3.network.serializing.Serializer; |
||||||
|
import com.jme3.network.service.AbstractHostedService; |
||||||
|
import com.jme3.network.service.HostedServiceManager; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class ServerSerializerRegistrationsService extends AbstractHostedService { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onInitialize( HostedServiceManager serviceManager ) { |
||||||
|
// Make sure our message type is registered
|
||||||
|
Serializer.registerClass(SerializerRegistrationsMessage.class); |
||||||
|
Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void start() { |
||||||
|
// Compile the registrations into a message we will
|
||||||
|
// send to all connecting clients
|
||||||
|
SerializerRegistrationsMessage.compile(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectionAdded(Server server, HostedConnection hc) { |
||||||
|
// Just in case
|
||||||
|
super.connectionAdded(server, hc); |
||||||
|
|
||||||
|
// Send the client the registration information
|
||||||
|
hc.send(SerializerRegistrationsMessage.INSTANCE); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,314 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.util; |
||||||
|
|
||||||
|
import com.jme3.network.Message; |
||||||
|
import com.jme3.network.MessageConnection; |
||||||
|
import com.jme3.network.MessageListener; |
||||||
|
import java.lang.reflect.InvocationTargetException; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A MessageListener implementation that will forward messages to methods |
||||||
|
* of a delegate object. These methods can be automapped or manually |
||||||
|
* specified. Subclasses provide specific implementations for how to |
||||||
|
* find the actual delegate object. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public abstract class AbstractMessageDelegator<S extends MessageConnection> |
||||||
|
implements MessageListener<S> { |
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName()); |
||||||
|
|
||||||
|
private Class delegateType; |
||||||
|
private Map<Class, Method> methods = new HashMap<Class, Method>(); |
||||||
|
private Class[] messageTypes; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates an AbstractMessageDelegator that will forward received |
||||||
|
* messages to methods of the specified delegate type. If automap |
||||||
|
* is true then reflection is used to lookup probably message handling |
||||||
|
* methods. |
||||||
|
*/ |
||||||
|
protected AbstractMessageDelegator( Class delegateType, boolean automap ) { |
||||||
|
this.delegateType = delegateType; |
||||||
|
if( automap ) { |
||||||
|
automap(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the array of messages known to be handled by this message |
||||||
|
* delegator. |
||||||
|
*/ |
||||||
|
public Class[] getMessageTypes() { |
||||||
|
if( messageTypes == null ) { |
||||||
|
messageTypes = methods.keySet().toArray(new Class[methods.size()]); |
||||||
|
} |
||||||
|
return messageTypes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if the specified method is valid for the specified |
||||||
|
* message type. This is used internally during automapping to |
||||||
|
* provide implementation specific filting of methods. |
||||||
|
* This implementation checks for methods that take either the connection and message |
||||||
|
* type arguments (in that order) or just the message type. |
||||||
|
*/ |
||||||
|
protected boolean isValidMethod( Method m, Class messageType ) { |
||||||
|
|
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "isValidMethod({0}, {1})", new Object[]{m, messageType}); |
||||||
|
} |
||||||
|
|
||||||
|
// Parameters must be S and message type or just message type
|
||||||
|
Class<?>[] parms = m.getParameterTypes(); |
||||||
|
if( parms.length != 2 && parms.length != 1 ) { |
||||||
|
log.finest("Parameter count is not 1 or 2"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
int messageIndex = 0; |
||||||
|
if( parms.length > 1 ) { |
||||||
|
if( MessageConnection.class.isAssignableFrom(parms[0]) ) { |
||||||
|
messageIndex++; |
||||||
|
} else { |
||||||
|
log.finest("First paramter is not a MessageConnection or subclass."); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if( messageType == null && !Message.class.isAssignableFrom(parms[messageIndex]) ) { |
||||||
|
log.finest("Second paramter is not a Message or subclass."); |
||||||
|
return false; |
||||||
|
} |
||||||
|
if( messageType != null && !parms[messageIndex].isAssignableFrom(messageType) ) { |
||||||
|
log.log(Level.FINEST, "Second paramter is not a {0}", messageType); |
||||||
|
return false; |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convenience method that returns the message type as |
||||||
|
* reflecively determined for a particular method. This |
||||||
|
* only works with methods that actually have arguments. |
||||||
|
* This implementation returns the last element of the method's |
||||||
|
* getParameterTypes() array, thus supporting both |
||||||
|
* method(connection, messageType) as well as just method(messageType) |
||||||
|
* calling forms. |
||||||
|
*/ |
||||||
|
protected Class getMessageType( Method m ) { |
||||||
|
Class<?>[] parms = m.getParameterTypes(); |
||||||
|
return parms[parms.length-1]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Goes through all of the delegate type's methods to find |
||||||
|
* a method of the specified name that may take the specified |
||||||
|
* message type. |
||||||
|
*/ |
||||||
|
protected Method findDelegate( String name, Class messageType ) { |
||||||
|
// We do an exhaustive search because it's easier to
|
||||||
|
// check for a variety of parameter types and it's all
|
||||||
|
// that Class would be doing in getMethod() anyway.
|
||||||
|
for( Method m : delegateType.getDeclaredMethods() ) { |
||||||
|
|
||||||
|
if( !m.getName().equals(name) ) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if( isValidMethod(m, messageType) ) { |
||||||
|
return m; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns true if the specified method name is allowed. |
||||||
|
* This is used by automapping to determine if a method |
||||||
|
* should be rejected purely on name. Default implemention |
||||||
|
* always returns true. |
||||||
|
*/ |
||||||
|
protected boolean allowName( String name ) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calls the map(Set) method with a null argument causing |
||||||
|
* all available matching methods to mapped to message types. |
||||||
|
*/ |
||||||
|
protected final void automap() { |
||||||
|
map((Set<String>)null); |
||||||
|
if( methods.isEmpty() ) { |
||||||
|
throw new RuntimeException("No message handling methods found for class:" + delegateType); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Specifically maps the specified methods names, autowiring |
||||||
|
* the parameters. |
||||||
|
*/ |
||||||
|
public AbstractMessageDelegator<S> map( String... methodNames ) { |
||||||
|
Set<String> names = new HashSet<String>( Arrays.asList(methodNames) ); |
||||||
|
map(names); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Goes through all of the delegate type's declared methods |
||||||
|
* mapping methods that match the current constraints. |
||||||
|
* If the constraints set is null then allowName() is |
||||||
|
* checked for names otherwise only names in the constraints |
||||||
|
* set are allowed. |
||||||
|
* For each candidate method that passes the above checks, |
||||||
|
* isValidMethod() is called with a null message type argument. |
||||||
|
* All methods are made accessible thus supporting non-public |
||||||
|
* methods as well as public methods. |
||||||
|
*/ |
||||||
|
protected void map( Set<String> constraints ) { |
||||||
|
|
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "map({0})", constraints); |
||||||
|
} |
||||||
|
for( Method m : delegateType.getDeclaredMethods() ) { |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "Checking method:{0}", m); |
||||||
|
} |
||||||
|
|
||||||
|
if( constraints == null && !allowName(m.getName()) ) { |
||||||
|
log.finest("Name is not allowed."); |
||||||
|
continue; |
||||||
|
} |
||||||
|
if( constraints != null && !constraints.contains(m.getName()) ) { |
||||||
|
log.finest("Name is not in constraints set."); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if( isValidMethod(m, null) ) { |
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{getMessageType(m), m}); |
||||||
|
} |
||||||
|
// Make sure we can access the method even if it's not public or
|
||||||
|
// is in a non-public inner class.
|
||||||
|
m.setAccessible(true); |
||||||
|
methods.put(getMessageType(m), m); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
messageTypes = null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Manually maps a specified method to the specified message type. |
||||||
|
*/ |
||||||
|
public AbstractMessageDelegator<S> map( Class messageType, String methodName ) { |
||||||
|
// Lookup the method
|
||||||
|
Method m = findDelegate( methodName, messageType ); |
||||||
|
if( m == null ) { |
||||||
|
throw new RuntimeException( "Method:" + methodName |
||||||
|
+ " not found matching signature (MessageConnection, " |
||||||
|
+ messageType.getName() + ")" ); |
||||||
|
} |
||||||
|
|
||||||
|
if( log.isLoggable(Level.FINEST) ) { |
||||||
|
log.log(Level.FINEST, "Adding method mapping:{0} = {1}", new Object[]{messageType, m}); |
||||||
|
} |
||||||
|
methods.put( messageType, m ); |
||||||
|
messageTypes = null; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the mapped method for the specified message type. |
||||||
|
*/ |
||||||
|
protected Method getMethod( Class c ) { |
||||||
|
Method m = methods.get(c); |
||||||
|
return m; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implemented by subclasses to provide the actual delegate object |
||||||
|
* against which the mapped message type methods will be called. |
||||||
|
*/ |
||||||
|
protected abstract Object getSourceDelegate( S source ); |
||||||
|
|
||||||
|
/** |
||||||
|
* Implementation of the MessageListener's messageReceived() |
||||||
|
* method that will use the current message type mapping to |
||||||
|
* find an appropriate message handling method and call it |
||||||
|
* on the delegate returned by getSourceDelegate(). |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void messageReceived( S source, Message msg ) { |
||||||
|
if( msg == null ) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Object delegate = getSourceDelegate(source); |
||||||
|
if( delegate == null ) { |
||||||
|
// Means ignore this message/source
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Method m = getMethod(msg.getClass()); |
||||||
|
if( m == null ) { |
||||||
|
throw new RuntimeException("Delegate method not found for message class:" |
||||||
|
+ msg.getClass()); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
if( m.getParameterTypes().length > 1 ) { |
||||||
|
m.invoke( delegate, source, msg ); |
||||||
|
} else { |
||||||
|
m.invoke( delegate, msg ); |
||||||
|
} |
||||||
|
} catch( IllegalAccessException e ) { |
||||||
|
throw new RuntimeException("Error executing:" + m, e); |
||||||
|
} catch( InvocationTargetException e ) { |
||||||
|
throw new RuntimeException("Error executing:" + m, e.getCause()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,72 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.util; |
||||||
|
|
||||||
|
import com.jme3.network.MessageConnection; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A MessageListener implementation that will forward messages to methods |
||||||
|
* of a specified delegate object. These methods can be automapped or manually |
||||||
|
* specified. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class ObjectMessageDelegator<S extends MessageConnection> extends AbstractMessageDelegator<S> { |
||||||
|
|
||||||
|
private Object delegate; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a MessageListener that will forward mapped message types |
||||||
|
* to methods of the specified object. |
||||||
|
* If automap is true then all methods with the proper signature will |
||||||
|
* be mapped. |
||||||
|
* <p>Methods of the following signatures are allowed: |
||||||
|
* <ul> |
||||||
|
* <li>void someName(S conn, SomeMessage msg) |
||||||
|
* <li>void someName(Message msg) |
||||||
|
* </ul> |
||||||
|
* Where S is the type of MessageConnection and SomeMessage is some |
||||||
|
* specific concreate Message subclass. |
||||||
|
*/ |
||||||
|
public ObjectMessageDelegator( Object delegate, boolean automap ) { |
||||||
|
super(delegate.getClass(), automap); |
||||||
|
this.delegate = delegate; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object getSourceDelegate( MessageConnection source ) { |
||||||
|
return delegate; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,103 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2015 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.network.util; |
||||||
|
|
||||||
|
import com.jme3.network.HostedConnection; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* A MessageListener implementation that will forward messages to methods |
||||||
|
* of a delegate specified as a HostedConnection session attribute. This is |
||||||
|
* useful for handling connection-specific messages from clients that must |
||||||
|
* delegate to client-specific data objects. |
||||||
|
* The delegate methods can be automapped or manually specified. |
||||||
|
* |
||||||
|
* @author Paul Speed |
||||||
|
*/ |
||||||
|
public class SessionDataDelegator extends AbstractMessageDelegator<HostedConnection> { |
||||||
|
|
||||||
|
static final Logger log = Logger.getLogger(SessionDataDelegator.class.getName()); |
||||||
|
|
||||||
|
private String attributeName; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a MessageListener that will forward mapped message types |
||||||
|
* to methods of an object specified as a HostedConnection attribute. |
||||||
|
* If automap is true then all methods with the proper signature will |
||||||
|
* be mapped. |
||||||
|
* <p>Methods of the following signatures are allowed: |
||||||
|
* <ul> |
||||||
|
* <li>void someName(S conn, SomeMessage msg) |
||||||
|
* <li>void someName(Message msg) |
||||||
|
* </ul> |
||||||
|
* Where S is the type of MessageConnection and SomeMessage is some |
||||||
|
* specific concreate Message subclass. |
||||||
|
*/ |
||||||
|
public SessionDataDelegator( Class delegateType, String attributeName, boolean automap ) { |
||||||
|
super(delegateType, automap); |
||||||
|
this.attributeName = attributeName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the attribute name that will be used to look up the |
||||||
|
* delegate object. |
||||||
|
*/ |
||||||
|
public String getAttributeName() { |
||||||
|
return attributeName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Called internally when there is no session object |
||||||
|
* for the current attribute name attached to the passed source |
||||||
|
* HostConnection. Default implementation logs a warning. |
||||||
|
*/ |
||||||
|
protected void miss( HostedConnection source ) { |
||||||
|
log.log(Level.WARNING, "Session data is null for:{0} on connection:{1}", new Object[]{attributeName, source}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the attributeName attribute of the supplied source |
||||||
|
* HostConnection. If there is no value at that attribute then |
||||||
|
* the miss() method is called. |
||||||
|
*/ |
||||||
|
protected Object getSourceDelegate( HostedConnection source ) { |
||||||
|
Object result = source.getAttribute(attributeName); |
||||||
|
if( result == null ) { |
||||||
|
miss(source); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,413 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx; |
||||||
|
|
||||||
|
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.Track; |
||||||
|
import com.jme3.asset.AssetInfo; |
||||||
|
import com.jme3.asset.AssetKey; |
||||||
|
import com.jme3.asset.AssetLoadException; |
||||||
|
import com.jme3.asset.AssetLoader; |
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.asset.ModelKey; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxToJmeTrack; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxAnimLayer; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxAnimStack; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxBindPose; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxDump; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxFile; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxReader; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxId; |
||||||
|
import com.jme3.scene.plugins.fbx.misc.FbxGlobalSettings; |
||||||
|
import com.jme3.scene.plugins.fbx.node.FbxNode; |
||||||
|
import com.jme3.scene.plugins.fbx.node.FbxRootNode; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObjectFactory; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
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; |
||||||
|
|
||||||
|
public class FbxLoader implements AssetLoader { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxLoader.class.getName()); |
||||||
|
|
||||||
|
private AssetManager assetManager; |
||||||
|
|
||||||
|
private String sceneName; |
||||||
|
private String sceneFilename; |
||||||
|
private String sceneFolderName; |
||||||
|
private FbxGlobalSettings globalSettings; |
||||||
|
private final Map<FbxId, FbxObject> objectMap = new HashMap<FbxId, FbxObject>(); |
||||||
|
|
||||||
|
private final List<FbxAnimStack> animStacks = new ArrayList<FbxAnimStack>(); |
||||||
|
private final List<FbxBindPose> bindPoses = new ArrayList<FbxBindPose>(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object load(AssetInfo assetInfo) throws IOException { |
||||||
|
this.assetManager = assetInfo.getManager(); |
||||||
|
AssetKey<?> assetKey = assetInfo.getKey(); |
||||||
|
if (!(assetKey instanceof ModelKey)) { |
||||||
|
throw new AssetLoadException("Invalid asset key"); |
||||||
|
} |
||||||
|
|
||||||
|
InputStream stream = assetInfo.openStream(); |
||||||
|
try { |
||||||
|
sceneFilename = assetKey.getName(); |
||||||
|
sceneFolderName = assetKey.getFolder(); |
||||||
|
String ext = assetKey.getExtension(); |
||||||
|
|
||||||
|
sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1); |
||||||
|
if (sceneFolderName != null && sceneFolderName.length() > 0) { |
||||||
|
sceneName = sceneName.substring(sceneFolderName.length()); |
||||||
|
} |
||||||
|
|
||||||
|
reset(); |
||||||
|
|
||||||
|
// Load the data from the stream.
|
||||||
|
loadData(stream); |
||||||
|
|
||||||
|
// Bind poses are needed to compute world transforms.
|
||||||
|
applyBindPoses(); |
||||||
|
|
||||||
|
// Need world transforms for skeleton creation.
|
||||||
|
updateWorldTransforms(); |
||||||
|
|
||||||
|
// Need skeletons for meshs to be created in scene graph construction.
|
||||||
|
// Mesh bone indices require skeletons to determine bone index.
|
||||||
|
constructSkeletons(); |
||||||
|
|
||||||
|
// Create the jME3 scene graph from the FBX scene graph.
|
||||||
|
// Also creates SkeletonControls based on the constructed skeletons.
|
||||||
|
Spatial scene = constructSceneGraph(); |
||||||
|
|
||||||
|
// Load animations into AnimControls
|
||||||
|
constructAnimations(); |
||||||
|
|
||||||
|
return scene; |
||||||
|
} finally { |
||||||
|
releaseObjects(); |
||||||
|
if (stream != null) { |
||||||
|
stream.close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void reset() { |
||||||
|
globalSettings = new FbxGlobalSettings(); |
||||||
|
} |
||||||
|
|
||||||
|
private void releaseObjects() { |
||||||
|
globalSettings = null; |
||||||
|
objectMap.clear(); |
||||||
|
animStacks.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
private void loadData(InputStream stream) throws IOException { |
||||||
|
FbxFile scene = FbxReader.readFBX(stream); |
||||||
|
|
||||||
|
FbxDump.dumpFile(scene); |
||||||
|
|
||||||
|
// TODO: Load FBX object templates
|
||||||
|
|
||||||
|
for (FbxElement e : scene.rootElements) { |
||||||
|
if (e.id.equals("FBXHeaderExtension")) { |
||||||
|
loadHeader(e); |
||||||
|
} else if (e.id.equals("GlobalSettings")) { |
||||||
|
loadGlobalSettings(e); |
||||||
|
} else if (e.id.equals("Objects")) { |
||||||
|
loadObjects(e); |
||||||
|
} else if (e.id.equals("Connections")) { |
||||||
|
connectObjects(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void loadHeader(FbxElement element) { |
||||||
|
for (FbxElement e : element.children) { |
||||||
|
if (e.id.equals("FBXVersion")) { |
||||||
|
Integer version = (Integer) e.properties.get(0); |
||||||
|
if (version < 7100) { |
||||||
|
logger.log(Level.WARNING, "FBX file version is older than 7.1. " |
||||||
|
+ "Some features may not work."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void loadGlobalSettings(FbxElement element) { |
||||||
|
globalSettings = new FbxGlobalSettings(); |
||||||
|
globalSettings.fromElement(element); |
||||||
|
} |
||||||
|
|
||||||
|
private void loadObjects(FbxElement element) { |
||||||
|
// Initialize the FBX root element.
|
||||||
|
objectMap.put(FbxId.ROOT, new FbxRootNode(assetManager, sceneFolderName)); |
||||||
|
|
||||||
|
for(FbxElement e : element.children) { |
||||||
|
if (e.id.equals("GlobalSettings")) { |
||||||
|
// Old FBX files seem to have the GlobalSettings element
|
||||||
|
// under Objects (??)
|
||||||
|
globalSettings.fromElement(e); |
||||||
|
} else { |
||||||
|
FbxObject object = FbxObjectFactory.createObject(e, assetManager, sceneFolderName); |
||||||
|
if (object != null) { |
||||||
|
if (objectMap.containsKey(object.getId())) { |
||||||
|
logger.log(Level.WARNING, "An object with ID \"{0}\" has " |
||||||
|
+ "already been defined. " |
||||||
|
+ "Ignoring.", |
||||||
|
object.getId()); |
||||||
|
} |
||||||
|
|
||||||
|
objectMap.put(object.getId(), object); |
||||||
|
|
||||||
|
if (object instanceof FbxAnimStack) { |
||||||
|
// NOTE: animation stacks are implicitly global.
|
||||||
|
// Capture them here.
|
||||||
|
animStacks.add((FbxAnimStack) object); |
||||||
|
} else if (object instanceof FbxBindPose) { |
||||||
|
bindPoses.add((FbxBindPose) object); |
||||||
|
} |
||||||
|
} else { |
||||||
|
throw new UnsupportedOperationException("Failed to create FBX object of type: " + e.id); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void removeUnconnectedObjects() { |
||||||
|
for (FbxObject object : new ArrayList<FbxObject>(objectMap.values())) { |
||||||
|
if (!object.isJmeObjectCreated()) { |
||||||
|
logger.log(Level.WARNING, "Purging orphan FBX object: {0}", object); |
||||||
|
objectMap.remove(object.getId()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void connectObjects(FbxElement element) { |
||||||
|
if (objectMap.isEmpty()) { |
||||||
|
logger.log(Level.WARNING, "FBX file is missing object information"); |
||||||
|
return; |
||||||
|
} else if (objectMap.size() == 1) { |
||||||
|
// Only root node (automatically added by jME3)
|
||||||
|
logger.log(Level.WARNING, "FBX file has no objects"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
for (FbxElement el : element.children) { |
||||||
|
if (!el.id.equals("C") && !el.id.equals("Connect")) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
String type = (String) el.properties.get(0); |
||||||
|
FbxId childId; |
||||||
|
FbxId parentId; |
||||||
|
if (type.equals("OO")) { |
||||||
|
childId = FbxId.create(el.properties.get(1)); |
||||||
|
parentId = FbxId.create(el.properties.get(2)); |
||||||
|
FbxObject child = objectMap.get(childId); |
||||||
|
FbxObject parent; |
||||||
|
|
||||||
|
if (parentId.isNull()) { |
||||||
|
// TODO: maybe clean this up a bit..
|
||||||
|
parent = objectMap.get(FbxId.ROOT); |
||||||
|
} else { |
||||||
|
parent = objectMap.get(parentId); |
||||||
|
} |
||||||
|
|
||||||
|
if (parent == null) { |
||||||
|
throw new UnsupportedOperationException("Cannot find parent object ID \"" + parentId + "\""); |
||||||
|
} |
||||||
|
|
||||||
|
parent.connectObject(child); |
||||||
|
} else if (type.equals("OP")) { |
||||||
|
childId = FbxId.create(el.properties.get(1)); |
||||||
|
parentId = FbxId.create(el.properties.get(2)); |
||||||
|
String propName = (String) el.properties.get(3); |
||||||
|
FbxObject child = objectMap.get(childId); |
||||||
|
FbxObject parent = objectMap.get(parentId); |
||||||
|
parent.connectObjectProperty(child, propName); |
||||||
|
} else { |
||||||
|
logger.log(Level.WARNING, "Unknown connection type: {0}. Ignoring.", type); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Copies the bind poses from FBX BindPose objects to FBX nodes. |
||||||
|
* Must be called prior to {@link #updateWorldTransforms()}. |
||||||
|
*/ |
||||||
|
private void applyBindPoses() { |
||||||
|
for (FbxBindPose bindPose : bindPoses) { |
||||||
|
Map<FbxId, Matrix4f> bindPoseData = bindPose.getJmeObject(); |
||||||
|
logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size()); |
||||||
|
for (Map.Entry<FbxId, Matrix4f> entry : bindPoseData.entrySet()) { |
||||||
|
FbxObject obj = objectMap.get(entry.getKey()); |
||||||
|
if (obj instanceof FbxNode) { |
||||||
|
FbxNode node = (FbxNode) obj; |
||||||
|
node.setWorldBindPose(entry.getValue()); |
||||||
|
} else { |
||||||
|
logger.log(Level.WARNING, "Bind pose can only be applied to FBX nodes. Ignoring."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates world transforms and bind poses for the FBX scene graph. |
||||||
|
*/ |
||||||
|
private void updateWorldTransforms() { |
||||||
|
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT); |
||||||
|
fbxRoot.updateWorldTransforms(null, null); |
||||||
|
} |
||||||
|
|
||||||
|
private void constructAnimations() { |
||||||
|
// In FBX, animation are not attached to any root.
|
||||||
|
// They are implicitly global.
|
||||||
|
// So, we need to use hueristics to find which node(s)
|
||||||
|
// an animation is associated with, so we can create the AnimControl
|
||||||
|
// in the appropriate location in the scene.
|
||||||
|
Map<FbxToJmeTrack, FbxToJmeTrack> pairs = new HashMap<FbxToJmeTrack, FbxToJmeTrack>(); |
||||||
|
for (FbxAnimStack stack : animStacks) { |
||||||
|
for (FbxAnimLayer layer : stack.getLayers()) { |
||||||
|
for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) { |
||||||
|
for (Map.Entry<FbxNode, String> nodePropertyEntry : curveNode.getInfluencedNodeProperties().entrySet()) { |
||||||
|
FbxToJmeTrack lookupPair = new FbxToJmeTrack(); |
||||||
|
lookupPair.animStack = stack; |
||||||
|
lookupPair.animLayer = layer; |
||||||
|
lookupPair.node = nodePropertyEntry.getKey(); |
||||||
|
|
||||||
|
// Find if this pair is already stored
|
||||||
|
FbxToJmeTrack storedPair = pairs.get(lookupPair); |
||||||
|
if (storedPair == null) { |
||||||
|
// If not, store it.
|
||||||
|
storedPair = lookupPair; |
||||||
|
pairs.put(storedPair, storedPair); |
||||||
|
} |
||||||
|
|
||||||
|
String property = nodePropertyEntry.getValue(); |
||||||
|
storedPair.animCurves.put(property, curveNode); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// At this point we can construct the animation for all pairs ...
|
||||||
|
for (FbxToJmeTrack pair : pairs.values()) { |
||||||
|
String animName = pair.animStack.getName(); |
||||||
|
float duration = pair.animStack.getDuration(); |
||||||
|
|
||||||
|
System.out.println("ANIMATION: " + animName + ", duration = " + duration); |
||||||
|
System.out.println("NODE: " + pair.node.getName()); |
||||||
|
|
||||||
|
duration = pair.getDuration(); |
||||||
|
|
||||||
|
if (pair.node instanceof FbxLimbNode) { |
||||||
|
// Find the spatial that has the skeleton for this limb.
|
||||||
|
FbxLimbNode limbNode = (FbxLimbNode) pair.node; |
||||||
|
Bone bone = limbNode.getJmeBone(); |
||||||
|
Spatial jmeSpatial = limbNode.getSkeletonHolder().getJmeObject(); |
||||||
|
Skeleton skeleton = limbNode.getSkeletonHolder().getJmeSkeleton(); |
||||||
|
|
||||||
|
// Get the animation control (create if missing).
|
||||||
|
AnimControl animControl = jmeSpatial.getControl(AnimControl.class); |
||||||
|
if (animControl.getSkeleton() != skeleton) { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
// Get the animation (create if missing).
|
||||||
|
Animation anim = animControl.getAnim(animName); |
||||||
|
if (anim == null) { |
||||||
|
anim = new Animation(animName, duration); |
||||||
|
animControl.addAnim(anim); |
||||||
|
} |
||||||
|
|
||||||
|
// Find the bone index from the spatial's skeleton.
|
||||||
|
int boneIndex = skeleton.getBoneIndex(bone); |
||||||
|
|
||||||
|
// Generate the bone track.
|
||||||
|
BoneTrack bt = pair.toJmeBoneTrack(boneIndex, bone.getBindInverseTransform()); |
||||||
|
|
||||||
|
// Add the bone track to the animation.
|
||||||
|
anim.addTrack(bt); |
||||||
|
} else { |
||||||
|
// Create the spatial animation
|
||||||
|
Animation anim = new Animation(animName, duration); |
||||||
|
anim.setTracks(new Track[]{ pair.toJmeSpatialTrack() }); |
||||||
|
|
||||||
|
// Get the animation control (create if missing).
|
||||||
|
Spatial jmeSpatial = pair.node.getJmeObject(); |
||||||
|
AnimControl animControl = jmeSpatial.getControl(AnimControl.class); |
||||||
|
|
||||||
|
if (animControl == null) { |
||||||
|
animControl = new AnimControl(null); |
||||||
|
jmeSpatial.addControl(animControl); |
||||||
|
} |
||||||
|
|
||||||
|
// Add the spatial animation
|
||||||
|
animControl.addAnim(anim); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void constructSkeletons() { |
||||||
|
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT); |
||||||
|
FbxNode.createSkeletons(fbxRoot); |
||||||
|
} |
||||||
|
|
||||||
|
private Spatial constructSceneGraph() { |
||||||
|
// Acquire the implicit root object.
|
||||||
|
FbxNode fbxRoot = (FbxNode) objectMap.get(FbxId.ROOT); |
||||||
|
|
||||||
|
// Convert it into a jME3 scene
|
||||||
|
Node jmeRoot = (Node) FbxNode.createScene(fbxRoot); |
||||||
|
|
||||||
|
// Fix the name (will probably be set to something like "-node")
|
||||||
|
jmeRoot.setName(sceneName + "-scene"); |
||||||
|
|
||||||
|
return jmeRoot; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,144 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
|
||||||
|
public class FbxAnimCurve extends FbxObject { |
||||||
|
|
||||||
|
private long[] keyTimes; |
||||||
|
private float[] keyValues; |
||||||
|
|
||||||
|
public FbxAnimCurve(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
|
||||||
|
for (FbxElement e : element.children) { |
||||||
|
if (e.id.equals("KeyTime")) { |
||||||
|
keyTimes = (long[]) e.properties.get(0); |
||||||
|
} else if (e.id.equals("KeyValueFloat")) { |
||||||
|
keyValues = (float[]) e.properties.get(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
long time = -1; |
||||||
|
for (int i = 0; i < keyTimes.length; i++) { |
||||||
|
if (time >= keyTimes[i]) { |
||||||
|
throw new UnsupportedOperationException("Keyframe times must be sequential, but they are not."); |
||||||
|
} |
||||||
|
time = keyTimes[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the times for the keyframes. |
||||||
|
* @return Keyframe times. |
||||||
|
*/ |
||||||
|
public long[] getKeyTimes() { |
||||||
|
return keyTimes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieve the curve value at the given time. |
||||||
|
* If the curve has no data, 0 is returned. |
||||||
|
* If the time is outside the curve, then the closest value is returned. |
||||||
|
* If the time isn't on an exact keyframe, linear interpolation is used |
||||||
|
* to determine the value between the keyframes at the given time. |
||||||
|
* @param time The time to get the curve value at (in FBX time units). |
||||||
|
* @return The value at the given time. |
||||||
|
*/ |
||||||
|
public float getValueAtTime(long time) { |
||||||
|
if (keyTimes.length == 0) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// If the time is outside the range,
|
||||||
|
// we just return the closest value. (No extrapolation)
|
||||||
|
if (time <= keyTimes[0]) { |
||||||
|
return keyValues[0]; |
||||||
|
} else if (time >= keyTimes[keyTimes.length - 1]) { |
||||||
|
return keyValues[keyValues.length - 1]; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int startFrame = 0; |
||||||
|
int endFrame = 1; |
||||||
|
int lastFrame = keyTimes.length - 1; |
||||||
|
|
||||||
|
for (int i = 0; i < lastFrame && keyTimes[i] < time; ++i) { |
||||||
|
startFrame = i; |
||||||
|
endFrame = i + 1; |
||||||
|
} |
||||||
|
|
||||||
|
long keyTime1 = keyTimes[startFrame]; |
||||||
|
float keyValue1 = keyValues[startFrame]; |
||||||
|
long keyTime2 = keyTimes[endFrame]; |
||||||
|
float keyValue2 = keyValues[endFrame]; |
||||||
|
|
||||||
|
if (keyTime2 == time) { |
||||||
|
return keyValue2; |
||||||
|
} |
||||||
|
|
||||||
|
long prevToNextDelta = keyTime2 - keyTime1; |
||||||
|
long prevToCurrentDelta = time - keyTime1; |
||||||
|
float lerpAmount = (float)prevToCurrentDelta / prevToNextDelta; |
||||||
|
|
||||||
|
return FastMath.interpolateLinear(lerpAmount, keyValue1, keyValue2); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object toJmeObject() { |
||||||
|
// An AnimCurve has no jME3 representation.
|
||||||
|
// The parent AnimCurveNode is responsible to create the jME3
|
||||||
|
// representation.
|
||||||
|
throw new UnsupportedOperationException("No jME3 object conversion available"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,147 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.node.FbxNode; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxAnimCurveNode extends FbxObject { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxAnimCurveNode.class.getName()); |
||||||
|
|
||||||
|
private final Map<FbxNode, String> influencedNodePropertiesMap = new HashMap<FbxNode, String>(); |
||||||
|
private final Map<String, FbxAnimCurve> propertyToCurveMap = new HashMap<String, FbxAnimCurve>(); |
||||||
|
private final Map<String, Float> propertyToDefaultMap = new HashMap<String, Float>(); |
||||||
|
|
||||||
|
public FbxAnimCurveNode(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
for (FbxElement prop : element.getFbxProperties()) { |
||||||
|
String propName = (String) prop.properties.get(0); |
||||||
|
String propType = (String) prop.properties.get(1); |
||||||
|
if (propType.equals("Number")) { |
||||||
|
float propValue = ((Double) prop.properties.get(4)).floatValue(); |
||||||
|
propertyToDefaultMap.put(propName, propValue); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void addInfluencedNode(FbxNode node, String property) { |
||||||
|
influencedNodePropertiesMap.put(node, property); |
||||||
|
} |
||||||
|
|
||||||
|
public Map<FbxNode, String> getInfluencedNodeProperties() { |
||||||
|
return influencedNodePropertiesMap; |
||||||
|
} |
||||||
|
|
||||||
|
public Collection<FbxAnimCurve> getCurves() { |
||||||
|
return propertyToCurveMap.values(); |
||||||
|
} |
||||||
|
|
||||||
|
public Vector3f getVector3Value(long time) { |
||||||
|
Vector3f value = new Vector3f(); |
||||||
|
FbxAnimCurve xCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X); |
||||||
|
FbxAnimCurve yCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y); |
||||||
|
FbxAnimCurve zCurve = propertyToCurveMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z); |
||||||
|
Float xDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_X); |
||||||
|
Float yDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Y); |
||||||
|
Float zDefault = propertyToDefaultMap.get(FbxAnimUtil.CURVE_NODE_PROPERTY_Z); |
||||||
|
value.x = xCurve != null ? xCurve.getValueAtTime(time) : xDefault; |
||||||
|
value.y = yCurve != null ? yCurve.getValueAtTime(time) : yDefault; |
||||||
|
value.z = zCurve != null ? zCurve.getValueAtTime(time) : zDefault; |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts the euler angles from {@link #getVector3Value(long)} to |
||||||
|
* a quaternion rotation. |
||||||
|
* @param time Time at which to get the euler angles. |
||||||
|
* @return The rotation at time |
||||||
|
*/ |
||||||
|
public Quaternion getQuaternionValue(long time) { |
||||||
|
Vector3f eulerAngles = getVector3Value(time); |
||||||
|
System.out.println("\tT: " + time + ". Rotation: " + |
||||||
|
eulerAngles.x + ", " + |
||||||
|
eulerAngles.y + ", " + eulerAngles.z); |
||||||
|
Quaternion q = new Quaternion(); |
||||||
|
q.fromAngles(eulerAngles.x * FastMath.DEG_TO_RAD, |
||||||
|
eulerAngles.y * FastMath.DEG_TO_RAD, |
||||||
|
eulerAngles.z * FastMath.DEG_TO_RAD); |
||||||
|
return q; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object toJmeObject() { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
if (!(object instanceof FbxAnimCurve)) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
if (!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_X) && |
||||||
|
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Y) && |
||||||
|
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_Z) && |
||||||
|
!property.equals(FbxAnimUtil.CURVE_NODE_PROPERTY_VISIBILITY)) { |
||||||
|
logger.log(Level.WARNING, "Animating the dimension ''{0}'' is not " |
||||||
|
+ "supported yet. Ignoring.", property); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (propertyToCurveMap.containsKey(property)) { |
||||||
|
throw new UnsupportedOperationException("!"); |
||||||
|
} |
||||||
|
|
||||||
|
propertyToCurveMap.put(property, (FbxAnimCurve) object); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxAnimLayer extends FbxObject { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxAnimLayer.class.getName()); |
||||||
|
|
||||||
|
private final List<FbxAnimCurveNode> animCurves = new ArrayList<FbxAnimCurveNode>(); |
||||||
|
|
||||||
|
public FbxAnimLayer(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
// No known properties for layers..
|
||||||
|
// Also jME3 doesn't support multiple layers anyway.
|
||||||
|
} |
||||||
|
|
||||||
|
public List<FbxAnimCurveNode> getAnimationCurveNodes() { |
||||||
|
return Collections.unmodifiableList(animCurves); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object toJmeObject() { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
if (!(object instanceof FbxAnimCurveNode)) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
|
||||||
|
animCurves.add((FbxAnimCurveNode) object); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,111 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxAnimStack extends FbxObject { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxAnimStack.class.getName()); |
||||||
|
|
||||||
|
private float duration; |
||||||
|
private FbxAnimLayer layer0; |
||||||
|
|
||||||
|
public FbxAnimStack(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
for (FbxElement child : element.getFbxProperties()) { |
||||||
|
String propName = (String) child.properties.get(0); |
||||||
|
if (propName.equals("LocalStop")) { |
||||||
|
long durationLong = (Long)child.properties.get(4); |
||||||
|
duration = (float) (durationLong * FbxAnimUtil.SECONDS_PER_UNIT); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Finds out which FBX nodes this animation is going to influence.
|
||||||
|
// *
|
||||||
|
// * @return A list of FBX nodes that the stack's curves are influencing.
|
||||||
|
// */
|
||||||
|
// public Set<FbxNode> getInfluencedNodes() {
|
||||||
|
// HashSet<FbxNode> influencedNodes = new HashSet<FbxNode>();
|
||||||
|
// if (layer0 == null) {
|
||||||
|
// return influencedNodes;
|
||||||
|
// }
|
||||||
|
// for (FbxAnimCurveNode curveNode : layer0.getAnimationCurveNodes()) {
|
||||||
|
// influencedNodes.addAll(curveNode.getInfluencedNodes());
|
||||||
|
// }
|
||||||
|
// return influencedNodes;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public float getDuration() { |
||||||
|
return duration; |
||||||
|
} |
||||||
|
|
||||||
|
public FbxAnimLayer[] getLayers() { |
||||||
|
return new FbxAnimLayer[]{ layer0 }; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object toJmeObject() { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
if (!(object instanceof FbxAnimLayer)) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
|
||||||
|
if (layer0 != null) { |
||||||
|
logger.log(Level.WARNING, "jME3 does not support layered animation. " |
||||||
|
+ "Only first layer has been loaded."); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
layer0 = (FbxAnimLayer) object; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
public class FbxAnimUtil { |
||||||
|
/** |
||||||
|
* Conversion factor from FBX animation time unit to seconds. |
||||||
|
*/ |
||||||
|
public static final double SECONDS_PER_UNIT = 1 / 46186158000d; |
||||||
|
|
||||||
|
public static final String CURVE_NODE_PROPERTY_X = "d|X"; |
||||||
|
public static final String CURVE_NODE_PROPERTY_Y = "d|Y"; |
||||||
|
public static final String CURVE_NODE_PROPERTY_Z = "d|Z"; |
||||||
|
public static final String CURVE_NODE_PROPERTY_VISIBILITY = "d|Visibility"; |
||||||
|
} |
@ -0,0 +1,103 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxId; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
public class FbxBindPose extends FbxObject<Map<FbxId, Matrix4f>> { |
||||||
|
|
||||||
|
private final Map<FbxId, Matrix4f> bindPose = new HashMap<FbxId, Matrix4f>(); |
||||||
|
|
||||||
|
public FbxBindPose(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
for (FbxElement child : element.children) { |
||||||
|
if (!child.id.equals("PoseNode")) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
FbxId node = null; |
||||||
|
float[] matData = null; |
||||||
|
|
||||||
|
for (FbxElement e : child.children) { |
||||||
|
if (e.id.equals("Node")) { |
||||||
|
node = FbxId.create(e.properties.get(0)); |
||||||
|
} else if (e.id.equals("Matrix")) { |
||||||
|
double[] matDataDoubles = (double[]) e.properties.get(0); |
||||||
|
|
||||||
|
if (matDataDoubles.length != 16) { |
||||||
|
// corrupt
|
||||||
|
throw new UnsupportedOperationException("Bind pose matrix " |
||||||
|
+ "must have 16 doubles, but it has " |
||||||
|
+ matDataDoubles.length + ". Data is corrupt"); |
||||||
|
} |
||||||
|
|
||||||
|
matData = new float[16]; |
||||||
|
for (int i = 0; i < matDataDoubles.length; i++) { |
||||||
|
matData[i] = (float) matDataDoubles[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (node != null && matData != null) { |
||||||
|
Matrix4f matrix = new Matrix4f(matData); |
||||||
|
bindPose.put(node, matrix); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Map<FbxId, Matrix4f> toJmeObject() { |
||||||
|
return bindPose; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxCluster extends FbxObject { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxCluster.class.getName()); |
||||||
|
|
||||||
|
private int[] indexes; |
||||||
|
private double[] weights; |
||||||
|
private FbxLimbNode limb; |
||||||
|
|
||||||
|
public FbxCluster(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
for (FbxElement e : element.children) { |
||||||
|
if (e.id.equals("Indexes")) { |
||||||
|
indexes = (int[]) e.properties.get(0); |
||||||
|
} else if (e.id.equals("Weights")) { |
||||||
|
weights = (double[]) e.properties.get(0); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public int[] getVertexIndices() { |
||||||
|
return indexes; |
||||||
|
} |
||||||
|
|
||||||
|
public double[] getWeights() { |
||||||
|
return weights; |
||||||
|
} |
||||||
|
|
||||||
|
public FbxLimbNode getLimb() { |
||||||
|
return limb; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object toJmeObject() { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
if (object instanceof FbxLimbNode) { |
||||||
|
if (limb != null) { |
||||||
|
logger.log(Level.WARNING, "This cluster already has a limb attached. Ignoring."); |
||||||
|
return; |
||||||
|
} |
||||||
|
limb = (FbxLimbNode) object; |
||||||
|
} else { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.scene.plugins.fbx.node.FbxNode; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class FbxLimbNode extends FbxNode { |
||||||
|
|
||||||
|
protected FbxNode skeletonHolder; |
||||||
|
protected Bone bone; |
||||||
|
|
||||||
|
public FbxLimbNode(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
private static void createBones(FbxNode skeletonHolderNode, FbxLimbNode limb, List<Bone> bones) { |
||||||
|
limb.skeletonHolder = skeletonHolderNode; |
||||||
|
|
||||||
|
Bone parentBone = limb.getJmeBone(); |
||||||
|
bones.add(parentBone); |
||||||
|
|
||||||
|
for (FbxNode child : limb.children) { |
||||||
|
if (child instanceof FbxLimbNode) { |
||||||
|
FbxLimbNode childLimb = (FbxLimbNode) child; |
||||||
|
createBones(skeletonHolderNode, childLimb, bones); |
||||||
|
parentBone.addChild(childLimb.getJmeBone()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static Skeleton createSkeleton(FbxNode skeletonHolderNode) { |
||||||
|
if (skeletonHolderNode instanceof FbxLimbNode) { |
||||||
|
throw new UnsupportedOperationException("Limb nodes cannot be skeleton holders"); |
||||||
|
} |
||||||
|
|
||||||
|
List<Bone> bones = new ArrayList<Bone>(); |
||||||
|
|
||||||
|
for (FbxNode child : skeletonHolderNode.getChildren()) { |
||||||
|
if (child instanceof FbxLimbNode) { |
||||||
|
createBones(skeletonHolderNode, (FbxLimbNode) child, bones); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new Skeleton(bones.toArray(new Bone[0])); |
||||||
|
} |
||||||
|
|
||||||
|
public FbxNode getSkeletonHolder() { |
||||||
|
return skeletonHolder; |
||||||
|
} |
||||||
|
|
||||||
|
public Bone getJmeBone() { |
||||||
|
if (bone == null) { |
||||||
|
bone = new Bone(name); |
||||||
|
bone.setBindTransforms(jmeLocalBindPose.getTranslation(), |
||||||
|
jmeLocalBindPose.getRotation(), |
||||||
|
jmeLocalBindPose.getScale()); |
||||||
|
} |
||||||
|
return bone; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> { |
||||||
|
|
||||||
|
private final List<FbxCluster> clusters = new ArrayList<FbxCluster>(); |
||||||
|
|
||||||
|
public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected List<FbxCluster> toJmeObject() { |
||||||
|
return clusters; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
if (object instanceof FbxCluster) { |
||||||
|
clusters.add((FbxCluster) object); |
||||||
|
} else { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,202 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.anim; |
||||||
|
|
||||||
|
import com.jme3.animation.BoneTrack; |
||||||
|
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.plugins.fbx.node.FbxNode; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
/** |
||||||
|
* Maps animation stacks to influenced nodes. |
||||||
|
* Will be used later to create jME3 tracks. |
||||||
|
*/ |
||||||
|
public final class FbxToJmeTrack { |
||||||
|
|
||||||
|
public FbxAnimStack animStack; |
||||||
|
public FbxAnimLayer animLayer; |
||||||
|
public FbxNode node; |
||||||
|
|
||||||
|
// These are not used in map lookups.
|
||||||
|
public transient final Map<String, FbxAnimCurveNode> animCurves = new HashMap<String, FbxAnimCurveNode>(); |
||||||
|
|
||||||
|
public long[] getKeyTimes() { |
||||||
|
Set<Long> keyFrameTimesSet = new HashSet<Long>(); |
||||||
|
for (FbxAnimCurveNode curveNode : animCurves.values()) { |
||||||
|
for (FbxAnimCurve curve : curveNode.getCurves()) { |
||||||
|
for (long keyTime : curve.getKeyTimes()) { |
||||||
|
keyFrameTimesSet.add(keyTime); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
long[] keyFrameTimes = new long[keyFrameTimesSet.size()]; |
||||||
|
int i = 0; |
||||||
|
for (Long keyFrameTime : keyFrameTimesSet) { |
||||||
|
keyFrameTimes[i++] = keyFrameTime; |
||||||
|
} |
||||||
|
Arrays.sort(keyFrameTimes); |
||||||
|
return keyFrameTimes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generate a {@link BoneTrack} from the animation data, for the given |
||||||
|
* boneIndex. |
||||||
|
* |
||||||
|
* @param boneIndex The bone index for which track data is generated for. |
||||||
|
* @param inverseBindPose Inverse bind pose of the bone (in world space). |
||||||
|
* @return A BoneTrack containing the animation data, for the specified |
||||||
|
* boneIndex. |
||||||
|
*/ |
||||||
|
public BoneTrack toJmeBoneTrack(int boneIndex, Transform inverseBindPose) { |
||||||
|
return (BoneTrack) toJmeTrackInternal(boneIndex, inverseBindPose); |
||||||
|
} |
||||||
|
|
||||||
|
public SpatialTrack toJmeSpatialTrack() { |
||||||
|
return (SpatialTrack) toJmeTrackInternal(-1, null); |
||||||
|
} |
||||||
|
|
||||||
|
public float getDuration() { |
||||||
|
long[] keyframes = getKeyTimes(); |
||||||
|
return (float) (keyframes[keyframes.length - 1] * FbxAnimUtil.SECONDS_PER_UNIT); |
||||||
|
} |
||||||
|
|
||||||
|
private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) { |
||||||
|
Transform t = new Transform(); |
||||||
|
t.setTranslation(translation); |
||||||
|
t.setRotation(rotation); |
||||||
|
if (scale != null) { |
||||||
|
t.setScale(scale); |
||||||
|
} |
||||||
|
t.combineWithParent(inverseBindPose); |
||||||
|
|
||||||
|
t.getTranslation(translation); |
||||||
|
t.getRotation(rotation); |
||||||
|
if (scale != null) { |
||||||
|
t.getScale(scale); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) { |
||||||
|
float duration = animStack.getDuration(); |
||||||
|
|
||||||
|
FbxAnimCurveNode translationCurve = animCurves.get("Lcl Translation"); |
||||||
|
FbxAnimCurveNode rotationCurve = animCurves.get("Lcl Rotation"); |
||||||
|
FbxAnimCurveNode scalingCurve = animCurves.get("Lcl Scaling"); |
||||||
|
|
||||||
|
long[] fbxTimes = getKeyTimes(); |
||||||
|
float[] times = new float[fbxTimes.length]; |
||||||
|
|
||||||
|
// Translations / Rotations must be set on all tracks.
|
||||||
|
// (Required for jME3)
|
||||||
|
Vector3f[] translations = new Vector3f[fbxTimes.length]; |
||||||
|
Quaternion[] rotations = new Quaternion[fbxTimes.length]; |
||||||
|
|
||||||
|
Vector3f[] scales = null; |
||||||
|
if (scalingCurve != null) { |
||||||
|
scales = new Vector3f[fbxTimes.length]; |
||||||
|
} |
||||||
|
|
||||||
|
for (int i = 0; i < fbxTimes.length; i++) { |
||||||
|
long fbxTime = fbxTimes[i]; |
||||||
|
float time = (float) (fbxTime * FbxAnimUtil.SECONDS_PER_UNIT); |
||||||
|
|
||||||
|
if (time > duration) { |
||||||
|
// Expand animation duration to fit the curve.
|
||||||
|
duration = time; |
||||||
|
System.out.println("actual duration: " + duration); |
||||||
|
} |
||||||
|
|
||||||
|
times[i] = time; |
||||||
|
if (translationCurve != null) { |
||||||
|
translations[i] = translationCurve.getVector3Value(fbxTime); |
||||||
|
} else { |
||||||
|
translations[i] = new Vector3f(); |
||||||
|
} |
||||||
|
if (rotationCurve != null) { |
||||||
|
rotations[i] = rotationCurve.getQuaternionValue(fbxTime); |
||||||
|
if (i > 0) { |
||||||
|
if (rotations[i - 1].dot(rotations[i]) < 0) { |
||||||
|
System.out.println("rotation will go the long way, oh noes"); |
||||||
|
rotations[i - 1].negate(); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
rotations[i] = new Quaternion(); |
||||||
|
} |
||||||
|
if (scalingCurve != null) { |
||||||
|
scales[i] = scalingCurve.getVector3Value(fbxTime); |
||||||
|
} |
||||||
|
|
||||||
|
if (inverseBindPose != null) { |
||||||
|
applyInverse(translations[i], rotations[i], scales != null ? scales[i] : null, inverseBindPose); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (boneIndex == -1) { |
||||||
|
return new SpatialTrack(times, translations, rotations, scales); |
||||||
|
} else { |
||||||
|
if (scales != null) { |
||||||
|
return new BoneTrack(boneIndex, times, translations, rotations, scales); |
||||||
|
} else { |
||||||
|
return new BoneTrack(boneIndex, times, translations, rotations); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
int hash = 3; |
||||||
|
hash = 79 * hash + this.animStack.hashCode(); |
||||||
|
hash = 79 * hash + this.animLayer.hashCode(); |
||||||
|
hash = 79 * hash + this.node.hashCode(); |
||||||
|
return hash; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if (obj == null) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
final FbxToJmeTrack other = (FbxToJmeTrack) obj; |
||||||
|
return this.node == other.node |
||||||
|
&& this.animStack == other.animStack |
||||||
|
&& this.animLayer == other.animLayer; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,124 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2014 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.fbx.file; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
public class FbxElement { |
||||||
|
|
||||||
|
public String id; |
||||||
|
public List<Object> properties; |
||||||
|
/* |
||||||
|
* Y - signed short |
||||||
|
* C - boolean |
||||||
|
* I - signed integer |
||||||
|
* F - float |
||||||
|
* D - double |
||||||
|
* L - long |
||||||
|
* R - byte array |
||||||
|
* S - string |
||||||
|
* f - array of floats |
||||||
|
* i - array of ints |
||||||
|
* d - array of doubles |
||||||
|
* l - array of longs |
||||||
|
* b - array of boleans |
||||||
|
* c - array of unsigned bytes (represented as array of ints) |
||||||
|
*/ |
||||||
|
public char[] propertiesTypes; |
||||||
|
public List<FbxElement> children = new ArrayList<FbxElement>(); |
||||||
|
|
||||||
|
public FbxElement(int propsCount) { |
||||||
|
this.properties = new ArrayList<Object>(propsCount); |
||||||
|
this.propertiesTypes = new char[propsCount]; |
||||||
|
} |
||||||
|
|
||||||
|
public FbxElement getChildById(String name) { |
||||||
|
for (FbxElement element : children) { |
||||||
|
if (element.id.equals(name)) { |
||||||
|
return element; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public List<FbxElement> getFbxProperties() { |
||||||
|
List<FbxElement> props = new ArrayList<FbxElement>(); |
||||||
|
FbxElement propsElement = null; |
||||||
|
boolean legacy = false; |
||||||
|
|
||||||
|
for (FbxElement element : children) { |
||||||
|
if (element.id.equals("Properties70")) { |
||||||
|
propsElement = element; |
||||||
|
break; |
||||||
|
} else if (element.id.equals("Properties60")) { |
||||||
|
legacy = true; |
||||||
|
propsElement = element; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (propsElement == null) { |
||||||
|
return props; |
||||||
|
} |
||||||
|
|
||||||
|
for (FbxElement prop : propsElement.children) { |
||||||
|
if (prop.id.equals("P") || prop.id.equals("Property")) { |
||||||
|
if (legacy) { |
||||||
|
char[] types = new char[prop.propertiesTypes.length + 1]; |
||||||
|
types[0] = prop.propertiesTypes[0]; |
||||||
|
types[1] = prop.propertiesTypes[0]; |
||||||
|
System.arraycopy(prop.propertiesTypes, 1, types, 2, types.length - 2); |
||||||
|
|
||||||
|
List<Object> values = new ArrayList<Object>(prop.properties); |
||||||
|
values.add(1, values.get(0)); |
||||||
|
|
||||||
|
FbxElement dummyProp = new FbxElement(types.length); |
||||||
|
dummyProp.children = prop.children; |
||||||
|
dummyProp.id = prop.id; |
||||||
|
dummyProp.propertiesTypes = types; |
||||||
|
dummyProp.properties = values; |
||||||
|
props.add(dummyProp); |
||||||
|
} else { |
||||||
|
props.add(prop); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return props; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "FBXElement[id=" + id + ", numProps=" + properties.size() + ", numChildren=" + children.size() + "]"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,130 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.file; |
||||||
|
|
||||||
|
public abstract class FbxId { |
||||||
|
|
||||||
|
public static final FbxId ROOT = new LongFbxId(0); |
||||||
|
|
||||||
|
protected FbxId() { } |
||||||
|
|
||||||
|
private static final class StringFbxId extends FbxId { |
||||||
|
|
||||||
|
private final String id; |
||||||
|
|
||||||
|
public StringFbxId(String id) { |
||||||
|
this.id = id; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return id.hashCode(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if (obj == null || obj.getClass() != StringFbxId.class) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return this.id.equals(((StringFbxId) obj).id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return id; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isNull() { |
||||||
|
return id.equals("Scene\u0000\u0001Model"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static final class LongFbxId extends FbxId { |
||||||
|
|
||||||
|
private final long id; |
||||||
|
|
||||||
|
public LongFbxId(long id) { |
||||||
|
this.id = id; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return (int) (this.id ^ (this.id >>> 32)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if (obj == null || obj.getClass() != LongFbxId.class) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return this.id == ((LongFbxId) obj).id; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isNull() { |
||||||
|
return id == 0; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return Long.toString(id); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public abstract boolean isNull(); |
||||||
|
|
||||||
|
public static FbxId create(Object obj) { |
||||||
|
if (obj instanceof Long) { |
||||||
|
return new LongFbxId((Long)obj); |
||||||
|
} else if (obj instanceof String) { |
||||||
|
return new StringFbxId((String)obj); |
||||||
|
} else { |
||||||
|
throw new UnsupportedOperationException("Unsupported ID object type: " + obj.getClass()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static FbxId getObjectId(FbxElement el) { |
||||||
|
if (el.propertiesTypes.length == 2 |
||||||
|
&& el.propertiesTypes[0] == 'S' |
||||||
|
&& el.propertiesTypes[1] == 'S') { |
||||||
|
return new StringFbxId((String) el.properties.get(0)); |
||||||
|
} else if (el.propertiesTypes.length == 3 |
||||||
|
&& el.propertiesTypes[0] == 'L' |
||||||
|
&& el.propertiesTypes[1] == 'S' |
||||||
|
&& el.propertiesTypes[2] == 'S') { |
||||||
|
return new LongFbxId((Long) el.properties.get(0)); |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,190 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.material; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetLoadException; |
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.asset.AssetNotFoundException; |
||||||
|
import com.jme3.asset.TextureKey; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import com.jme3.texture.Image; |
||||||
|
import com.jme3.util.PlaceholderAssets; |
||||||
|
import java.io.ByteArrayInputStream; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public final class FbxImage extends FbxObject { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxImage.class.getName()); |
||||||
|
|
||||||
|
protected TextureKey key; |
||||||
|
protected String type; // = "Clip"
|
||||||
|
protected String filePath; // = "C:\Whatever\Blah\Texture.png"
|
||||||
|
protected String relativeFilePath; // = "..\Blah\Texture.png"
|
||||||
|
protected byte[] content; // = null, byte[0] OR embedded image data (unknown format?)
|
||||||
|
|
||||||
|
public FbxImage(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
if (element.propertiesTypes.length == 3) { |
||||||
|
type = (String) element.properties.get(2); |
||||||
|
} else { |
||||||
|
type = (String) element.properties.get(1); |
||||||
|
} |
||||||
|
if (type.equals("Clip")) { |
||||||
|
for (FbxElement e : element.children) { |
||||||
|
if (e.id.equals("Type")) { |
||||||
|
type = (String) e.properties.get(0); |
||||||
|
} else if (e.id.equals("FileName")) { |
||||||
|
filePath = (String) e.properties.get(0); |
||||||
|
} else if (e.id.equals("RelativeFilename")) { |
||||||
|
relativeFilePath = (String) e.properties.get(0); |
||||||
|
} else if (e.id.equals("Content")) { |
||||||
|
if (e.properties.size() > 0) { |
||||||
|
byte[] storedContent = (byte[]) e.properties.get(0); |
||||||
|
if (storedContent.length > 0) { |
||||||
|
this.content = storedContent; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Image loadImageSafe(AssetManager assetManager, TextureKey texKey) { |
||||||
|
try { |
||||||
|
return assetManager.loadTexture(texKey).getImage(); |
||||||
|
} catch (AssetNotFoundException ex) { |
||||||
|
return null; |
||||||
|
} catch (AssetLoadException ex) { |
||||||
|
logger.log(Level.WARNING, "Error when loading image: " + texKey, ex); |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static String getFileName(String filePath) { |
||||||
|
// NOTE: Gotta do it this way because new File().getParent()
|
||||||
|
// will not strip forward slashes on Linux / Mac OS X.
|
||||||
|
int fwdSlashIdx = filePath.lastIndexOf("\\"); |
||||||
|
int bkSlashIdx = filePath.lastIndexOf("/"); |
||||||
|
|
||||||
|
if (fwdSlashIdx != -1) { |
||||||
|
filePath = filePath.substring(fwdSlashIdx + 1); |
||||||
|
} else if (bkSlashIdx != -1) { |
||||||
|
filePath = filePath.substring(bkSlashIdx + 1); |
||||||
|
} |
||||||
|
|
||||||
|
return filePath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The texture key that was used to load the image. |
||||||
|
* Only valid after {@link #getJmeObject()} has been called. |
||||||
|
* @return the key that was used to load the image. |
||||||
|
*/ |
||||||
|
public TextureKey getTextureKey() { |
||||||
|
return key; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object toJmeObject() { |
||||||
|
Image image = null; |
||||||
|
String fileName = null; |
||||||
|
String relativeFilePathJme; |
||||||
|
|
||||||
|
if (filePath != null) { |
||||||
|
fileName = getFileName(filePath); |
||||||
|
} else if (relativeFilePath != null) { |
||||||
|
fileName = getFileName(relativeFilePath); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if (fileName != null) { |
||||||
|
try { |
||||||
|
// Try to load filename relative to FBX folder
|
||||||
|
key = new TextureKey(sceneFolderName + fileName); |
||||||
|
key.setGenerateMips(true); |
||||||
|
image = loadImageSafe(assetManager, key); |
||||||
|
|
||||||
|
// Try to load relative filepath relative to FBX folder
|
||||||
|
if (image == null && relativeFilePath != null) { |
||||||
|
// Convert Windows paths to jME3 paths
|
||||||
|
relativeFilePathJme = relativeFilePath.replace('\\', '/'); |
||||||
|
key = new TextureKey(sceneFolderName + relativeFilePathJme); |
||||||
|
key.setGenerateMips(true); |
||||||
|
image = loadImageSafe(assetManager, key); |
||||||
|
} |
||||||
|
|
||||||
|
// Try to load embedded image
|
||||||
|
if (image == null && content != null && content.length > 0) { |
||||||
|
key = new TextureKey(fileName); |
||||||
|
key.setGenerateMips(true); |
||||||
|
InputStream is = new ByteArrayInputStream(content); |
||||||
|
image = assetManager.loadAssetFromStream(key, is).getImage(); |
||||||
|
|
||||||
|
// NOTE: embedded texture doesn't exist in the asset manager,
|
||||||
|
// so the texture key must not be saved.
|
||||||
|
key = null; |
||||||
|
} |
||||||
|
} catch (AssetLoadException ex) { |
||||||
|
logger.log(Level.WARNING, "Error while attempting to load texture {0}:\n{1}", |
||||||
|
new Object[]{name, ex.toString()}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (image == null) { |
||||||
|
logger.log(Level.WARNING, "Cannot locate {0} for texture {1}", new Object[]{fileName, name}); |
||||||
|
image = PlaceholderAssets.getPlaceholderImage(assetManager); |
||||||
|
} |
||||||
|
|
||||||
|
// NOTE: At this point, key will be set to the last
|
||||||
|
// attempted texture key that we attempted to load.
|
||||||
|
|
||||||
|
return image; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,363 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.material; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
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.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import com.jme3.texture.Texture; |
||||||
|
import com.jme3.texture.image.ColorSpace; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxMaterial extends FbxObject<Material> { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxMaterial.class.getName()); |
||||||
|
|
||||||
|
private String shadingModel; // TODO: do we care about this? lambert just has no specular?
|
||||||
|
private final FbxMaterialProperties properties = new FbxMaterialProperties(); |
||||||
|
|
||||||
|
public FbxMaterial(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
if(!getSubclassName().equals("")) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
FbxElement shadingModelEl = element.getChildById("ShadingModel"); |
||||||
|
if (shadingModelEl != null) { |
||||||
|
shadingModel = (String) shadingModelEl.properties.get(0); |
||||||
|
if (!shadingModel.equals("")) { |
||||||
|
if (!shadingModel.equalsIgnoreCase("phong") && |
||||||
|
!shadingModel.equalsIgnoreCase("lambert")) { |
||||||
|
logger.log(Level.WARNING, "FBX material uses unknown shading model: {0}. " |
||||||
|
+ "Material may display incorrectly.", shadingModel); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (FbxElement child : element.getFbxProperties()) { |
||||||
|
properties.setPropertyFromElement(child); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
if (!(object instanceof FbxTexture)) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
|
||||||
|
properties.setPropertyTexture(property, (FbxTexture) object); |
||||||
|
} |
||||||
|
|
||||||
|
private static void multRGB(ColorRGBA color, float factor) { |
||||||
|
color.r *= factor; |
||||||
|
color.g *= factor; |
||||||
|
color.b *= factor; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Material toJmeObject() { |
||||||
|
ColorRGBA ambient = null; |
||||||
|
ColorRGBA diffuse = null; |
||||||
|
ColorRGBA specular = null; |
||||||
|
ColorRGBA transp = null; |
||||||
|
ColorRGBA emissive = null; |
||||||
|
float shininess = 1f; |
||||||
|
boolean separateTexCoord = false; |
||||||
|
|
||||||
|
Texture diffuseMap = null; |
||||||
|
Texture specularMap = null; |
||||||
|
Texture normalMap = null; |
||||||
|
Texture transpMap = null; |
||||||
|
Texture emitMap = null; |
||||||
|
Texture aoMap = null; |
||||||
|
|
||||||
|
FbxTexture fbxDiffuseMap = null; |
||||||
|
|
||||||
|
Object diffuseColor = properties.getProperty("DiffuseColor"); |
||||||
|
if (diffuseColor != null) { |
||||||
|
if (diffuseColor instanceof ColorRGBA) { |
||||||
|
diffuse = ((ColorRGBA) diffuseColor).clone(); |
||||||
|
} else if (diffuseColor instanceof FbxTexture) { |
||||||
|
FbxTexture tex = (FbxTexture) diffuseColor; |
||||||
|
fbxDiffuseMap = tex; |
||||||
|
diffuseMap = tex.getJmeObject(); |
||||||
|
diffuseMap.getImage().setColorSpace(ColorSpace.sRGB); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object diffuseFactor = properties.getProperty("DiffuseFactor"); |
||||||
|
if (diffuseFactor != null && diffuseFactor instanceof Float) { |
||||||
|
float factor = (Float)diffuseFactor; |
||||||
|
if (diffuse != null) { |
||||||
|
multRGB(diffuse, factor); |
||||||
|
} else { |
||||||
|
diffuse = new ColorRGBA(factor, factor, factor, 1f); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object specularColor = properties.getProperty("SpecularColor"); |
||||||
|
if (specularColor != null) { |
||||||
|
if (specularColor instanceof ColorRGBA) { |
||||||
|
specular = ((ColorRGBA) specularColor).clone(); |
||||||
|
} else if (specularColor instanceof FbxTexture) { |
||||||
|
FbxTexture tex = (FbxTexture) specularColor; |
||||||
|
specularMap = tex.getJmeObject(); |
||||||
|
specularMap.getImage().setColorSpace(ColorSpace.sRGB); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object specularFactor = properties.getProperty("SpecularFactor"); |
||||||
|
if (specularFactor != null && specularFactor instanceof Float) { |
||||||
|
float factor = (Float)specularFactor; |
||||||
|
if (specular != null) { |
||||||
|
multRGB(specular, factor); |
||||||
|
} else { |
||||||
|
specular = new ColorRGBA(factor, factor, factor, 1f); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object transparentColor = properties.getProperty("TransparentColor"); |
||||||
|
if (transparentColor != null) { |
||||||
|
if (transparentColor instanceof ColorRGBA) { |
||||||
|
transp = ((ColorRGBA) transparentColor).clone(); |
||||||
|
} else if (transparentColor instanceof FbxTexture) { |
||||||
|
FbxTexture tex = (FbxTexture) transparentColor; |
||||||
|
transpMap = tex.getJmeObject(); |
||||||
|
transpMap.getImage().setColorSpace(ColorSpace.sRGB); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object transparencyFactor = properties.getProperty("TransparencyFactor"); |
||||||
|
if (transparencyFactor != null && transparencyFactor instanceof Float) { |
||||||
|
float factor = (Float)transparencyFactor; |
||||||
|
if (transp != null) { |
||||||
|
transp.a *= factor; |
||||||
|
} else { |
||||||
|
transp = new ColorRGBA(1f, 1f, 1f, factor); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object emissiveColor = properties.getProperty("EmissiveColor"); |
||||||
|
if (emissiveColor != null) { |
||||||
|
if (emissiveColor instanceof ColorRGBA) { |
||||||
|
emissive = ((ColorRGBA)emissiveColor).clone(); |
||||||
|
} else if (emissiveColor instanceof FbxTexture) { |
||||||
|
FbxTexture tex = (FbxTexture) emissiveColor; |
||||||
|
emitMap = tex.getJmeObject(); |
||||||
|
emitMap.getImage().setColorSpace(ColorSpace.sRGB); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object emissiveFactor = properties.getProperty("EmissiveFactor"); |
||||||
|
if (emissiveFactor != null && emissiveFactor instanceof Float) { |
||||||
|
float factor = (Float)emissiveFactor; |
||||||
|
if (emissive != null) { |
||||||
|
multRGB(emissive, factor); |
||||||
|
} else { |
||||||
|
emissive = new ColorRGBA(factor, factor, factor, 1f); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object ambientColor = properties.getProperty("AmbientColor"); |
||||||
|
if (ambientColor != null && ambientColor instanceof ColorRGBA) { |
||||||
|
ambient = ((ColorRGBA)ambientColor).clone(); |
||||||
|
} |
||||||
|
|
||||||
|
Object ambientFactor = properties.getProperty("AmbientFactor"); |
||||||
|
if (ambientFactor != null && ambientFactor instanceof Float) { |
||||||
|
float factor = (Float)ambientFactor; |
||||||
|
if (ambient != null) { |
||||||
|
multRGB(ambient, factor); |
||||||
|
} else { |
||||||
|
ambient = new ColorRGBA(factor, factor, factor, 1f); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object shininessFactor = properties.getProperty("Shininess"); |
||||||
|
if (shininessFactor != null) { |
||||||
|
if (shininessFactor instanceof Float) { |
||||||
|
shininess = (Float) shininessFactor; |
||||||
|
} else if (shininessFactor instanceof FbxTexture) { |
||||||
|
// TODO: support shininess textures
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object bumpNormal = properties.getProperty("NormalMap"); |
||||||
|
if (bumpNormal != null) { |
||||||
|
if (bumpNormal instanceof FbxTexture) { |
||||||
|
// TODO: check all meshes that use this material have tangents
|
||||||
|
// otherwise shading errors occur
|
||||||
|
FbxTexture tex = (FbxTexture) bumpNormal; |
||||||
|
normalMap = tex.getJmeObject(); |
||||||
|
normalMap.getImage().setColorSpace(ColorSpace.Linear); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Object aoColor = properties.getProperty("DiffuseColor2"); |
||||||
|
if (aoColor != null) { |
||||||
|
if (aoColor instanceof FbxTexture) { |
||||||
|
FbxTexture tex = (FbxTexture) aoColor; |
||||||
|
if (tex.getUvSet() != null && fbxDiffuseMap != null) { |
||||||
|
if (!tex.getUvSet().equals(fbxDiffuseMap.getUvSet())) { |
||||||
|
separateTexCoord = true; |
||||||
|
} |
||||||
|
} |
||||||
|
aoMap = tex.getJmeObject(); |
||||||
|
aoMap.getImage().setColorSpace(ColorSpace.sRGB); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: how to disable transparency from diffuse map?? Need "UseAlpha" again..
|
||||||
|
|
||||||
|
assert ambient == null || ambient.a == 1f; |
||||||
|
assert diffuse == null || diffuse.a == 1f; |
||||||
|
assert specular == null || specular.a == 1f; |
||||||
|
assert emissive == null || emissive.a == 1f; |
||||||
|
assert transp == null || (transp.r == 1f && transp.g == 1f && transp.b == 1f); |
||||||
|
|
||||||
|
// If shininess is less than 1.0, the lighting shader won't be able
|
||||||
|
// to handle it. Gotta disable specularity then.
|
||||||
|
if (shininess < 1f) { |
||||||
|
shininess = 1f; |
||||||
|
specular = ColorRGBA.Black; |
||||||
|
} |
||||||
|
|
||||||
|
// Try to guess if we need to enable alpha blending.
|
||||||
|
// FBX does not specify this explicitly.
|
||||||
|
boolean useAlphaBlend = false; |
||||||
|
|
||||||
|
if (diffuseMap != null && diffuseMap == transpMap) { |
||||||
|
// jME3 already uses alpha from diffuseMap
|
||||||
|
// (if alpha blend is enabled)
|
||||||
|
useAlphaBlend = true; |
||||||
|
transpMap = null; |
||||||
|
} else if (diffuseMap != null && transpMap != null && diffuseMap != transpMap) { |
||||||
|
// TODO: potential bug here. Alpha from diffuse may
|
||||||
|
// leak unintentionally.
|
||||||
|
useAlphaBlend = true; |
||||||
|
} else if (transpMap != null) { |
||||||
|
// We have alpha map but no diffuse map, OK.
|
||||||
|
useAlphaBlend = true; |
||||||
|
} |
||||||
|
|
||||||
|
if (transp != null && transp.a != 1f) { |
||||||
|
// Consolidate transp into diffuse
|
||||||
|
// (jME3 doesn't use a separate alpha color)
|
||||||
|
|
||||||
|
// TODO: potential bug here. Alpha from diffuse may
|
||||||
|
// leak unintentionally.
|
||||||
|
useAlphaBlend = true; |
||||||
|
if (diffuse != null) { |
||||||
|
diffuse.a = transp.a; |
||||||
|
} else { |
||||||
|
diffuse = transp; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||||
|
mat.setName(name); |
||||||
|
|
||||||
|
// TODO: load this from FBX material.
|
||||||
|
mat.setReceivesShadows(true); |
||||||
|
|
||||||
|
if (useAlphaBlend) { |
||||||
|
// No idea if this is a transparent or translucent model, gotta guess..
|
||||||
|
mat.setTransparent(true); |
||||||
|
mat.setFloat("AlphaDiscardThreshold", 0.01f); |
||||||
|
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); |
||||||
|
} |
||||||
|
|
||||||
|
mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); |
||||||
|
|
||||||
|
// Set colors.
|
||||||
|
if (ambient != null || diffuse != null || specular != null) { |
||||||
|
// If either of those is set, we have to set them all.
|
||||||
|
// NOTE: default specular is black, unless it is set explicitly.
|
||||||
|
mat.setBoolean("UseMaterialColors", true); |
||||||
|
mat.setColor("Ambient", /*ambient != null ? ambient :*/ ColorRGBA.White); |
||||||
|
mat.setColor("Diffuse", diffuse != null ? diffuse : ColorRGBA.White); |
||||||
|
mat.setColor("Specular", specular != null ? specular : ColorRGBA.Black); |
||||||
|
} |
||||||
|
|
||||||
|
if (emissive != null) { |
||||||
|
mat.setColor("GlowColor", emissive); |
||||||
|
} |
||||||
|
|
||||||
|
// Set shininess.
|
||||||
|
if (shininess > 1f) { |
||||||
|
// Convert shininess from
|
||||||
|
// Phong (FBX shading model) to Blinn (jME3 shading model).
|
||||||
|
float blinnShininess = (shininess * 5.1f) + 1f; |
||||||
|
mat.setFloat("Shininess", blinnShininess); |
||||||
|
} |
||||||
|
|
||||||
|
// Set textures.
|
||||||
|
if (diffuseMap != null) { |
||||||
|
mat.setTexture("DiffuseMap", diffuseMap); |
||||||
|
} |
||||||
|
if (specularMap != null) { |
||||||
|
mat.setTexture("SpecularMap", specularMap); |
||||||
|
} |
||||||
|
if (normalMap != null) { |
||||||
|
mat.setTexture("NormalMap", normalMap); |
||||||
|
} |
||||||
|
if (transpMap != null) { |
||||||
|
// mat.setTexture("AlphaMap", transpMap);
|
||||||
|
} |
||||||
|
if (emitMap != null) { |
||||||
|
mat.setTexture("GlowMap", emitMap); |
||||||
|
} |
||||||
|
if (aoMap != null) { |
||||||
|
mat.setTexture("LightMap", aoMap); |
||||||
|
if (separateTexCoord) { |
||||||
|
mat.setBoolean("SeparateTexCoord", true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return mat; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,234 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.material; |
||||||
|
|
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxMaterialProperties { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxMaterialProperties.class.getName()); |
||||||
|
|
||||||
|
private static final Map<String, FBXMaterialProperty> propertyMetaMap = new HashMap<String, FBXMaterialProperty>(); |
||||||
|
|
||||||
|
private final Map<String, Object> propertyValueMap = new HashMap<String, Object>(); |
||||||
|
|
||||||
|
private static enum Type { |
||||||
|
Color, |
||||||
|
Alpha, |
||||||
|
Factor, |
||||||
|
Texture2DOrColor, |
||||||
|
Texture2DOrAlpha, |
||||||
|
Texture2DOrFactor, |
||||||
|
Texture2D, |
||||||
|
TextureCubeMap, |
||||||
|
Ignore; |
||||||
|
} |
||||||
|
|
||||||
|
private static class FBXMaterialProperty { |
||||||
|
private final String name; |
||||||
|
private final Type type; |
||||||
|
|
||||||
|
public FBXMaterialProperty(String name, Type type) { |
||||||
|
this.name = name; |
||||||
|
this.type = type; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static boolean isValueAcceptable(Type type, Object value) { |
||||||
|
if (type == Type.Ignore) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (value instanceof FbxTexture) { |
||||||
|
switch (type) { |
||||||
|
case Texture2D: |
||||||
|
case Texture2DOrAlpha: |
||||||
|
case Texture2DOrColor: |
||||||
|
case Texture2DOrFactor: |
||||||
|
return true; |
||||||
|
} |
||||||
|
} else if (value instanceof ColorRGBA) { |
||||||
|
switch (type) { |
||||||
|
case Color: |
||||||
|
case Texture2DOrColor: |
||||||
|
return true; |
||||||
|
} |
||||||
|
} else if (value instanceof Float) { |
||||||
|
switch (type) { |
||||||
|
case Alpha: |
||||||
|
case Factor: |
||||||
|
case Texture2DOrAlpha: |
||||||
|
case Texture2DOrFactor: |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
private static void defineProp(String name, Type type) { |
||||||
|
propertyMetaMap.put(name, new FBXMaterialProperty(name, type)); |
||||||
|
} |
||||||
|
|
||||||
|
private static void defineAlias(String alias, String name) { |
||||||
|
propertyMetaMap.put(alias, propertyMetaMap.get(name)); |
||||||
|
} |
||||||
|
|
||||||
|
static { |
||||||
|
// Lighting->Ambient
|
||||||
|
// TODO: Add support for AmbientMap??
|
||||||
|
defineProp("AmbientColor", Type.Color); |
||||||
|
defineProp("AmbientFactor", Type.Factor); |
||||||
|
defineAlias("Ambient", "AmbientColor"); |
||||||
|
|
||||||
|
// Lighting->DiffuseMap/Diffuse
|
||||||
|
defineProp("DiffuseColor", Type.Texture2DOrColor); |
||||||
|
defineProp("DiffuseFactor", Type.Factor); |
||||||
|
defineAlias("Diffuse", "DiffuseColor"); |
||||||
|
|
||||||
|
// Lighting->SpecularMap/Specular
|
||||||
|
defineProp("SpecularColor", Type.Texture2DOrColor); |
||||||
|
defineProp("SpecularFactor", Type.Factor); |
||||||
|
defineAlias("Specular", "SpecularColor"); |
||||||
|
|
||||||
|
// Lighting->AlphaMap/Diffuse
|
||||||
|
defineProp("TransparentColor", Type.Texture2DOrAlpha); |
||||||
|
|
||||||
|
// Lighting->Diffuse
|
||||||
|
defineProp("TransparencyFactor", Type.Alpha); |
||||||
|
defineAlias("Opacity", "TransparencyFactor"); |
||||||
|
|
||||||
|
// Lighting->GlowMap/GlowColor
|
||||||
|
defineProp("EmissiveColor", Type.Texture2DOrColor); |
||||||
|
defineProp("EmissiveFactor", Type.Factor); |
||||||
|
defineAlias("Emissive", "EmissiveColor"); |
||||||
|
|
||||||
|
// Lighting->Shininess
|
||||||
|
defineProp("Shininess", Type.Factor); |
||||||
|
defineAlias("ShininessExponent", "Shininess"); |
||||||
|
|
||||||
|
// Lighting->NormalMap
|
||||||
|
defineProp("NormalMap", Type.Texture2D); |
||||||
|
defineAlias("Normal", "NormalMap"); |
||||||
|
|
||||||
|
// Lighting->EnvMap
|
||||||
|
defineProp("ReflectionColor", Type.Texture2DOrColor); |
||||||
|
|
||||||
|
// Lighting->FresnelParams
|
||||||
|
defineProp("Reflectivity", Type.Factor); |
||||||
|
defineAlias("ReflectionFactor", "Reflectivity"); |
||||||
|
|
||||||
|
// ShadingModel is no longer specified under Properties element.
|
||||||
|
defineProp("ShadingModel", Type.Ignore); |
||||||
|
|
||||||
|
// MultiLayer materials aren't supported anyway..
|
||||||
|
defineProp("MultiLayer", Type.Ignore); |
||||||
|
|
||||||
|
// Not sure what this is.. NormalMap again??
|
||||||
|
defineProp("Bump", Type.Texture2DOrColor); |
||||||
|
|
||||||
|
defineProp("BumpFactor", Type.Factor); |
||||||
|
defineProp("DisplacementColor", Type.Color); |
||||||
|
defineProp("DisplacementFactor", Type.Factor); |
||||||
|
} |
||||||
|
|
||||||
|
public void setPropertyTexture(String name, FbxTexture texture) { |
||||||
|
FBXMaterialProperty prop = propertyMetaMap.get(name); |
||||||
|
|
||||||
|
if (prop == null) { |
||||||
|
logger.log(Level.WARNING, "Unknown FBX material property '{0}'", name); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (propertyValueMap.get(name) instanceof FbxTexture) { |
||||||
|
// Multiple / layered textures ..
|
||||||
|
// Just write into 2nd slot for now (maybe will use for lightmaps).
|
||||||
|
name = name + "2"; |
||||||
|
} |
||||||
|
|
||||||
|
propertyValueMap.put(name, texture); |
||||||
|
} |
||||||
|
|
||||||
|
public void setPropertyFromElement(FbxElement propertyElement) { |
||||||
|
String name = (String) propertyElement.properties.get(0); |
||||||
|
FBXMaterialProperty prop = propertyMetaMap.get(name); |
||||||
|
|
||||||
|
if (prop == null) { |
||||||
|
logger.log(Level.WARNING, "Unknown FBX material property ''{0}''", name); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// It is either a color, alpha, or factor.
|
||||||
|
// Textures can only be set via setPropertyTexture.
|
||||||
|
|
||||||
|
// If it is an alias, get the real name of the property.
|
||||||
|
String realName = prop.name; |
||||||
|
|
||||||
|
switch (prop.type) { |
||||||
|
case Alpha: |
||||||
|
case Factor: |
||||||
|
case Texture2DOrFactor: |
||||||
|
case Texture2DOrAlpha: |
||||||
|
double value = (Double) propertyElement.properties.get(4); |
||||||
|
propertyValueMap.put(realName, (float)value); |
||||||
|
break; |
||||||
|
case Color: |
||||||
|
case Texture2DOrColor: |
||||||
|
double x = (Double) propertyElement.properties.get(4); |
||||||
|
double y = (Double) propertyElement.properties.get(5); |
||||||
|
double z = (Double) propertyElement.properties.get(6); |
||||||
|
ColorRGBA color = new ColorRGBA((float)x, (float)y, (float)z, 1f); |
||||||
|
propertyValueMap.put(realName, color); |
||||||
|
break; |
||||||
|
default: |
||||||
|
logger.log(Level.WARNING, "FBX material property ''{0}'' requires a texture.", name); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Object getProperty(String name) { |
||||||
|
return propertyValueMap.get(name); |
||||||
|
} |
||||||
|
|
||||||
|
public static Type getPropertyType(String name) { |
||||||
|
FBXMaterialProperty prop = propertyMetaMap.get(name); |
||||||
|
if (prop == null) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
return prop.type; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,146 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.material; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.asset.TextureKey; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import com.jme3.texture.Image; |
||||||
|
import com.jme3.texture.Texture; |
||||||
|
import com.jme3.texture.Texture.MagFilter; |
||||||
|
import com.jme3.texture.Texture.MinFilter; |
||||||
|
import com.jme3.texture.Texture.WrapAxis; |
||||||
|
import com.jme3.texture.Texture.WrapMode; |
||||||
|
import com.jme3.texture.Texture2D; |
||||||
|
import com.jme3.util.PlaceholderAssets; |
||||||
|
|
||||||
|
public class FbxTexture extends FbxObject<Texture> { |
||||||
|
|
||||||
|
private static enum AlphaSource { |
||||||
|
None, |
||||||
|
FromTextureAlpha, |
||||||
|
FromTextureIntensity; |
||||||
|
} |
||||||
|
|
||||||
|
private String type; |
||||||
|
private FbxImage media; |
||||||
|
|
||||||
|
// TODO: not currently used.
|
||||||
|
private AlphaSource alphaSource = AlphaSource.FromTextureAlpha; |
||||||
|
private String uvSet; |
||||||
|
private int wrapModeU = 0, wrapModeV = 0; |
||||||
|
private final Vector2f uvTranslation = new Vector2f(0, 0); |
||||||
|
private final Vector2f uvScaling = new Vector2f(1, 1); |
||||||
|
|
||||||
|
public FbxTexture(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
public String getUvSet() { |
||||||
|
return uvSet; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Texture toJmeObject() { |
||||||
|
Image image = null; |
||||||
|
TextureKey key = null; |
||||||
|
if (media != null) { |
||||||
|
image = (Image) media.getJmeObject(); |
||||||
|
key = media.getTextureKey(); |
||||||
|
} |
||||||
|
if (image == null) { |
||||||
|
image = PlaceholderAssets.getPlaceholderImage(assetManager); |
||||||
|
} |
||||||
|
Texture2D tex = new Texture2D(image); |
||||||
|
if (key != null) { |
||||||
|
tex.setKey(key); |
||||||
|
tex.setName(key.getName()); |
||||||
|
tex.setAnisotropicFilter(key.getAnisotropy()); |
||||||
|
} |
||||||
|
tex.setMinFilter(MinFilter.Trilinear); |
||||||
|
tex.setMagFilter(MagFilter.Bilinear); |
||||||
|
if (wrapModeU == 0) { |
||||||
|
tex.setWrap(WrapAxis.S, WrapMode.Repeat); |
||||||
|
} |
||||||
|
if (wrapModeV == 0) { |
||||||
|
tex.setWrap(WrapAxis.T, WrapMode.Repeat); |
||||||
|
} |
||||||
|
return tex; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
if (getSubclassName().equals("")) { |
||||||
|
for (FbxElement e : element.children) { |
||||||
|
if (e.id.equals("Type")) { |
||||||
|
type = (String) e.properties.get(0); |
||||||
|
} |
||||||
|
/*else if (e.id.equals("FileName")) { |
||||||
|
filename = (String) e.properties.get(0); |
||||||
|
}*/ |
||||||
|
} |
||||||
|
|
||||||
|
for (FbxElement prop : element.getFbxProperties()) { |
||||||
|
String propName = (String) prop.properties.get(0); |
||||||
|
if (propName.equals("AlphaSource")) { |
||||||
|
// ???
|
||||||
|
} else if (propName.equals("UVSet")) { |
||||||
|
uvSet = (String) prop.properties.get(4); |
||||||
|
} else if (propName.equals("WrapModeU")) { |
||||||
|
wrapModeU = (Integer) prop.properties.get(4); |
||||||
|
} else if (propName.equals("WrapModeV")) { |
||||||
|
wrapModeV = (Integer) prop.properties.get(4); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
if (!(object instanceof FbxImage)) { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
// } else if (media != null) {
|
||||||
|
// throw new UnsupportedOperationException("An image is already attached to this texture.");
|
||||||
|
} |
||||||
|
|
||||||
|
this.media = (FbxImage) object; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.mesh; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.EnumMap; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public final class FbxLayer { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxLayer.class.getName()); |
||||||
|
|
||||||
|
public static class FbxLayerElementRef { |
||||||
|
FbxLayerElement.Type layerElementType; |
||||||
|
int layerElementIndex; |
||||||
|
FbxLayerElement layerElement; |
||||||
|
} |
||||||
|
|
||||||
|
int layer; |
||||||
|
final EnumMap<FbxLayerElement.Type, FbxLayerElementRef> references = |
||||||
|
new EnumMap<FbxLayerElement.Type, FbxLayerElementRef>(FbxLayerElement.Type.class); |
||||||
|
|
||||||
|
private FbxLayer() { } |
||||||
|
|
||||||
|
public Object getVertexData(FbxLayerElement.Type type, int polygonIndex, |
||||||
|
int polygonVertexIndex, int positionIndex, int edgeIndex) { |
||||||
|
FbxLayerElementRef reference = references.get(type); |
||||||
|
if (reference == null) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
return reference.layerElement.getVertexData(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public FbxLayerElement.Type[] getLayerElementTypes() { |
||||||
|
FbxLayerElement.Type[] types = new FbxLayerElement.Type[references.size()]; |
||||||
|
references.keySet().toArray(types); |
||||||
|
return types; |
||||||
|
} |
||||||
|
|
||||||
|
public void setLayerElements(Collection<FbxLayerElement> layerElements) { |
||||||
|
for (FbxLayerElement layerElement : layerElements) { |
||||||
|
FbxLayerElementRef reference = references.get(layerElement.type); |
||||||
|
if (reference != null && reference.layerElementIndex == layerElement.index) { |
||||||
|
reference.layerElement = layerElement; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static FbxLayer fromElement(FbxElement element) { |
||||||
|
FbxLayer layer = new FbxLayer(); |
||||||
|
layer.layer = (Integer)element.properties.get(0); |
||||||
|
next_element: for (FbxElement child : element.children) { |
||||||
|
if (!child.id.equals("LayerElement")) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
FbxLayerElementRef ref = new FbxLayerElementRef(); |
||||||
|
for (FbxElement child2 : child.children) { |
||||||
|
if (child2.id.equals("Type")) { |
||||||
|
String layerElementTypeStr = (String) child2.properties.get(0); |
||||||
|
layerElementTypeStr = layerElementTypeStr.substring("LayerElement".length()); |
||||||
|
try { |
||||||
|
ref.layerElementType = FbxLayerElement.Type.valueOf(layerElementTypeStr); |
||||||
|
} catch (IllegalArgumentException ex) { |
||||||
|
logger.log(Level.WARNING, "Unsupported layer type: {0}. Ignoring.", layerElementTypeStr); |
||||||
|
continue next_element; |
||||||
|
} |
||||||
|
} else if (child2.id.equals("TypedIndex")) { |
||||||
|
ref.layerElementIndex = (Integer) child2.properties.get(0); |
||||||
|
} |
||||||
|
} |
||||||
|
layer.references.put(ref.layerElementType, ref); |
||||||
|
} |
||||||
|
return layer; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,243 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.mesh; |
||||||
|
|
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxLayerElement { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxLayerElement.class.getName()); |
||||||
|
|
||||||
|
public enum Type { |
||||||
|
Position, // Vector3f (isn't actually defined in FBX)
|
||||||
|
BoneIndex, // List<Integer> (isn't actually defined in FBX)
|
||||||
|
BoneWeight, // List<Float> isn't actually defined in FBX)
|
||||||
|
Normal, // Vector3f
|
||||||
|
Binormal, // Vector3f
|
||||||
|
Tangent, // Vector3f
|
||||||
|
UV, // Vector2f
|
||||||
|
TransparentUV, // Vector2f
|
||||||
|
Color, // ColorRGBA
|
||||||
|
Material, // Integer
|
||||||
|
Smoothing, // Integer
|
||||||
|
Visibility, // Integer
|
||||||
|
Texture, // ??? (FBX 6.x)
|
||||||
|
PolygonGroup, // ??? (FBX 6.x)
|
||||||
|
NormalMapTextures, // ??? (FBX 6.x)
|
||||||
|
SpecularFactorUV, // ??? (FBX 6.x)
|
||||||
|
NormalMapUV, // ??? (FBX 6.x)
|
||||||
|
SpecularFactorTextures, // ??? (FBX 6.x)
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public enum MappingInformationType { |
||||||
|
NoMappingInformation, |
||||||
|
AllSame, |
||||||
|
ByPolygonVertex, |
||||||
|
ByVertex, |
||||||
|
ByPolygon, |
||||||
|
ByEdge; |
||||||
|
} |
||||||
|
|
||||||
|
public enum ReferenceInformationType { |
||||||
|
Direct, |
||||||
|
IndexToDirect; |
||||||
|
} |
||||||
|
|
||||||
|
public enum TextureBlendMode { |
||||||
|
Translucent; |
||||||
|
} |
||||||
|
|
||||||
|
private static final Set<String> indexTypes = new HashSet<String>(); |
||||||
|
|
||||||
|
static { |
||||||
|
indexTypes.add("UVIndex"); |
||||||
|
indexTypes.add("NormalsIndex"); |
||||||
|
indexTypes.add("TangentsIndex"); |
||||||
|
indexTypes.add("BinormalsIndex"); |
||||||
|
indexTypes.add("Smoothing"); |
||||||
|
indexTypes.add("Materials"); |
||||||
|
indexTypes.add("TextureId"); |
||||||
|
indexTypes.add("ColorIndex"); |
||||||
|
indexTypes.add("PolygonGroup"); |
||||||
|
} |
||||||
|
|
||||||
|
int index; |
||||||
|
Type type; |
||||||
|
ReferenceInformationType refInfoType; |
||||||
|
MappingInformationType mapInfoType; |
||||||
|
String name = ""; |
||||||
|
Object[] data; |
||||||
|
int[] dataIndices; |
||||||
|
|
||||||
|
private FbxLayerElement() { } |
||||||
|
|
||||||
|
public String toString() { |
||||||
|
return "LayerElement[type=" + type + ", layer=" + index + |
||||||
|
", mapInfoType=" + mapInfoType + ", refInfoType=" + refInfoType + "]"; |
||||||
|
} |
||||||
|
|
||||||
|
private Object getVertexDataIndexToDirect(int polygonIndex, int polygonVertexIndex, |
||||||
|
int positionIndex, int edgeIndex) { |
||||||
|
switch (mapInfoType) { |
||||||
|
case AllSame: return data[dataIndices[0]]; |
||||||
|
case ByPolygon: return data[dataIndices[polygonIndex]]; |
||||||
|
case ByPolygonVertex: return data[dataIndices[polygonVertexIndex]]; |
||||||
|
case ByVertex: return data[dataIndices[positionIndex]]; |
||||||
|
case ByEdge: return data[dataIndices[edgeIndex]]; |
||||||
|
default: throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Object getVertexDataDirect(int polygonIndex, int polygonVertexIndex, |
||||||
|
int positionIndex, int edgeIndex) { |
||||||
|
switch (mapInfoType) { |
||||||
|
case AllSame: return data[0]; |
||||||
|
case ByPolygon: return data[polygonIndex]; |
||||||
|
case ByPolygonVertex: return data[polygonVertexIndex]; |
||||||
|
case ByVertex: return data[positionIndex]; |
||||||
|
case ByEdge: return data[edgeIndex]; |
||||||
|
default: throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Object getVertexData(int polygonIndex, int polygonVertexIndex, int positionIndex, int edgeIndex) { |
||||||
|
switch (refInfoType) { |
||||||
|
case Direct: return getVertexDataDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex); |
||||||
|
case IndexToDirect: return getVertexDataIndexToDirect(polygonIndex, polygonVertexIndex, positionIndex, edgeIndex); |
||||||
|
default: return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static FbxLayerElement fromPositions(double[] positionData) { |
||||||
|
FbxLayerElement layerElement = new FbxLayerElement(); |
||||||
|
layerElement.index = -1; |
||||||
|
layerElement.name = ""; |
||||||
|
layerElement.type = Type.Position; |
||||||
|
layerElement.mapInfoType = MappingInformationType.ByVertex; |
||||||
|
layerElement.refInfoType = ReferenceInformationType.Direct; |
||||||
|
layerElement.data = toVector3(positionData); |
||||||
|
layerElement.dataIndices = null; |
||||||
|
return layerElement; |
||||||
|
} |
||||||
|
|
||||||
|
public static FbxLayerElement fromElement(FbxElement element) { |
||||||
|
FbxLayerElement layerElement = new FbxLayerElement(); |
||||||
|
if (!element.id.startsWith("LayerElement")) { |
||||||
|
throw new IllegalArgumentException("Not a layer element"); |
||||||
|
} |
||||||
|
layerElement.index = (Integer)element.properties.get(0); |
||||||
|
|
||||||
|
String elementType = element.id.substring("LayerElement".length()); |
||||||
|
try { |
||||||
|
layerElement.type = Type.valueOf(elementType); |
||||||
|
} catch (IllegalArgumentException ex) { |
||||||
|
logger.log(Level.WARNING, "Unsupported layer element: {0}. Ignoring.", elementType); |
||||||
|
} |
||||||
|
for (FbxElement child : element.children) { |
||||||
|
if (child.id.equals("MappingInformationType")) { |
||||||
|
String mapInfoTypeVal = (String) child.properties.get(0); |
||||||
|
if (mapInfoTypeVal.equals("ByVertice")) { |
||||||
|
mapInfoTypeVal = "ByVertex"; |
||||||
|
} |
||||||
|
layerElement.mapInfoType = MappingInformationType.valueOf(mapInfoTypeVal); |
||||||
|
} else if (child.id.equals("ReferenceInformationType")) { |
||||||
|
String refInfoTypeVal = (String) child.properties.get(0); |
||||||
|
if (refInfoTypeVal.equals("Index")) { |
||||||
|
refInfoTypeVal = "IndexToDirect"; |
||||||
|
} |
||||||
|
layerElement.refInfoType = ReferenceInformationType.valueOf(refInfoTypeVal); |
||||||
|
} else if (child.id.equals("Normals") || child.id.equals("Tangents") || child.id.equals("Binormals")) { |
||||||
|
layerElement.data = toVector3(FbxMeshUtil.getDoubleArray(child)); |
||||||
|
} else if (child.id.equals("Colors")) { |
||||||
|
layerElement.data = toColorRGBA(FbxMeshUtil.getDoubleArray(child)); |
||||||
|
} else if (child.id.equals("UV")) { |
||||||
|
layerElement.data = toVector2(FbxMeshUtil.getDoubleArray(child)); |
||||||
|
} else if (indexTypes.contains(child.id)) { |
||||||
|
layerElement.dataIndices = FbxMeshUtil.getIntArray(child); |
||||||
|
} else if (child.id.equals("Name")) { |
||||||
|
layerElement.name = (String) child.properties.get(0); |
||||||
|
} |
||||||
|
} |
||||||
|
if (layerElement.data == null) { |
||||||
|
// For Smoothing / Materials, data = dataIndices
|
||||||
|
layerElement.refInfoType = ReferenceInformationType.Direct; |
||||||
|
layerElement.data = new Integer[layerElement.dataIndices.length]; |
||||||
|
for (int i = 0; i < layerElement.data.length; i++) { |
||||||
|
layerElement.data[i] = layerElement.dataIndices[i]; |
||||||
|
} |
||||||
|
layerElement.dataIndices = null; |
||||||
|
} |
||||||
|
return layerElement; |
||||||
|
} |
||||||
|
|
||||||
|
static Vector3f[] toVector3(double[] data) { |
||||||
|
Vector3f[] vectors = new Vector3f[data.length / 3]; |
||||||
|
for (int i = 0; i < vectors.length; i++) { |
||||||
|
float x = (float) data[i * 3]; |
||||||
|
float y = (float) data[i * 3 + 1]; |
||||||
|
float z = (float) data[i * 3 + 2]; |
||||||
|
vectors[i] = new Vector3f(x, y, z); |
||||||
|
} |
||||||
|
return vectors; |
||||||
|
} |
||||||
|
|
||||||
|
static Vector2f[] toVector2(double[] data) { |
||||||
|
Vector2f[] vectors = new Vector2f[data.length / 2]; |
||||||
|
for (int i = 0; i < vectors.length; i++) { |
||||||
|
float x = (float) data[i * 2]; |
||||||
|
float y = (float) data[i * 2 + 1]; |
||||||
|
vectors[i] = new Vector2f(x, y); |
||||||
|
} |
||||||
|
return vectors; |
||||||
|
} |
||||||
|
|
||||||
|
static ColorRGBA[] toColorRGBA(double[] data) { |
||||||
|
ColorRGBA[] colors = new ColorRGBA[data.length / 4]; |
||||||
|
for (int i = 0; i < colors.length; i++) { |
||||||
|
float r = (float) data[i * 4]; |
||||||
|
float g = (float) data[i * 4 + 1]; |
||||||
|
float b = (float) data[i * 4 + 2]; |
||||||
|
float a = (float) data[i * 4 + 3]; |
||||||
|
colors[i] = new ColorRGBA(r, g, b, a); |
||||||
|
} |
||||||
|
return colors; |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,316 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.mesh; |
||||||
|
|
||||||
|
import com.jme3.animation.Bone; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.math.Vector2f; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.plugins.IrUtils; |
||||||
|
import com.jme3.scene.plugins.IrBoneWeightIndex; |
||||||
|
import com.jme3.scene.plugins.IrMesh; |
||||||
|
import com.jme3.scene.plugins.IrPolygon; |
||||||
|
import com.jme3.scene.plugins.IrVertex; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxCluster; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import com.jme3.scene.plugins.fbx.node.FbxNodeAttribute; |
||||||
|
import com.jme3.util.IntMap; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxMesh.class.getName()); |
||||||
|
|
||||||
|
private FbxPolygon[] polygons; |
||||||
|
private int[] edges; |
||||||
|
private FbxLayerElement[] layerElements; |
||||||
|
private Vector3f[] positions; |
||||||
|
private FbxLayer[] layers; |
||||||
|
|
||||||
|
private ArrayList<Integer>[] boneIndices; |
||||||
|
private ArrayList<Float>[] boneWeights; |
||||||
|
|
||||||
|
private FbxSkinDeformer skinDeformer; |
||||||
|
|
||||||
|
public FbxMesh(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
|
||||||
|
List<FbxLayerElement> layerElementsList = new ArrayList<FbxLayerElement>(); |
||||||
|
List<FbxLayer> layersList = new ArrayList<FbxLayer>(); |
||||||
|
|
||||||
|
for (FbxElement e : element.children) { |
||||||
|
if (e.id.equals("Vertices")) { |
||||||
|
setPositions(FbxMeshUtil.getDoubleArray(e)); |
||||||
|
} else if (e.id.equals("PolygonVertexIndex")) { |
||||||
|
setPolygonVertexIndices(FbxMeshUtil.getIntArray(e)); |
||||||
|
} else if (e.id.equals("Edges")) { |
||||||
|
setEdges(FbxMeshUtil.getIntArray(e)); |
||||||
|
} else if (e.id.startsWith("LayerElement")) { |
||||||
|
layerElementsList.add(FbxLayerElement.fromElement(e)); |
||||||
|
} else if (e.id.equals("Layer")) { |
||||||
|
layersList.add(FbxLayer.fromElement(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (FbxLayer layer : layersList) { |
||||||
|
layer.setLayerElements(layerElementsList); |
||||||
|
} |
||||||
|
|
||||||
|
layerElements = new FbxLayerElement[layerElementsList.size()]; |
||||||
|
layerElementsList.toArray(layerElements); |
||||||
|
|
||||||
|
layers = new FbxLayer[layersList.size()]; |
||||||
|
layersList.toArray(layers); |
||||||
|
} |
||||||
|
|
||||||
|
public FbxSkinDeformer getSkinDeformer() { |
||||||
|
return skinDeformer; |
||||||
|
} |
||||||
|
|
||||||
|
public void applyCluster(FbxCluster cluster) { |
||||||
|
if (boneIndices == null) { |
||||||
|
boneIndices = new ArrayList[positions.length]; |
||||||
|
boneWeights = new ArrayList[positions.length]; |
||||||
|
} |
||||||
|
|
||||||
|
FbxLimbNode limb = cluster.getLimb(); |
||||||
|
Bone bone = limb.getJmeBone(); |
||||||
|
Skeleton skeleton = limb.getSkeletonHolder().getJmeSkeleton(); |
||||||
|
int boneIndex = skeleton.getBoneIndex(bone); |
||||||
|
|
||||||
|
int[] positionIndices = cluster.getVertexIndices(); |
||||||
|
double[] weights = cluster.getWeights(); |
||||||
|
|
||||||
|
for (int i = 0; i < positionIndices.length; i++) { |
||||||
|
int positionIndex = positionIndices[i]; |
||||||
|
float boneWeight = (float)weights[i]; |
||||||
|
|
||||||
|
ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex]; |
||||||
|
ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex]; |
||||||
|
|
||||||
|
if (boneIndicesForVertex == null) { |
||||||
|
boneIndicesForVertex = new ArrayList<Integer>(); |
||||||
|
boneWeightsForVertex = new ArrayList<Float>(); |
||||||
|
boneIndices[positionIndex] = boneIndicesForVertex; |
||||||
|
boneWeights[positionIndex] = boneWeightsForVertex; |
||||||
|
} |
||||||
|
|
||||||
|
boneIndicesForVertex.add(boneIndex); |
||||||
|
boneWeightsForVertex.add(boneWeight); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
if (object instanceof FbxSkinDeformer) { |
||||||
|
if (skinDeformer != null) { |
||||||
|
logger.log(Level.WARNING, "This mesh already has a skin deformer attached. Ignoring."); |
||||||
|
return; |
||||||
|
} |
||||||
|
skinDeformer = (FbxSkinDeformer) object; |
||||||
|
} else { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
|
||||||
|
private void setPositions(double[] positions) { |
||||||
|
this.positions = FbxLayerElement.toVector3(positions); |
||||||
|
} |
||||||
|
|
||||||
|
private void setEdges(int[] edges) { |
||||||
|
this.edges = edges; |
||||||
|
// TODO: ...
|
||||||
|
} |
||||||
|
|
||||||
|
private void setPolygonVertexIndices(int[] polygonVertexIndices) { |
||||||
|
List<FbxPolygon> polygonList = new ArrayList<FbxPolygon>(); |
||||||
|
|
||||||
|
boolean finishPolygon = false; |
||||||
|
List<Integer> vertexIndices = new ArrayList<Integer>(); |
||||||
|
|
||||||
|
for (int i = 0; i < polygonVertexIndices.length; i++) { |
||||||
|
int vertexIndex = polygonVertexIndices[i]; |
||||||
|
|
||||||
|
if (vertexIndex < 0) { |
||||||
|
vertexIndex ^= -1; |
||||||
|
finishPolygon = true; |
||||||
|
} |
||||||
|
|
||||||
|
vertexIndices.add(vertexIndex); |
||||||
|
|
||||||
|
if (finishPolygon) { |
||||||
|
finishPolygon = false; |
||||||
|
polygonList.add(FbxPolygon.fromIndices(vertexIndices)); |
||||||
|
vertexIndices.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
polygons = new FbxPolygon[polygonList.size()]; |
||||||
|
polygonList.toArray(polygons); |
||||||
|
} |
||||||
|
|
||||||
|
private static IrBoneWeightIndex[] toBoneWeightIndices(List<Integer> boneIndices, List<Float> boneWeights) { |
||||||
|
IrBoneWeightIndex[] boneWeightIndices = new IrBoneWeightIndex[boneIndices.size()]; |
||||||
|
for (int i = 0; i < boneIndices.size(); i++) { |
||||||
|
boneWeightIndices[i] = new IrBoneWeightIndex(boneIndices.get(i), boneWeights.get(i)); |
||||||
|
} |
||||||
|
return boneWeightIndices; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected IntMap<Mesh> toJmeObject() { |
||||||
|
// Load clusters from SkinDeformer
|
||||||
|
if (skinDeformer != null) { |
||||||
|
for (FbxCluster cluster : skinDeformer.getJmeObject()) { |
||||||
|
applyCluster(cluster); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
IrMesh irMesh = toIRMesh(); |
||||||
|
|
||||||
|
// Trim bone weights to 4 weights per vertex.
|
||||||
|
IrUtils.trimBoneWeights(irMesh); |
||||||
|
|
||||||
|
// Convert tangents / binormals to tangents with parity.
|
||||||
|
IrUtils.toTangentsWithParity(irMesh); |
||||||
|
|
||||||
|
// Triangulate quads.
|
||||||
|
IrUtils.triangulate(irMesh); |
||||||
|
|
||||||
|
// Split meshes by material indices.
|
||||||
|
IntMap<IrMesh> irMeshes = IrUtils.splitByMaterial(irMesh); |
||||||
|
|
||||||
|
// Create a jME3 Mesh for each material index.
|
||||||
|
IntMap<Mesh> jmeMeshes = new IntMap<Mesh>(); |
||||||
|
for (IntMap.Entry<IrMesh> irMeshEntry : irMeshes) { |
||||||
|
Mesh jmeMesh = IrUtils.convertIrMeshToJmeMesh(irMeshEntry.getValue()); |
||||||
|
jmeMeshes.put(irMeshEntry.getKey(), jmeMesh); |
||||||
|
} |
||||||
|
|
||||||
|
if (jmeMeshes.size() == 0) { |
||||||
|
// When will this actually happen? Not sure.
|
||||||
|
logger.log(Level.WARNING, "Empty FBX mesh found (unusual)."); |
||||||
|
} |
||||||
|
|
||||||
|
// IMPORTANT: If we have a -1 entry, those are triangles
|
||||||
|
// with no material indices.
|
||||||
|
// It makes sense only if the mesh uses a single material!
|
||||||
|
if (jmeMeshes.containsKey(-1) && jmeMeshes.size() > 1) { |
||||||
|
logger.log(Level.WARNING, "Mesh has polygons with no material " |
||||||
|
+ "indices (unusual) - they will use material index 0."); |
||||||
|
} |
||||||
|
|
||||||
|
return jmeMeshes; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Convert FBXMesh to IRMesh. |
||||||
|
*/ |
||||||
|
public IrMesh toIRMesh() { |
||||||
|
IrMesh newMesh = new IrMesh(); |
||||||
|
newMesh.polygons = new IrPolygon[polygons.length]; |
||||||
|
|
||||||
|
int polygonVertexIndex = 0; |
||||||
|
int positionIndex = 0; |
||||||
|
|
||||||
|
FbxLayer layer0 = layers[0]; |
||||||
|
FbxLayer layer1 = layers.length > 1 ? layers[1] : null; |
||||||
|
|
||||||
|
for (int i = 0; i < polygons.length; i++) { |
||||||
|
FbxPolygon polygon = polygons[i]; |
||||||
|
IrPolygon irPolygon = new IrPolygon(); |
||||||
|
irPolygon.vertices = new IrVertex[polygon.indices.length]; |
||||||
|
|
||||||
|
for (int j = 0; j < polygon.indices.length; j++) { |
||||||
|
positionIndex = polygon.indices[j]; |
||||||
|
|
||||||
|
IrVertex irVertex = new IrVertex(); |
||||||
|
irVertex.pos = positions[positionIndex]; |
||||||
|
|
||||||
|
if (layer0 != null) { |
||||||
|
irVertex.norm = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Normal, i, polygonVertexIndex, positionIndex, 0); |
||||||
|
irVertex.tang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Tangent, i, polygonVertexIndex, positionIndex, 0); |
||||||
|
irVertex.bitang = (Vector3f) layer0.getVertexData(FbxLayerElement.Type.Binormal, i, polygonVertexIndex, positionIndex, 0); |
||||||
|
irVertex.uv0 = (Vector2f) layer0.getVertexData(FbxLayerElement.Type.UV, i, polygonVertexIndex, positionIndex, 0); |
||||||
|
irVertex.color = (ColorRGBA) layer0.getVertexData(FbxLayerElement.Type.Color, i, polygonVertexIndex, positionIndex, 0); |
||||||
|
irVertex.material = (Integer) layer0.getVertexData(FbxLayerElement.Type.Material, i, polygonVertexIndex, positionIndex, 0); |
||||||
|
irVertex.smoothing = (Integer) layer0.getVertexData(FbxLayerElement.Type.Smoothing, i, polygonVertexIndex, positionIndex, 0); |
||||||
|
} |
||||||
|
|
||||||
|
if (layer1 != null) { |
||||||
|
irVertex.uv1 = (Vector2f) layer1.getVertexData(FbxLayerElement.Type.UV, i, |
||||||
|
polygonVertexIndex, positionIndex, 0); |
||||||
|
} |
||||||
|
|
||||||
|
if (boneIndices != null) { |
||||||
|
ArrayList<Integer> boneIndicesForVertex = boneIndices[positionIndex]; |
||||||
|
ArrayList<Float> boneWeightsForVertex = boneWeights[positionIndex]; |
||||||
|
if (boneIndicesForVertex != null) { |
||||||
|
irVertex.boneWeightsIndices = toBoneWeightIndices(boneIndicesForVertex, boneWeightsForVertex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
irPolygon.vertices[j] = irVertex; |
||||||
|
|
||||||
|
polygonVertexIndex++; |
||||||
|
} |
||||||
|
|
||||||
|
newMesh.polygons[i] = irPolygon; |
||||||
|
} |
||||||
|
|
||||||
|
// Ensure "inspection vertex" specifies that mesh has bone indices / weights
|
||||||
|
if (boneIndices != null && newMesh.polygons[0].vertices[0] == null) { |
||||||
|
newMesh.polygons[0].vertices[0].boneWeightsIndices = new IrBoneWeightIndex[0]; |
||||||
|
} |
||||||
|
|
||||||
|
return newMesh; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.mesh; |
||||||
|
|
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
|
||||||
|
public class FbxMeshUtil { |
||||||
|
|
||||||
|
public static double[] getDoubleArray(FbxElement el) { |
||||||
|
if (el.propertiesTypes[0] == 'd') { |
||||||
|
// FBX 7.x
|
||||||
|
return (double[]) el.properties.get(0); |
||||||
|
} else if (el.propertiesTypes[0] == 'D') { |
||||||
|
// FBX 6.x
|
||||||
|
double[] doubles = new double[el.propertiesTypes.length]; |
||||||
|
for (int i = 0; i < doubles.length; i++) { |
||||||
|
doubles[i] = (Double) el.properties.get(i); |
||||||
|
} |
||||||
|
return doubles; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static int[] getIntArray(FbxElement el) { |
||||||
|
if (el.propertiesTypes[0] == 'i') { |
||||||
|
// FBX 7.x
|
||||||
|
return (int[]) el.properties.get(0); |
||||||
|
} else if (el.propertiesTypes[0] == 'I') { |
||||||
|
// FBX 6.x
|
||||||
|
int[] ints = new int[el.propertiesTypes.length]; |
||||||
|
for (int i = 0; i < ints.length; i++) { |
||||||
|
ints[i] = (Integer) el.properties.get(i); |
||||||
|
} |
||||||
|
return ints; |
||||||
|
} else { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,145 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.misc; |
||||||
|
|
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.logging.Level; |
||||||
|
import java.util.logging.Logger; |
||||||
|
|
||||||
|
public class FbxGlobalSettings { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxGlobalSettings.class.getName()); |
||||||
|
|
||||||
|
private static final Map<Integer, Float> timeModeToFps = new HashMap<Integer, Float>(); |
||||||
|
|
||||||
|
static { |
||||||
|
timeModeToFps.put(1, 120f); |
||||||
|
timeModeToFps.put(2, 100f); |
||||||
|
timeModeToFps.put(3, 60f); |
||||||
|
timeModeToFps.put(4, 50f); |
||||||
|
timeModeToFps.put(5, 48f); |
||||||
|
timeModeToFps.put(6, 30f); |
||||||
|
timeModeToFps.put(9, 30f / 1.001f); |
||||||
|
timeModeToFps.put(10, 25f); |
||||||
|
timeModeToFps.put(11, 24f); |
||||||
|
timeModeToFps.put(13, 24f / 1.001f); |
||||||
|
timeModeToFps.put(14, -1f); |
||||||
|
timeModeToFps.put(15, 96f); |
||||||
|
timeModeToFps.put(16, 72f); |
||||||
|
timeModeToFps.put(17, 60f / 1.001f); |
||||||
|
} |
||||||
|
|
||||||
|
public float unitScaleFactor = 1.0f; |
||||||
|
public ColorRGBA ambientColor = ColorRGBA.Black; |
||||||
|
public float frameRate = 25.0f; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return A {@link Transform} that converts from the FBX file coordinate |
||||||
|
* system to jME3 coordinate system. |
||||||
|
* jME3's coordinate system is: |
||||||
|
* <ul> |
||||||
|
* <li>Units are specified in meters.</li> |
||||||
|
* <li>Orientation is right-handed with Y-up.</li> |
||||||
|
* </ul> |
||||||
|
*/ |
||||||
|
public Transform getGlobalTransform() { |
||||||
|
// Default unit scale factor is 1 (centimeters),
|
||||||
|
// convert to meters.
|
||||||
|
float scale = unitScaleFactor / 100.0f; |
||||||
|
|
||||||
|
// TODO: handle rotation
|
||||||
|
|
||||||
|
return new Transform(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(scale, scale, scale)); |
||||||
|
} |
||||||
|
|
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
// jME3 uses a +Y up, -Z forward coordinate system (same as OpenGL)
|
||||||
|
// Luckily enough, this is also the default for FBX models.
|
||||||
|
|
||||||
|
int timeMode = -1; |
||||||
|
float customFrameRate = 30.0f; |
||||||
|
|
||||||
|
for (FbxElement e2 : element.getFbxProperties()) { |
||||||
|
String propName = (String) e2.properties.get(0); |
||||||
|
if (propName.equals("UnitScaleFactor")) { |
||||||
|
unitScaleFactor = ((Double) e2.properties.get(4)).floatValue(); |
||||||
|
if (unitScaleFactor != 100.0f) { |
||||||
|
logger.log(Level.WARNING, "FBX model isn't using meters for world units. Scale could be incorrect."); |
||||||
|
} |
||||||
|
} else if (propName.equals("TimeMode")) { |
||||||
|
timeMode = (Integer) e2.properties.get(4); |
||||||
|
} else if (propName.equals("CustomFrameRate")) { |
||||||
|
float framerate = ((Double) e2.properties.get(4)).floatValue(); |
||||||
|
if (framerate != -1) { |
||||||
|
customFrameRate = framerate; |
||||||
|
} |
||||||
|
} else if (propName.equals("UpAxis")) { |
||||||
|
Integer upAxis = (Integer) e2.properties.get(4); |
||||||
|
if (upAxis != 1) { |
||||||
|
logger.log(Level.WARNING, "FBX model isn't using Y as up axis. Orientation could be incorrect"); |
||||||
|
} |
||||||
|
} else if (propName.equals("UpAxisSign")) { |
||||||
|
Integer upAxisSign = (Integer) e2.properties.get(4); |
||||||
|
if (upAxisSign != 1) { |
||||||
|
logger.log(Level.WARNING, "FBX model isn't using correct up axis sign. Orientation could be incorrect"); |
||||||
|
} |
||||||
|
} else if (propName.equals("FrontAxis")) { |
||||||
|
Integer frontAxis = (Integer) e2.properties.get(4); |
||||||
|
if (frontAxis != 2) { |
||||||
|
logger.log(Level.WARNING, "FBX model isn't using Z as forward axis. Orientation could be incorrect"); |
||||||
|
} |
||||||
|
} else if (propName.equals("FrontAxisSign")) { |
||||||
|
Integer frontAxisSign = (Integer) e2.properties.get(4); |
||||||
|
if (frontAxisSign != -1) { |
||||||
|
logger.log(Level.WARNING, "FBX model isn't using correct forward axis sign. Orientation could be incorrect"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Float fps = timeModeToFps.get(timeMode); |
||||||
|
if (fps != null) { |
||||||
|
if (fps == -1f) { |
||||||
|
// Using custom framerate
|
||||||
|
frameRate = customFrameRate; |
||||||
|
} else { |
||||||
|
// Use FPS from time mode.
|
||||||
|
frameRate = fps; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,617 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.node; |
||||||
|
|
||||||
|
import com.jme3.animation.AnimControl; |
||||||
|
import com.jme3.animation.Skeleton; |
||||||
|
import com.jme3.animation.SkeletonControl; |
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.material.Material; |
||||||
|
import com.jme3.math.ColorRGBA; |
||||||
|
import com.jme3.math.FastMath; |
||||||
|
import com.jme3.math.Matrix4f; |
||||||
|
import com.jme3.math.Quaternion; |
||||||
|
import com.jme3.math.Transform; |
||||||
|
import com.jme3.math.Vector3f; |
||||||
|
import com.jme3.renderer.queue.RenderQueue.Bucket; |
||||||
|
import com.jme3.renderer.queue.RenderQueue.ShadowMode; |
||||||
|
import com.jme3.scene.Geometry; |
||||||
|
import com.jme3.scene.Mesh; |
||||||
|
import com.jme3.scene.Node; |
||||||
|
import com.jme3.scene.Spatial; |
||||||
|
import com.jme3.scene.Spatial.CullHint; |
||||||
|
import com.jme3.scene.debug.SkeletonDebugger; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxAnimCurveNode; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxCluster; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; |
||||||
|
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer; |
||||||
|
import com.jme3.scene.plugins.fbx.file.FbxElement; |
||||||
|
import com.jme3.scene.plugins.fbx.material.FbxImage; |
||||||
|
import com.jme3.scene.plugins.fbx.material.FbxMaterial; |
||||||
|
import com.jme3.scene.plugins.fbx.material.FbxTexture; |
||||||
|
import com.jme3.scene.plugins.fbx.mesh.FbxMesh; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
import com.jme3.util.IntMap; |
||||||
|
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; |
||||||
|
|
||||||
|
public class FbxNode extends FbxObject<Spatial> { |
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(FbxNode.class.getName()); |
||||||
|
|
||||||
|
private static enum InheritMode { |
||||||
|
/** |
||||||
|
* Apply parent scale after child rotation. |
||||||
|
* This is the only mode correctly supported by jME3. |
||||||
|
*/ |
||||||
|
ScaleAfterChildRotation, |
||||||
|
|
||||||
|
/** |
||||||
|
* Apply parent scale before child rotation. |
||||||
|
* Not supported by jME3, will cause distortion with |
||||||
|
* non-uniform scale. No way around it. |
||||||
|
*/ |
||||||
|
ScaleBeforeChildRotation, |
||||||
|
|
||||||
|
/** |
||||||
|
* Do not apply parent scale at all. |
||||||
|
* Not supported by jME3, will cause distortion. |
||||||
|
* Could be worked around by via: |
||||||
|
* <code>jmeChildScale = jmeParentScale / fbxChildScale</code> |
||||||
|
*/ |
||||||
|
NoParentScale |
||||||
|
} |
||||||
|
|
||||||
|
private InheritMode inheritMode = InheritMode.ScaleAfterChildRotation; |
||||||
|
|
||||||
|
protected FbxNode parent; |
||||||
|
protected List<FbxNode> children = new ArrayList<FbxNode>(); |
||||||
|
protected List<FbxMaterial> materials = new ArrayList<FbxMaterial>(); |
||||||
|
protected Map<String, Object> userData = new HashMap<String, Object>(); |
||||||
|
protected Map<String, List<FbxAnimCurveNode>> propertyToAnimCurveMap = new HashMap<String, List<FbxAnimCurveNode>>(); |
||||||
|
protected FbxNodeAttribute nodeAttribute; |
||||||
|
protected double visibility = 1.0; |
||||||
|
|
||||||
|
/** |
||||||
|
* For FBX nodes that contain a skeleton (i.e. FBX limbs). |
||||||
|
*/ |
||||||
|
protected Skeleton skeleton; |
||||||
|
|
||||||
|
protected final Transform jmeWorldNodeTransform = new Transform(); |
||||||
|
protected final Transform jmeLocalNodeTransform = new Transform(); |
||||||
|
|
||||||
|
// optional - used for limbs / bones / skeletons
|
||||||
|
protected Transform jmeWorldBindPose; |
||||||
|
protected Transform jmeLocalBindPose; |
||||||
|
|
||||||
|
// used for debugging only
|
||||||
|
protected Matrix4f cachedWorldBindPose; |
||||||
|
|
||||||
|
public FbxNode(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
|
||||||
|
public Transform computeFbxLocalTransform() { |
||||||
|
// TODO: implement the actual algorithm, which is this:
|
||||||
|
// Render Local Translation =
|
||||||
|
// Inv Scale Pivot * Lcl Scale * Scale Pivot * Scale Offset * Inv Rota Pivot * Post Rotation * Rotation * Pre Rotation * Rotation Pivot * Rotation Offset * Translation
|
||||||
|
|
||||||
|
// LclTranslation,
|
||||||
|
// LclRotation,
|
||||||
|
// PreRotation,
|
||||||
|
// PostRotation,
|
||||||
|
// RotationPivot,
|
||||||
|
// RotationOffset,
|
||||||
|
// LclScaling,
|
||||||
|
// ScalingPivot,
|
||||||
|
// ScalingOffset
|
||||||
|
|
||||||
|
Matrix4f scaleMat = new Matrix4f(); |
||||||
|
scaleMat.setScale(jmeLocalNodeTransform.getScale()); |
||||||
|
|
||||||
|
Matrix4f rotationMat = new Matrix4f(); |
||||||
|
rotationMat.setRotationQuaternion(jmeLocalNodeTransform.getRotation()); |
||||||
|
|
||||||
|
Matrix4f translationMat = new Matrix4f(); |
||||||
|
translationMat.setTranslation(jmeLocalNodeTransform.getTranslation()); |
||||||
|
|
||||||
|
Matrix4f result = new Matrix4f(); |
||||||
|
result.multLocal(scaleMat).multLocal(rotationMat).multLocal(translationMat); |
||||||
|
|
||||||
|
Transform t = new Transform(); |
||||||
|
t.fromTransformMatrix(result); |
||||||
|
|
||||||
|
return t; |
||||||
|
} |
||||||
|
|
||||||
|
public void setWorldBindPose(Matrix4f worldBindPose) { |
||||||
|
if (cachedWorldBindPose != null) { |
||||||
|
if (!cachedWorldBindPose.equals(worldBindPose)) { |
||||||
|
throw new UnsupportedOperationException("Bind poses don't match"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
cachedWorldBindPose = worldBindPose; |
||||||
|
|
||||||
|
this.jmeWorldBindPose = new Transform(); |
||||||
|
this.jmeWorldBindPose.setTranslation(worldBindPose.toTranslationVector()); |
||||||
|
this.jmeWorldBindPose.setRotation(worldBindPose.toRotationQuat()); |
||||||
|
this.jmeWorldBindPose.setScale(worldBindPose.toScaleVector()); |
||||||
|
|
||||||
|
System.out.println("\tBind Pose for " + getName()); |
||||||
|
System.out.println(jmeWorldBindPose); |
||||||
|
|
||||||
|
float[] angles = new float[3]; |
||||||
|
jmeWorldBindPose.getRotation().toAngles(angles); |
||||||
|
System.out.println("Angles: " + angles[0] * FastMath.RAD_TO_DEG + ", " + |
||||||
|
angles[1] * FastMath.RAD_TO_DEG + ", " + |
||||||
|
angles[2] * FastMath.RAD_TO_DEG); |
||||||
|
} |
||||||
|
|
||||||
|
public void updateWorldTransforms(Transform jmeParentNodeTransform, Transform parentBindPose) { |
||||||
|
Transform fbxLocalTransform = computeFbxLocalTransform(); |
||||||
|
jmeLocalNodeTransform.set(fbxLocalTransform); |
||||||
|
|
||||||
|
if (jmeParentNodeTransform != null) { |
||||||
|
jmeParentNodeTransform = jmeParentNodeTransform.clone(); |
||||||
|
switch (inheritMode) { |
||||||
|
case NoParentScale: |
||||||
|
case ScaleAfterChildRotation: |
||||||
|
case ScaleBeforeChildRotation: |
||||||
|
jmeWorldNodeTransform.set(jmeLocalNodeTransform); |
||||||
|
jmeWorldNodeTransform.combineWithParent(jmeParentNodeTransform); |
||||||
|
break; |
||||||
|
} |
||||||
|
} else { |
||||||
|
jmeWorldNodeTransform.set(jmeLocalNodeTransform); |
||||||
|
} |
||||||
|
|
||||||
|
if (jmeWorldBindPose != null) { |
||||||
|
jmeLocalBindPose = new Transform(); |
||||||
|
|
||||||
|
// Need to derive local bind pose from world bind pose
|
||||||
|
// (this is to be expected for FBX limbs)
|
||||||
|
jmeLocalBindPose.set(jmeWorldBindPose); |
||||||
|
jmeLocalBindPose.combineWithParent(parentBindPose.invert()); |
||||||
|
|
||||||
|
// Its somewhat odd for the transforms to differ ...
|
||||||
|
System.out.println("Bind Pose for: " + getName()); |
||||||
|
if (!jmeLocalBindPose.equals(jmeLocalNodeTransform)) { |
||||||
|
System.out.println("Local Bind: " + jmeLocalBindPose); |
||||||
|
System.out.println("Local Trans: " + jmeLocalNodeTransform); |
||||||
|
} |
||||||
|
if (!jmeWorldBindPose.equals(jmeWorldNodeTransform)) { |
||||||
|
System.out.println("World Bind: " + jmeWorldBindPose); |
||||||
|
System.out.println("World Trans: " + jmeWorldNodeTransform); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// World pose derived from local transforms
|
||||||
|
// (this is to be expected for FBX nodes)
|
||||||
|
jmeLocalBindPose = new Transform(); |
||||||
|
jmeWorldBindPose = new Transform(); |
||||||
|
|
||||||
|
jmeLocalBindPose.set(jmeLocalNodeTransform); |
||||||
|
if (parentBindPose != null) { |
||||||
|
jmeWorldBindPose.set(jmeLocalNodeTransform); |
||||||
|
jmeWorldBindPose.combineWithParent(parentBindPose); |
||||||
|
} else { |
||||||
|
jmeWorldBindPose.set(jmeWorldNodeTransform); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (FbxNode child : children) { |
||||||
|
child.updateWorldTransforms(jmeWorldNodeTransform, jmeWorldBindPose); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void fromElement(FbxElement element) { |
||||||
|
super.fromElement(element); |
||||||
|
|
||||||
|
Vector3f localTranslation = new Vector3f(); |
||||||
|
Quaternion localRotation = new Quaternion(); |
||||||
|
Vector3f localScale = new Vector3f(Vector3f.UNIT_XYZ); |
||||||
|
Quaternion preRotation = new Quaternion(); |
||||||
|
|
||||||
|
for (FbxElement e2 : element.getFbxProperties()) { |
||||||
|
String propName = (String) e2.properties.get(0); |
||||||
|
String type = (String) e2.properties.get(3); |
||||||
|
if (propName.equals("Lcl Translation")) { |
||||||
|
double x = (Double) e2.properties.get(4); |
||||||
|
double y = (Double) e2.properties.get(5); |
||||||
|
double z = (Double) e2.properties.get(6); |
||||||
|
localTranslation.set((float) x, (float) y, (float) z); //.divideLocal(unitSize);
|
||||||
|
} else if (propName.equals("Lcl Rotation")) { |
||||||
|
double x = (Double) e2.properties.get(4); |
||||||
|
double y = (Double) e2.properties.get(5); |
||||||
|
double z = (Double) e2.properties.get(6); |
||||||
|
localRotation.fromAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD); |
||||||
|
} else if (propName.equals("Lcl Scaling")) { |
||||||
|
double x = (Double) e2.properties.get(4); |
||||||
|
double y = (Double) e2.properties.get(5); |
||||||
|
double z = (Double) e2.properties.get(6); |
||||||
|
localScale.set((float) x, (float) y, (float) z); //.multLocal(unitSize);
|
||||||
|
} else if (propName.equals("PreRotation")) { |
||||||
|
double x = (Double) e2.properties.get(4); |
||||||
|
double y = (Double) e2.properties.get(5); |
||||||
|
double z = (Double) e2.properties.get(6); |
||||||
|
preRotation.set(FbxNodeUtil.quatFromBoneAngles((float) x * FastMath.DEG_TO_RAD, (float) y * FastMath.DEG_TO_RAD, (float) z * FastMath.DEG_TO_RAD)); |
||||||
|
} else if (propName.equals("InheritType")) { |
||||||
|
int inheritType = (Integer) e2.properties.get(4); |
||||||
|
inheritMode = InheritMode.values()[inheritType]; |
||||||
|
} else if (propName.equals("Visibility")) { |
||||||
|
visibility = (Double) e2.properties.get(4); |
||||||
|
} else if (type.contains("U")) { |
||||||
|
String userDataKey = (String) e2.properties.get(0); |
||||||
|
String userDataType = (String) e2.properties.get(1); |
||||||
|
Object userDataValue; |
||||||
|
|
||||||
|
if (userDataType.equals("KString")) { |
||||||
|
userDataValue = (String) e2.properties.get(4); |
||||||
|
} else if (userDataType.equals("int")) { |
||||||
|
userDataValue = (Integer) e2.properties.get(4); |
||||||
|
} else if (userDataType.equals("double")) { |
||||||
|
// NOTE: jME3 does not support doubles in UserData.
|
||||||
|
// Need to convert to float.
|
||||||
|
userDataValue = ((Double) e2.properties.get(4)).floatValue(); |
||||||
|
} else if (userDataType.equals("Vector")) { |
||||||
|
float x = ((Double) e2.properties.get(4)).floatValue(); |
||||||
|
float y = ((Double) e2.properties.get(5)).floatValue(); |
||||||
|
float z = ((Double) e2.properties.get(6)).floatValue(); |
||||||
|
userDataValue = new Vector3f(x, y, z); |
||||||
|
} else { |
||||||
|
logger.log(Level.WARNING, "Unsupported user data type: {0}. Ignoring.", userDataType); |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
userData.put(userDataKey, userDataValue); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Create local transform
|
||||||
|
// TODO: take into account Maya-style transforms (pre / post rotation ..)
|
||||||
|
jmeLocalNodeTransform.setTranslation(localTranslation); |
||||||
|
jmeLocalNodeTransform.setRotation(localRotation); |
||||||
|
jmeLocalNodeTransform.setScale(localScale); |
||||||
|
|
||||||
|
if (element.getChildById("Vertices") != null) { |
||||||
|
// This is an old-style FBX 6.1
|
||||||
|
// Meshes could be embedded inside the node..
|
||||||
|
|
||||||
|
// Inject the mesh into ourselves..
|
||||||
|
FbxMesh mesh = new FbxMesh(assetManager, sceneFolderName); |
||||||
|
mesh.fromElement(element); |
||||||
|
connectObject(mesh); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Spatial tryCreateGeometry(int materialIndex, Mesh jmeMesh, boolean single) { |
||||||
|
// Map meshes without material indices to material 0.
|
||||||
|
if (materialIndex == -1) { |
||||||
|
materialIndex = 0; |
||||||
|
} |
||||||
|
|
||||||
|
Material jmeMat; |
||||||
|
if (materialIndex >= materials.size()) { |
||||||
|
// Material index does not exist. Create default material.
|
||||||
|
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); |
||||||
|
jmeMat.setReceivesShadows(true); |
||||||
|
} else { |
||||||
|
FbxMaterial fbxMat = materials.get(materialIndex); |
||||||
|
jmeMat = fbxMat.getJmeObject(); |
||||||
|
} |
||||||
|
|
||||||
|
String geomName = getName(); |
||||||
|
if (single) { |
||||||
|
geomName += "-submesh"; |
||||||
|
} else { |
||||||
|
geomName += "-mat-" + materialIndex + "-submesh"; |
||||||
|
} |
||||||
|
Spatial spatial = new Geometry(geomName, jmeMesh); |
||||||
|
spatial.setMaterial(jmeMat); |
||||||
|
if (jmeMat.isTransparent()) { |
||||||
|
spatial.setQueueBucket(Bucket.Transparent); |
||||||
|
} |
||||||
|
if (jmeMat.isReceivesShadows()) { |
||||||
|
spatial.setShadowMode(ShadowMode.Receive); |
||||||
|
} |
||||||
|
spatial.updateModelBound(); |
||||||
|
return spatial; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* If this geometry node is deformed by a skeleton, this |
||||||
|
* returns the node containing the skeleton. |
||||||
|
* |
||||||
|
* In jME3, a mesh can be deformed by a skeleton only if it is |
||||||
|
* a child of the node containing the skeleton. However, this |
||||||
|
* is not a requirement in FBX, so we have to modify the scene graph |
||||||
|
* of the loaded model to adjust for this. |
||||||
|
* This happens automatically in |
||||||
|
* {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}. |
||||||
|
* |
||||||
|
* @return The model this node would like to be a child of, or null |
||||||
|
* if no preferred parent. |
||||||
|
*/ |
||||||
|
public FbxNode getPreferredParent() { |
||||||
|
if (!(nodeAttribute instanceof FbxMesh)) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
FbxMesh fbxMesh = (FbxMesh) nodeAttribute; |
||||||
|
FbxSkinDeformer deformer = fbxMesh.getSkinDeformer(); |
||||||
|
FbxNode preferredParent = null; |
||||||
|
|
||||||
|
if (deformer != null) { |
||||||
|
for (FbxCluster cluster : deformer.getJmeObject()) { |
||||||
|
FbxLimbNode limb = cluster.getLimb(); |
||||||
|
if (preferredParent == null) { |
||||||
|
preferredParent = limb.getSkeletonHolder(); |
||||||
|
} else if (preferredParent != limb.getSkeletonHolder()) { |
||||||
|
logger.log(Level.WARNING, "A mesh is being deformed by multiple skeletons. " |
||||||
|
+ "Only one skeleton will work, ignoring other skeletons."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return preferredParent; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Spatial toJmeObject() { |
||||||
|
Spatial spatial; |
||||||
|
|
||||||
|
if (nodeAttribute instanceof FbxMesh) { |
||||||
|
FbxMesh fbxMesh = (FbxMesh) nodeAttribute; |
||||||
|
IntMap<Mesh> jmeMeshes = fbxMesh.getJmeObject(); |
||||||
|
|
||||||
|
if (jmeMeshes == null || jmeMeshes.size() == 0) { |
||||||
|
// No meshes found on FBXMesh (??)
|
||||||
|
logger.log(Level.WARNING, "No meshes could be loaded. Creating empty node."); |
||||||
|
spatial = new Node(getName() + "-node"); |
||||||
|
} else { |
||||||
|
// Multiple jME3 geometries required for a single FBXMesh.
|
||||||
|
String nodeName; |
||||||
|
if (children.isEmpty()) { |
||||||
|
nodeName = getName() + "-mesh"; |
||||||
|
} else { |
||||||
|
nodeName = getName() + "-node"; |
||||||
|
} |
||||||
|
Node node = new Node(nodeName); |
||||||
|
boolean singleMesh = jmeMeshes.size() == 1; |
||||||
|
for (IntMap.Entry<Mesh> meshInfo : jmeMeshes) { |
||||||
|
node.attachChild(tryCreateGeometry(meshInfo.getKey(), meshInfo.getValue(), singleMesh)); |
||||||
|
} |
||||||
|
spatial = node; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (nodeAttribute != null) { |
||||||
|
// Just specifies that this is a "null" node.
|
||||||
|
nodeAttribute.getJmeObject(); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: handle other node attribute types.
|
||||||
|
// right now everything we don't know about gets converted
|
||||||
|
// to jME3 Node.
|
||||||
|
spatial = new Node(getName() + "-node"); |
||||||
|
} |
||||||
|
|
||||||
|
if (!children.isEmpty()) { |
||||||
|
// Check uniform scale.
|
||||||
|
// Although, if inheritType is 0 (eInheritRrSs)
|
||||||
|
// it might not be a problem.
|
||||||
|
Vector3f localScale = jmeLocalNodeTransform.getScale(); |
||||||
|
if (!FastMath.approximateEquals(localScale.x, localScale.y) || |
||||||
|
!FastMath.approximateEquals(localScale.x, localScale.z)) { |
||||||
|
logger.log(Level.WARNING, "Non-uniform scale detected on parent node. " + |
||||||
|
"The model may appear distorted."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
spatial.setLocalTransform(jmeLocalNodeTransform); |
||||||
|
|
||||||
|
if (visibility == 0.0) { |
||||||
|
spatial.setCullHint(CullHint.Always); |
||||||
|
} |
||||||
|
|
||||||
|
for (Map.Entry<String, Object> userDataEntry : userData.entrySet()) { |
||||||
|
spatial.setUserData(userDataEntry.getKey(), userDataEntry.getValue()); |
||||||
|
} |
||||||
|
|
||||||
|
return spatial; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create jME3 Skeleton objects on the scene. |
||||||
|
* |
||||||
|
* Goes through the scene graph and finds limbs that are |
||||||
|
* attached to FBX nodes, then creates a Skeleton on the node |
||||||
|
* based on the child limbs. |
||||||
|
* |
||||||
|
* Must be called prior to calling |
||||||
|
* {@link #createScene(com.jme3.scene.plugins.fbx.node.FbxNode)}. |
||||||
|
* |
||||||
|
* @param fbxNode The root FBX node. |
||||||
|
*/ |
||||||
|
public static void createSkeletons(FbxNode fbxNode) { |
||||||
|
boolean createSkeleton = false; |
||||||
|
for (FbxNode fbxChild : fbxNode.children) { |
||||||
|
if (fbxChild instanceof FbxLimbNode) { |
||||||
|
createSkeleton = true; |
||||||
|
} else { |
||||||
|
createSkeletons(fbxChild); |
||||||
|
} |
||||||
|
} |
||||||
|
if (createSkeleton) { |
||||||
|
if (fbxNode.skeleton != null) { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode); |
||||||
|
System.out.println("created skeleton: " + fbxNode.skeleton); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static void relocateSpatial(Spatial spatial, |
||||||
|
Transform originalWorldTransform, Transform newWorldTransform) { |
||||||
|
Transform localTransform = new Transform(); |
||||||
|
localTransform.set(originalWorldTransform); |
||||||
|
localTransform.combineWithParent(newWorldTransform.invert()); |
||||||
|
spatial.setLocalTransform(localTransform); |
||||||
|
} |
||||||
|
|
||||||
|
public static Spatial createScene(FbxNode fbxNode) { |
||||||
|
Spatial jmeSpatial = fbxNode.getJmeObject(); |
||||||
|
|
||||||
|
if (jmeSpatial instanceof Node) { |
||||||
|
// Attach children to Node
|
||||||
|
Node jmeNode = (Node) jmeSpatial; |
||||||
|
for (FbxNode fbxChild : fbxNode.children) { |
||||||
|
if (!(fbxChild instanceof FbxLimbNode)) { |
||||||
|
createScene(fbxChild); |
||||||
|
|
||||||
|
FbxNode preferredParent = fbxChild.getPreferredParent(); |
||||||
|
Spatial jmeChild = fbxChild.getJmeObject(); |
||||||
|
if (preferredParent != null) { |
||||||
|
System.out.println("Preferred parent for " + fbxChild + " is " + preferredParent); |
||||||
|
|
||||||
|
Node jmePreferredParent = (Node) preferredParent.getJmeObject(); |
||||||
|
relocateSpatial(jmeChild, fbxChild.jmeWorldNodeTransform, |
||||||
|
preferredParent.jmeWorldNodeTransform); |
||||||
|
jmePreferredParent.attachChild(jmeChild); |
||||||
|
} else { |
||||||
|
jmeNode.attachChild(jmeChild); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (fbxNode.skeleton != null) { |
||||||
|
jmeSpatial.addControl(new AnimControl(fbxNode.skeleton)); |
||||||
|
jmeSpatial.addControl(new SkeletonControl(fbxNode.skeleton)); |
||||||
|
|
||||||
|
SkeletonDebugger sd = new SkeletonDebugger("debug", fbxNode.skeleton); |
||||||
|
Material mat = new Material(fbxNode.assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
||||||
|
mat.getAdditionalRenderState().setWireframe(true); |
||||||
|
mat.getAdditionalRenderState().setDepthTest(false); |
||||||
|
mat.setColor("Color", ColorRGBA.Green); |
||||||
|
sd.setMaterial(mat); |
||||||
|
|
||||||
|
((Node)jmeSpatial).attachChild(sd); |
||||||
|
} |
||||||
|
|
||||||
|
return jmeSpatial; |
||||||
|
} |
||||||
|
|
||||||
|
// public SceneLoader.Limb toLimb() {
|
||||||
|
// SceneLoader.Limb limb = new SceneLoader.Limb();
|
||||||
|
// limb.name = getName();
|
||||||
|
// Quaternion rotation = preRotation.mult(localRotation);
|
||||||
|
// limb.bindTransform = new Transform(localTranslation, rotation, localScale);
|
||||||
|
// return limb;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public Skeleton getJmeSkeleton() { |
||||||
|
return skeleton; |
||||||
|
} |
||||||
|
|
||||||
|
public List<FbxNode> getChildren() { |
||||||
|
return children; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObject(FbxObject object) { |
||||||
|
if (object instanceof FbxNode) { |
||||||
|
// Scene Graph Object
|
||||||
|
FbxNode childNode = (FbxNode) object; |
||||||
|
if (childNode.parent != null) { |
||||||
|
throw new IllegalStateException("Cannot attach " + childNode |
||||||
|
+ " to " + this + ". It is already " |
||||||
|
+ "attached to " + childNode.parent); |
||||||
|
} |
||||||
|
childNode.parent = this; |
||||||
|
children.add(childNode); |
||||||
|
} else if (object instanceof FbxNodeAttribute) { |
||||||
|
// Node Attribute
|
||||||
|
if (nodeAttribute != null) { |
||||||
|
throw new IllegalStateException("An FBXNodeAttribute (" + nodeAttribute + ")" + |
||||||
|
" is already attached to " + this + ". " + |
||||||
|
"Only one attribute allowed per node."); |
||||||
|
} |
||||||
|
|
||||||
|
nodeAttribute = (FbxNodeAttribute) object; |
||||||
|
if (nodeAttribute instanceof FbxNullAttribute) { |
||||||
|
nodeAttribute.getJmeObject(); |
||||||
|
} |
||||||
|
} else if (object instanceof FbxMaterial) { |
||||||
|
materials.add((FbxMaterial) object); |
||||||
|
} else if (object instanceof FbxImage || object instanceof FbxTexture) { |
||||||
|
// Ignore - attaching textures to nodes is legacy feature.
|
||||||
|
} else { |
||||||
|
unsupportedConnectObject(object); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connectObjectProperty(FbxObject object, String property) { |
||||||
|
// Only allowed to connect local transform properties to object
|
||||||
|
// (FbxAnimCurveNode)
|
||||||
|
if (object instanceof FbxAnimCurveNode) { |
||||||
|
FbxAnimCurveNode curveNode = (FbxAnimCurveNode) object; |
||||||
|
if (property.equals("Lcl Translation") |
||||||
|
|| property.equals("Lcl Rotation") |
||||||
|
|| property.equals("Lcl Scaling")) { |
||||||
|
|
||||||
|
List<FbxAnimCurveNode> curveNodes = propertyToAnimCurveMap.get(property); |
||||||
|
if (curveNodes == null) { |
||||||
|
curveNodes = new ArrayList<FbxAnimCurveNode>(); |
||||||
|
curveNodes.add(curveNode); |
||||||
|
propertyToAnimCurveMap.put(property, curveNodes); |
||||||
|
} |
||||||
|
curveNodes.add(curveNode); |
||||||
|
|
||||||
|
// Make sure the curve knows about it animating
|
||||||
|
// this node as well.
|
||||||
|
curveNode.addInfluencedNode(this, property); |
||||||
|
} else { |
||||||
|
logger.log(Level.WARNING, "Animating the property ''{0}'' is not " |
||||||
|
+ "supported. Ignoring.", property); |
||||||
|
} |
||||||
|
} else { |
||||||
|
unsupportedConnectObjectProperty(object, property); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2009-2015 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.fbx.node; |
||||||
|
|
||||||
|
import com.jme3.asset.AssetManager; |
||||||
|
import com.jme3.scene.plugins.fbx.obj.FbxObject; |
||||||
|
|
||||||
|
public abstract class FbxNodeAttribute<JT> extends FbxObject<JT> { |
||||||
|
public FbxNodeAttribute(AssetManager assetManager, String sceneFolderName) { |
||||||
|
super(assetManager, sceneFolderName); |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue