Compare commits
92 Commits
master
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
|
74a1d8b219 | ||
|
3bb01d6963 | ||
|
4fcd575c47 | ||
|
60d9d1b4d8 | ||
|
4ab5a9f7dd | ||
|
452c307d59 | ||
|
a8fca2bcf6 | ||
|
2fc4e5b607 | ||
|
f28d74a1f6 | ||
|
87af1f30b0 | ||
|
e691de4459 | ||
|
2dafd1e485 | ||
|
c586bacbb0 | ||
|
fde095458e | ||
|
e8fd22223a | ||
|
53d3b72478 | ||
|
644b8167b8 | ||
|
2cdb4a8486 | ||
|
152a7638cd | ||
|
adc5084f5d | ||
|
42d76cfd29 | ||
|
ba22487c38 | ||
|
00b6d904af | ||
|
2d2c394b42 | ||
|
655457ab6a | ||
|
7639657fc1 | ||
|
e87f008244 | ||
|
12b3c4a140 | ||
|
ae7fb6984c | ||
|
3b9e412f80 | ||
|
0aaa28e66b | ||
|
9b45189f48 | ||
|
69eaf39da9 | ||
|
8024babb47 | ||
|
c7da1c4efd | ||
|
ab8527770c | ||
|
c6b568c125 | ||
|
772330c308 | ||
|
8413ed715c | ||
|
d76cb99772 | ||
|
454e210d3d | ||
|
c6336c0781 | ||
|
fda40563c5 | ||
|
34aa21bfd9 | ||
|
f9969008c3 | ||
|
2893ac9156 | ||
|
a3638f3e0c | ||
|
27041e1341 | ||
|
962ab22ef4 | ||
|
f9500f955f | ||
|
c50839796f | ||
|
ff6b1be725 | ||
|
97281de5c4 | ||
|
8f54af3263 | ||
|
79125f2f63 | ||
|
908b37350d | ||
|
f986043745 | ||
|
961bf92734 | ||
|
3d82f5c459 | ||
|
4a646de49d | ||
|
06e8210e5d | ||
|
85feb305ef | ||
|
15465a020f | ||
|
f005c05f8d | ||
|
42729b2302 | ||
|
30855f5bb4 | ||
|
352c02db8a | ||
|
ea4d750d52 | ||
|
6db1d15045 | ||
|
f9ce9e246c | ||
|
18db26292f | ||
|
0d3ebf75bd | ||
|
4e572605a8 | ||
|
28e2b5650c | ||
|
3d2a9b83e9 | ||
|
618c8d02eb | ||
|
3a00aff886 | ||
|
fc680ea121 | ||
|
12c001addc | ||
|
7b147171bf | ||
|
2e9a9f9f9e | ||
|
c72b036c9f | ||
|
81a76fdf69 | ||
|
7b64e91681 | ||
|
d2f38f8adb | ||
|
937d97b8d7 | ||
|
9e17f39cfb | ||
|
2ced7653a7 | ||
|
f0b63e7910 | ||
|
860de88298 | ||
|
e8f344a0db | ||
|
bee759bddc |
@ -1,42 +1,40 @@
|
||||
# Version number used for plugins, only 3 numbers (e.g. 3.1.3)
|
||||
jmeVersion = 3.1.0
|
||||
# Version used for application and settings folder, no spaces!
|
||||
jmeMainVersion = 3.1
|
||||
# Version addition pre-alpha-svn, Stable, Beta
|
||||
jmeVersionTag = SNAPSHOT
|
||||
# Increment this each time jmeVersionTag changes but jmeVersion stays the same
|
||||
jmeVersionTagID = 0
|
||||
|
||||
# specify if JavaDoc should be built
|
||||
buildJavaDoc = true
|
||||
|
||||
# specify if SDK and Native libraries get built
|
||||
buildNativeProjects = false
|
||||
buildAndroidExamples = false
|
||||
|
||||
# Path to android NDK for building native libraries
|
||||
#ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7
|
||||
ndkPath = /opt/android-ndk-r10c
|
||||
|
||||
# Path for downloading native Bullet
|
||||
bulletUrl = http://bullet.googlecode.com/files/bullet-2.82-r2704.zip
|
||||
bulletFolder = bullet-2.82-r2704
|
||||
bulletZipFile = bullet.zip
|
||||
|
||||
# Path for downloading NetBeans Base
|
||||
netbeansUrl = http://download.netbeans.org/netbeans/8.0.2/final/zip/netbeans-8.0.2-201411181905-javase.zip
|
||||
|
||||
# POM settings
|
||||
POM_NAME=jMonkeyEngine
|
||||
POM_DESCRIPTION=jMonkeyEngine is a 3D game engine for adventurous Java developers
|
||||
POM_URL=http://jmonkeyengine.org
|
||||
POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine
|
||||
POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git
|
||||
POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git
|
||||
POM_LICENSE_NAME=New BSD (3-clause) License
|
||||
POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause
|
||||
POM_LICENSE_DISTRIBUTION=repo
|
||||
|
||||
# Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline
|
||||
bintray_user=
|
||||
bintray_api_key=
|
||||
# Version number used for plugins, only 3 numbers (e.g. 3.1.3)
|
||||
jmeVersion = 3.1.0
|
||||
# Version used for application and settings folder, no spaces!
|
||||
jmeMainVersion = 3.1
|
||||
# Increment this each time jmeVersionTag changes but jmeVersion stays the same
|
||||
jmeVersionTagID = 0
|
||||
|
||||
# specify if JavaDoc should be built
|
||||
buildJavaDoc = false
|
||||
|
||||
# specify if SDK and Native libraries get built
|
||||
buildNativeProjects = false
|
||||
buildAndroidExamples = false
|
||||
|
||||
# Path to android NDK for building native libraries
|
||||
#ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7
|
||||
ndkPath = /opt/android-ndk-r10c
|
||||
|
||||
# Path for downloading native Bullet
|
||||
bulletUrl = http://bullet.googlecode.com/files/bullet-2.82-r2704.zip
|
||||
bulletFolder = bullet-2.82-r2704
|
||||
bulletZipFile = bullet.zip
|
||||
|
||||
# Path for downloading NetBeans Base
|
||||
netbeansUrl = http://download.netbeans.org/netbeans/8.0.2/final/zip/netbeans-8.0.2-201411181905-javase.zip
|
||||
|
||||
# POM settings
|
||||
POM_NAME=jMonkeyEngine
|
||||
POM_DESCRIPTION=jMonkeyEngine is a 3D game engine for adventurous Java developers
|
||||
POM_URL=http://jmonkeyengine.org
|
||||
POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine
|
||||
POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git
|
||||
POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git
|
||||
POM_LICENSE_NAME=New BSD (3-clause) License
|
||||
POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause
|
||||
POM_LICENSE_DISTRIBUTION=repo
|
||||
|
||||
# Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline
|
||||
bintray_user=
|
||||
bintray_api_key=
|
||||
|
@ -13,10 +13,24 @@ sourceSets {
|
||||
test {
|
||||
java {
|
||||
srcDir 'src/test/java'
|
||||
srcDir 'src/plugins/java'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
testOutput.extendsFrom testCompile
|
||||
}
|
||||
|
||||
task testJar(type: Jar) {
|
||||
classifier "test"
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
artifacts {
|
||||
testOutput testJar
|
||||
}
|
||||
|
||||
task updateVersionPropertiesFile << {
|
||||
def verfile = file('src/main/resources/com/jme3/system/version.properties')
|
||||
verfile.text = "# THIS IS AN AUTO-GENERATED FILE..\n" +
|
||||
@ -34,6 +48,3 @@ task updateVersionPropertiesFile << {
|
||||
}
|
||||
|
||||
compileJava.dependsOn(updateVersionPropertiesFile)
|
||||
|
||||
dependencies {
|
||||
}
|
||||
|
@ -1165,7 +1165,8 @@ public class ParticleEmitter extends Geometry {
|
||||
|
||||
inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
|
||||
}
|
||||
particleMesh.updateParticleData(particles, cam, inverseRotation);
|
||||
particleMesh.updateParticleData(rm, particles, cam, inverseRotation);
|
||||
|
||||
if (!worldSpace) {
|
||||
vars.release();
|
||||
}
|
||||
@ -1173,7 +1174,7 @@ public class ParticleEmitter extends Geometry {
|
||||
|
||||
public void preload(RenderManager rm, ViewPort vp) {
|
||||
this.updateParticleState(0);
|
||||
particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
|
||||
particleMesh.updateParticleData(rm, particles, vp.getCamera(), Matrix3f.IDENTITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -34,6 +34,7 @@ package com.jme3.effect;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.Matrix3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.scene.Mesh;
|
||||
|
||||
/**
|
||||
@ -80,6 +81,6 @@ public abstract class ParticleMesh extends Mesh {
|
||||
/**
|
||||
* Update the particle visual data. Typically called every frame.
|
||||
*/
|
||||
public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation);
|
||||
public abstract void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation);
|
||||
|
||||
}
|
||||
|
@ -33,15 +33,24 @@ package com.jme3.effect;
|
||||
|
||||
import com.jme3.math.Matrix3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Format;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.TempVars;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class ParticlePointMesh extends ParticleMesh {
|
||||
|
||||
private static final int POS_SIZE = 3 * 4;
|
||||
private static final int COLOR_SIZE = 4 * 1;
|
||||
private static final int SIZE_SIZE = 1 * 4;
|
||||
private static final int UV_SIZE = 4 * 4;
|
||||
private static final int BYTES_PER_PARTICLE = POS_SIZE + COLOR_SIZE + SIZE_SIZE + UV_SIZE;
|
||||
private static final int FLOATS_PER_PARTICLE = BYTES_PER_PARTICLE / 4;
|
||||
|
||||
private ParticleEmitter emitter;
|
||||
|
||||
private int imagesX = 1;
|
||||
@ -59,109 +68,108 @@ public class ParticlePointMesh extends ParticleMesh {
|
||||
|
||||
this.emitter = emitter;
|
||||
|
||||
// set positions
|
||||
FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles);
|
||||
|
||||
//if the buffer is already set only update the data
|
||||
VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
|
||||
if (buf != null) {
|
||||
buf.updateData(pb);
|
||||
ByteBuffer eb = BufferUtils.createByteBuffer(BYTES_PER_PARTICLE * numParticles);
|
||||
VertexBuffer vb = getBuffer(VertexBuffer.Type.InterleavedData);
|
||||
if (vb != null) {
|
||||
vb.updateData(eb);
|
||||
} else {
|
||||
VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
|
||||
pvb.setupData(Usage.Stream, 3, Format.Float, pb);
|
||||
setBuffer(pvb);
|
||||
vb = new VertexBuffer(VertexBuffer.Type.InterleavedData);
|
||||
vb.setupData(Usage.Stream, 1, Format.Byte, eb);
|
||||
setBuffer(vb);
|
||||
}
|
||||
|
||||
// set colors
|
||||
ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4);
|
||||
|
||||
buf = getBuffer(VertexBuffer.Type.Color);
|
||||
if (buf != null) {
|
||||
buf.updateData(cb);
|
||||
} else {
|
||||
VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
|
||||
cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
|
||||
cvb.setNormalized(true);
|
||||
setBuffer(cvb);
|
||||
if (getBuffer(VertexBuffer.Type.Position) == null) {
|
||||
VertexBuffer pb = new VertexBuffer(VertexBuffer.Type.Position);
|
||||
pb.setupData(Usage.Stream, 3, Format.Float, eb);
|
||||
pb.updateData(null);
|
||||
pb.setOffset(0);
|
||||
pb.setStride(BYTES_PER_PARTICLE);
|
||||
setBuffer(pb);
|
||||
|
||||
VertexBuffer cb = new VertexBuffer(VertexBuffer.Type.Color);
|
||||
cb.setupData(Usage.Stream, 4, Format.UnsignedByte, eb);
|
||||
cb.updateData(null);
|
||||
cb.setNormalized(true);
|
||||
cb.setOffset(POS_SIZE);
|
||||
cb.setStride(BYTES_PER_PARTICLE);
|
||||
setBuffer(cb);
|
||||
|
||||
VertexBuffer sb = new VertexBuffer(VertexBuffer.Type.Size);
|
||||
sb.setupData(Usage.Stream, 1, Format.Float, eb);
|
||||
sb.updateData(null);
|
||||
sb.setOffset(POS_SIZE + COLOR_SIZE);
|
||||
sb.setStride(BYTES_PER_PARTICLE);
|
||||
setBuffer(sb);
|
||||
|
||||
VertexBuffer tb = new VertexBuffer(VertexBuffer.Type.TexCoord);
|
||||
tb.setupData(Usage.Stream, 4, Format.Float, eb);
|
||||
tb.updateData(null);
|
||||
tb.setOffset(POS_SIZE + COLOR_SIZE + SIZE_SIZE);
|
||||
tb.setStride(BYTES_PER_PARTICLE);
|
||||
setBuffer(tb);
|
||||
}
|
||||
|
||||
// set sizes
|
||||
FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles);
|
||||
|
||||
buf = getBuffer(VertexBuffer.Type.Size);
|
||||
if (buf != null) {
|
||||
buf.updateData(sb);
|
||||
} else {
|
||||
VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size);
|
||||
svb.setupData(Usage.Stream, 1, Format.Float, sb);
|
||||
setBuffer(svb);
|
||||
}
|
||||
|
||||
// set UV-scale
|
||||
FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles*4);
|
||||
|
||||
buf = getBuffer(VertexBuffer.Type.TexCoord);
|
||||
if (buf != null) {
|
||||
buf.updateData(tb);
|
||||
} else {
|
||||
VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
|
||||
tvb.setupData(Usage.Stream, 4, Format.Float, tb);
|
||||
setBuffer(tvb);
|
||||
}
|
||||
|
||||
updateCounts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
|
||||
VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position);
|
||||
FloatBuffer positions = (FloatBuffer) pvb.getData();
|
||||
|
||||
VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color);
|
||||
ByteBuffer colors = (ByteBuffer) cvb.getData();
|
||||
|
||||
VertexBuffer svb = getBuffer(VertexBuffer.Type.Size);
|
||||
FloatBuffer sizes = (FloatBuffer) svb.getData();
|
||||
|
||||
VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord);
|
||||
FloatBuffer texcoords = (FloatBuffer) tvb.getData();
|
||||
public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
|
||||
VertexBuffer eb = getBuffer(VertexBuffer.Type.InterleavedData);
|
||||
ByteBuffer elements = (ByteBuffer) eb.getData();
|
||||
FloatBuffer floatElements = elements.asFloatBuffer();
|
||||
|
||||
float sizeScale = emitter.getWorldScale().x;
|
||||
|
||||
// update data in vertex buffers
|
||||
positions.rewind();
|
||||
colors.rewind();
|
||||
sizes.rewind();
|
||||
texcoords.rewind();
|
||||
for (int i = 0; i < particles.length; i++){
|
||||
Particle p = particles[i];
|
||||
|
||||
positions.put(p.position.x)
|
||||
.put(p.position.y)
|
||||
.put(p.position.z);
|
||||
TempVars vars = TempVars.get();
|
||||
try {
|
||||
float[] floatArray = vars.skinTangents;
|
||||
|
||||
sizes.put(p.size * sizeScale);
|
||||
colors.putInt(p.color.asIntABGR());
|
||||
int particlesPerIteration = floatArray.length / FLOATS_PER_PARTICLE;
|
||||
int iterations = (particles.length + particlesPerIteration - 1) / particlesPerIteration;
|
||||
|
||||
int imgX = p.imageIndex % imagesX;
|
||||
int imgY = (p.imageIndex - imgX) / imagesY;
|
||||
int particleIndex = 0;
|
||||
for (int iteration = 0; iteration < iterations; iteration++) {
|
||||
int particlesRemaining = Math.min(
|
||||
particles.length - particleIndex,
|
||||
particlesPerIteration);
|
||||
|
||||
float startX = ((float) imgX) / imagesX;
|
||||
float startY = ((float) imgY) / imagesY;
|
||||
float endX = startX + (1f / imagesX);
|
||||
float endY = startY + (1f / imagesY);
|
||||
int floatIndex = 0;
|
||||
for (int i = 0; i < particlesRemaining; i++) {
|
||||
Particle p = particles[particleIndex++];
|
||||
|
||||
texcoords.put(startX).put(startY).put(endX).put(endY);
|
||||
floatArray[floatIndex++] = p.position.x;
|
||||
floatArray[floatIndex++] = p.position.y;
|
||||
floatArray[floatIndex++] = p.position.z;
|
||||
floatArray[floatIndex++] = Float.intBitsToFloat(p.color.asIntABGR());
|
||||
floatArray[floatIndex++] = p.size * sizeScale;
|
||||
|
||||
int imgX = p.imageIndex % imagesX;
|
||||
int imgY = (p.imageIndex - imgX) / imagesY;
|
||||
|
||||
float startX = ((float) imgX) / imagesX;
|
||||
float startY = ((float) imgY) / imagesY;
|
||||
float endX = startX + (1f / imagesX);
|
||||
float endY = startY + (1f / imagesY);
|
||||
|
||||
floatArray[floatIndex++] = startX;
|
||||
floatArray[floatIndex++] = startY;
|
||||
floatArray[floatIndex++] = endX;
|
||||
floatArray[floatIndex++] = endY;
|
||||
}
|
||||
|
||||
floatElements.put(floatArray, 0, FLOATS_PER_PARTICLE * particlesRemaining);
|
||||
}
|
||||
|
||||
if (floatElements.remaining() != 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
eb.updateData(elements);
|
||||
|
||||
// cheating!
|
||||
rm.getRenderer().updateBufferData(eb);
|
||||
} finally {
|
||||
vars.release();
|
||||
}
|
||||
positions.flip();
|
||||
colors.flip();
|
||||
sizes.flip();
|
||||
texcoords.flip();
|
||||
|
||||
// force renderer to re-send data to GPU
|
||||
pvb.updateData(positions);
|
||||
cvb.updateData(colors);
|
||||
svb.updateData(sizes);
|
||||
tvb.updateData(texcoords);
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Matrix3f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.VertexBuffer.Format;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
@ -145,7 +146,7 @@ public class ParticleTriMesh extends ParticleMesh {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
|
||||
public void updateParticleData(RenderManager rm, Particle[] particles, Camera cam, Matrix3f inverseRotation) {
|
||||
// System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length);
|
||||
// comparator.setCamera(cam);
|
||||
// Arrays.sort(particlesCopy, comparator);
|
||||
|
@ -145,6 +145,11 @@ class BitmapTextPage extends Geometry {
|
||||
Mesh m = getMesh();
|
||||
int vertCount = pageQuads.size() * 4;
|
||||
int triCount = pageQuads.size() * 2;
|
||||
|
||||
if (vertCount > m.getVertexCount() ||
|
||||
triCount > m.getTriangleCount()) {
|
||||
m.setUpdateNeeded();
|
||||
}
|
||||
|
||||
VertexBuffer pb = m.getBuffer(Type.Position);
|
||||
VertexBuffer tb = m.getBuffer(Type.TexCoord);
|
||||
|
@ -349,7 +349,12 @@ public enum Caps {
|
||||
/**
|
||||
* GPU can provide and accept binary shaders.
|
||||
*/
|
||||
BinaryShader;
|
||||
BinaryShader,
|
||||
|
||||
/**
|
||||
* Supports {@link Format#RGTC} and {@link Format#RTC} texture compression.
|
||||
*/
|
||||
TextureCompressionRGTC;
|
||||
|
||||
/**
|
||||
* Returns true if given the renderer capabilities, the texture
|
||||
|
@ -283,6 +283,12 @@ public interface Renderer {
|
||||
* the per-instance attributes.
|
||||
*/
|
||||
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData);
|
||||
|
||||
/**
|
||||
* Delete a Mesh (or Vertex Array Object in GL terms) from the GPU.
|
||||
* @param mesh The mesh to delete.
|
||||
*/
|
||||
public void deleteMesh(Mesh mesh);
|
||||
|
||||
/**
|
||||
* Resets all previously used {@link NativeObject Native Objects} on this Renderer.
|
||||
|
280
jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java
Executable file
280
jme3-core/src/main/java/com/jme3/renderer/opengl/AsyncFrameReader.java
Executable file
@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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 com.jme3.renderer.RenderContext;
|
||||
import com.jme3.renderer.RendererException;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
final class AsyncFrameReader {
|
||||
|
||||
private final ArrayList<PixelBuffer> pboPool = new ArrayList<PixelBuffer>();
|
||||
private final List<FrameBufferReadRequest> pending = Collections.synchronizedList(new ArrayList<FrameBufferReadRequest>());
|
||||
private final GLRenderer renderer;
|
||||
private final GL gl;
|
||||
private final GLExt glext;
|
||||
private final IntBuffer intBuf = BufferUtils.createIntBuffer(1);
|
||||
private final RenderContext context;
|
||||
private final Thread glThread;
|
||||
|
||||
AsyncFrameReader(GLRenderer renderer, GL gl, GLExt glext, RenderContext context) {
|
||||
this.renderer = renderer;
|
||||
this.gl = gl;
|
||||
this.glext = glext;
|
||||
this.context = context;
|
||||
this.glThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
private PixelBuffer acquirePixelBuffer(int dataSize) {
|
||||
PixelBuffer pb;
|
||||
|
||||
if (pboPool.isEmpty()) {
|
||||
// create PBO
|
||||
pb = new PixelBuffer();
|
||||
intBuf.clear();
|
||||
gl.glGenBuffers(intBuf);
|
||||
pb.id = intBuf.get(0);
|
||||
} else {
|
||||
// reuse PBO.
|
||||
pb = pboPool.remove(pboPool.size() - 1);
|
||||
}
|
||||
|
||||
// resize or allocate PBO if required.
|
||||
if (pb.size != dataSize) {
|
||||
if (context.boundPixelPackPBO != pb.id) {
|
||||
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pb.id);
|
||||
context.boundPixelPackPBO = pb.id;
|
||||
}
|
||||
gl.glBufferData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, dataSize, GL.GL_STREAM_READ);
|
||||
}
|
||||
|
||||
pb.size = dataSize;
|
||||
|
||||
return pb;
|
||||
}
|
||||
|
||||
private void readFrameBufferFromPBO(FrameBufferReadRequest fbrr) {
|
||||
// assumes waitForCompletion was already called!
|
||||
if (context.boundPixelPackPBO != fbrr.pb.id) {
|
||||
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, fbrr.pb.id);
|
||||
context.boundPixelPackPBO = fbrr.pb.id;
|
||||
}
|
||||
gl.glGetBufferSubData(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0, fbrr.targetBuf);
|
||||
}
|
||||
|
||||
private boolean waitForCompletion(FrameBufferReadRequest fbrr, long time, TimeUnit unit, boolean flush) {
|
||||
int flags = flush ? GLExt.GL_SYNC_FLUSH_COMMANDS_BIT : 0;
|
||||
long nanos = unit.toNanos(time);
|
||||
switch (glext.glClientWaitSync(fbrr.fence, flags, nanos)) {
|
||||
case GLExt.GL_ALREADY_SIGNALED:
|
||||
case GLExt.GL_CONDITION_SATISFIED:
|
||||
return true;
|
||||
case GLExt.GL_TIMEOUT_EXPIRED:
|
||||
return false;
|
||||
case GLExt.GL_WAIT_FAILED:
|
||||
throw new RendererException("Waiting for fence failed");
|
||||
default:
|
||||
throw new RendererException("Unexpected result from glClientWaitSync");
|
||||
}
|
||||
}
|
||||
|
||||
private void signalFinished(FrameBufferReadRequest fbrr) {
|
||||
fbrr.lock.lock();
|
||||
try {
|
||||
fbrr.done = true;
|
||||
fbrr.cond.signalAll();
|
||||
} finally {
|
||||
fbrr.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void signalCancelled(FrameBufferReadRequest fbrr) {
|
||||
fbrr.lock.lock();
|
||||
try {
|
||||
fbrr.cancelled = true;
|
||||
fbrr.cond.signalAll();
|
||||
} finally {
|
||||
fbrr.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateReadRequests() {
|
||||
// Update requests in the order they were made (e.g. earliest first)
|
||||
for (Iterator<FrameBufferReadRequest> it = pending.iterator(); it.hasNext();) {
|
||||
FrameBufferReadRequest fbrr = it.next();
|
||||
|
||||
// Check status for the user... (non-blocking)
|
||||
if (!fbrr.cancelled && !fbrr.done) {
|
||||
// Request a flush if we know clients are waiting
|
||||
// (to speed up the process, or make it take finite time ..)
|
||||
boolean flush = false; // fbrr.clientsWaiting.get() > 0;
|
||||
if (waitForCompletion(fbrr, 0, TimeUnit.NANOSECONDS, flush)) {
|
||||
if (!fbrr.cancelled) {
|
||||
// Operation completed.
|
||||
// Read data into user's ByteBuffer
|
||||
readFrameBufferFromPBO(fbrr);
|
||||
|
||||
// Signal any waiting threads that we are done.
|
||||
// Also, set the done flag.
|
||||
signalFinished(fbrr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fbrr.cancelled || fbrr.done) {
|
||||
// Cleanup
|
||||
// Return the pixel buffer back into the pool.
|
||||
if (!pboPool.contains(fbrr.pb)) {
|
||||
pboPool.add(fbrr.pb);
|
||||
}
|
||||
|
||||
// Remove this request from the pending requests list.
|
||||
it.remove();
|
||||
|
||||
// Get rid of the fence
|
||||
glext.glDeleteSync(fbrr.fence);
|
||||
|
||||
fbrr.pb = null;
|
||||
fbrr.fence = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer getFrameBufferData(FrameBufferReadRequest fbrr, long time, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
|
||||
if (fbrr.cancelled) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
if (fbrr.done) {
|
||||
return fbrr.targetBuf;
|
||||
}
|
||||
|
||||
if (glThread == Thread.currentThread()) {
|
||||
// Running on GL thread, hence can use GL commands ..
|
||||
try {
|
||||
// Wait until we reach the fence..
|
||||
|
||||
// PROBLEM: if the user is holding any locks,
|
||||
// they will not be released here,
|
||||
// causing a potential deadlock!
|
||||
if (!waitForCompletion(fbrr, time, unit, true)) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
|
||||
// Command stream reached this point.
|
||||
if (fbrr.cancelled) {
|
||||
// User not interested in this anymore.
|
||||
throw new CancellationException();
|
||||
} else {
|
||||
// Read data into user's ByteBuffer
|
||||
readFrameBufferFromPBO(fbrr);
|
||||
}
|
||||
|
||||
// Mark it as done, so future get() calls always return.
|
||||
signalFinished(fbrr);
|
||||
|
||||
return fbrr.targetBuf;
|
||||
} catch (RendererException ex) {
|
||||
throw new ExecutionException(ex);
|
||||
}
|
||||
} else {
|
||||
long nanos = unit.toNanos(time);
|
||||
|
||||
fbrr.lock.lock();
|
||||
try {
|
||||
// Not running on GL thread, indicate that we are running
|
||||
// so GL thread can request GPU to finish quicker ...
|
||||
fbrr.clientsWaiting.getAndIncrement();
|
||||
|
||||
// Wait until we finish
|
||||
while (!fbrr.done && !fbrr.cancelled) {
|
||||
if (nanos <= 0L) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
|
||||
nanos = fbrr.cond.awaitNanos(nanos);
|
||||
}
|
||||
|
||||
if (fbrr.cancelled) {
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
return fbrr.targetBuf;
|
||||
} finally {
|
||||
fbrr.lock.unlock();
|
||||
fbrr.clientsWaiting.getAndDecrement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) {
|
||||
// Create & allocate a PBO (or reuse an existing one if available)
|
||||
FrameBufferReadRequest fbrr = new FrameBufferReadRequest(this);
|
||||
fbrr.targetBuf = byteBuf;
|
||||
|
||||
int desiredSize = fb.getWidth() * fb.getHeight() * 4;
|
||||
|
||||
if (byteBuf.remaining() != desiredSize) {
|
||||
throw new IllegalArgumentException("Ensure buffer size matches framebuffer size");
|
||||
}
|
||||
|
||||
fbrr.pb = acquirePixelBuffer(desiredSize);
|
||||
|
||||
// Read into PBO (asynchronous)
|
||||
// renderer.readFrameBufferWithGLFormat(fb, null, GL2.GL_BGRA, GL2.GL_UNSIGNED_BYTE, fbrr.pb.id);
|
||||
|
||||
// Insert fence into command stream.
|
||||
fbrr.fence = glext.glFenceSync(GLExt.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
|
||||
// Insert into FIFO
|
||||
pending.add(fbrr);
|
||||
|
||||
return fbrr;
|
||||
}
|
||||
}
|
98
jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java
Executable file
98
jme3-core/src/main/java/com/jme3/renderer/opengl/FrameBufferReadRequest.java
Executable file
@ -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.renderer.opengl;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
class PixelBuffer {
|
||||
int id = -1;
|
||||
int size = -1;
|
||||
}
|
||||
|
||||
class FrameBufferReadRequest implements Future<ByteBuffer> {
|
||||
|
||||
AsyncFrameReader reader;
|
||||
Object fence;
|
||||
PixelBuffer pb;
|
||||
ByteBuffer targetBuf;
|
||||
boolean cancelled;
|
||||
boolean done;
|
||||
|
||||
final ReentrantLock lock = new ReentrantLock();
|
||||
final Condition cond = lock.newCondition();
|
||||
final AtomicInteger clientsWaiting = new AtomicInteger(0);
|
||||
|
||||
public FrameBufferReadRequest(AsyncFrameReader reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (isDone()) {
|
||||
return false;
|
||||
}
|
||||
reader.signalCancelled(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer get(long l, TimeUnit tu) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return reader.getFrameBufferData(this, l, tu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer get() throws InterruptedException, ExecutionException {
|
||||
try {
|
||||
return get(1, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException ex) {
|
||||
throw new ExecutionException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ import java.nio.IntBuffer;
|
||||
public interface GLExt {
|
||||
|
||||
public static final int GL_ALREADY_SIGNALED = 0x911A;
|
||||
public static final int GL_COMPRESSED_RED_RGTC1 = 0x8DBB;
|
||||
public static final int GL_COMPRESSED_RG_RGTC2 = 0x8DBD;
|
||||
public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274;
|
||||
public static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
|
||||
public static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
|
||||
|
@ -233,6 +233,11 @@ public final class GLImageFormats {
|
||||
formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
|
||||
}
|
||||
|
||||
if (caps.contains(Caps.TextureCompressionRGTC)) {
|
||||
formatComp(formatToGL, Format.RGTC, GLExt.GL_COMPRESSED_RG_RGTC2, GL.GL_RGB, GL.GL_UNSIGNED_BYTE);
|
||||
formatComp(formatToGL, Format.RTC, GLExt.GL_COMPRESSED_RED_RGTC1, GL.GL_RED, GL.GL_UNSIGNED_BYTE);
|
||||
}
|
||||
|
||||
return formatToGL;
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
@ -100,6 +101,7 @@ public final class GLRenderer implements Renderer {
|
||||
private final GLExt glext;
|
||||
private final GLFbo glfbo;
|
||||
private final TextureUtil texUtil;
|
||||
private final AsyncFrameReader frameReader;
|
||||
|
||||
public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) {
|
||||
this.gl = gl;
|
||||
@ -109,6 +111,7 @@ public final class GLRenderer implements Renderer {
|
||||
this.glfbo = glfbo;
|
||||
this.glext = glext;
|
||||
this.texUtil = new TextureUtil(gl, gl2, glext);
|
||||
this.frameReader = new AsyncFrameReader(this, gl, glext, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -348,6 +351,10 @@ public final class GLRenderer implements Renderer {
|
||||
} else if (hasExtension("GL_OES_compressed_ETC1_RGB8_texture")) {
|
||||
caps.add(Caps.TextureCompressionETC1);
|
||||
}
|
||||
|
||||
if (hasExtension("GL_ARB_texture_compression_rgtc")) {
|
||||
caps.add(Caps.TextureCompressionRGTC);
|
||||
}
|
||||
|
||||
// == end texture format extensions ==
|
||||
|
||||
@ -379,7 +386,7 @@ public final class GLRenderer implements Renderer {
|
||||
limits.put(Limits.TextureAnisotropy, getInteger(GLExt.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT));
|
||||
}
|
||||
|
||||
if (hasExtension("GL_EXT_framebuffer_object")
|
||||
if (hasExtension("GL_EXT_framebuffer_object")
|
||||
|| caps.contains(Caps.OpenGL30)
|
||||
|| caps.contains(Caps.OpenGLES20)) {
|
||||
caps.add(Caps.FrameBuffer);
|
||||
@ -474,7 +481,6 @@ public final class GLRenderer implements Renderer {
|
||||
{
|
||||
sb.append("\t").append(cap.toString()).append("\n");
|
||||
}
|
||||
|
||||
sb.append("\nHardware limits: \n");
|
||||
for (Limits limit : Limits.values()) {
|
||||
Integer value = limits.get(limit);
|
||||
@ -484,7 +490,7 @@ public final class GLRenderer implements Renderer {
|
||||
sb.append("\t").append(limit.name()).append(" = ")
|
||||
.append(value).append("\n");
|
||||
}
|
||||
|
||||
|
||||
logger.log(Level.FINE, sb.toString());
|
||||
}
|
||||
|
||||
@ -525,9 +531,9 @@ public final class GLRenderer implements Renderer {
|
||||
|
||||
if (caps.contains(Caps.CoreProfile)) {
|
||||
// Core Profile requires VAO to be bound.
|
||||
gl3.glGenVertexArrays(intBuf16);
|
||||
int vaoId = intBuf16.get(0);
|
||||
gl3.glBindVertexArray(vaoId);
|
||||
// gl3.glGenVertexArrays(intBuf16);
|
||||
// int vaoId = intBuf16.get(0);
|
||||
// gl3.glBindVertexArray(vaoId);
|
||||
}
|
||||
if (gl2 != null) {
|
||||
gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE);
|
||||
@ -883,6 +889,7 @@ public final class GLRenderer implements Renderer {
|
||||
|
||||
public void postFrame() {
|
||||
objManager.deleteUnused(this);
|
||||
frameReader.updateReadRequests();
|
||||
gl.resetStats();
|
||||
}
|
||||
|
||||
@ -1524,15 +1531,15 @@ public final class GLRenderer implements Renderer {
|
||||
|
||||
bindFrameBuffer(fb);
|
||||
|
||||
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
|
||||
FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
|
||||
updateFrameBufferAttachment(fb, colorBuf);
|
||||
}
|
||||
FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer();
|
||||
if (depthBuf != null) {
|
||||
updateFrameBufferAttachment(fb, depthBuf);
|
||||
}
|
||||
|
||||
for (int i = 0; i < fb.getNumColorBuffers(); i++) {
|
||||
FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i);
|
||||
updateFrameBufferAttachment(fb, colorBuf);
|
||||
}
|
||||
|
||||
setReadDrawBuffers(fb);
|
||||
checkFrameBufferError();
|
||||
@ -1697,11 +1704,11 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
|
||||
readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE);
|
||||
public Future<ByteBuffer> readFrameBufferLater(FrameBuffer fb, ByteBuffer byteBuf) {
|
||||
return frameReader.readFrameBufferLater(fb, byteBuf);
|
||||
}
|
||||
|
||||
private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) {
|
||||
void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType, int pboId) {
|
||||
if (fb != null) {
|
||||
RenderBuffer rb = fb.getColorBuffer();
|
||||
if (rb == null) {
|
||||
@ -1720,12 +1727,30 @@ public final class GLRenderer implements Renderer {
|
||||
setFrameBuffer(null);
|
||||
}
|
||||
|
||||
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf);
|
||||
if (context.boundPixelPackPBO != pboId) {
|
||||
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, pboId);
|
||||
context.boundPixelPackPBO = pboId;
|
||||
}
|
||||
|
||||
if (byteBuf == null) {
|
||||
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, 0);
|
||||
} else {
|
||||
gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf);
|
||||
}
|
||||
|
||||
if (context.boundPixelPackPBO != 0) {
|
||||
gl.glBindBuffer(GLExt.GL_PIXEL_PACK_BUFFER_ARB, 0);
|
||||
context.boundPixelPackPBO = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {
|
||||
GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false);
|
||||
readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType);
|
||||
readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType, 0);
|
||||
}
|
||||
|
||||
public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
|
||||
readFrameBufferWithFormat(fb, byteBuf, Image.Format.RGBA8);
|
||||
}
|
||||
|
||||
private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) {
|
||||
@ -2346,32 +2371,37 @@ public final class GLRenderer implements Renderer {
|
||||
context.attribIndexList.copyNewToOld();
|
||||
}
|
||||
|
||||
public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {
|
||||
if (vb.getBufferType() == VertexBuffer.Type.Index) {
|
||||
throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
|
||||
}
|
||||
|
||||
if (context.boundShaderProgram <= 0) {
|
||||
throw new IllegalStateException("Cannot render mesh without shader bound");
|
||||
}
|
||||
|
||||
Attribute attrib = context.boundShader.getAttribute(vb.getBufferType());
|
||||
private int updateAttributeLocation(Shader shader, VertexBuffer.Type attribType) {
|
||||
Attribute attrib = shader.getAttribute(attribType);
|
||||
int loc = attrib.getLocation();
|
||||
if (loc == -1) {
|
||||
return; // not defined
|
||||
return -1; // not defined
|
||||
}
|
||||
if (loc == -2) {
|
||||
loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + vb.getBufferType().name());
|
||||
loc = gl.glGetAttribLocation(context.boundShaderProgram, "in" + attribType.name());
|
||||
|
||||
// not really the name of it in the shader (inPosition) but
|
||||
// the internal name of the enum (Position).
|
||||
if (loc < 0) {
|
||||
attrib.setLocation(-1);
|
||||
return; // not available in shader.
|
||||
return -1; // not available in shader.
|
||||
} else {
|
||||
attrib.setLocation(loc);
|
||||
}
|
||||
}
|
||||
return loc;
|
||||
}
|
||||
|
||||
public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) {
|
||||
if (vb.getBufferType() == VertexBuffer.Type.Index) {
|
||||
throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
|
||||
}
|
||||
|
||||
Shader shader = context.boundShader;
|
||||
int location = updateAttributeLocation(shader, vb.getBufferType());
|
||||
if (location == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vb.isInstanced()) {
|
||||
if (!caps.contains(Caps.MeshInstancing)) {
|
||||
@ -2395,11 +2425,11 @@ public final class GLRenderer implements Renderer {
|
||||
|
||||
VertexBuffer[] attribs = context.boundAttribs;
|
||||
for (int i = 0; i < slotsRequired; i++) {
|
||||
if (!context.attribIndexList.moveToNew(loc + i)) {
|
||||
gl.glEnableVertexAttribArray(loc + i);
|
||||
if (!context.attribIndexList.moveToNew(location + i)) {
|
||||
gl.glEnableVertexAttribArray(location + i);
|
||||
}
|
||||
}
|
||||
if (attribs[loc] != vb) {
|
||||
if (attribs[location] != vb) {
|
||||
// NOTE: Use id from interleaved buffer if specified
|
||||
int bufId = idb != null ? idb.getId() : vb.getId();
|
||||
assert bufId != -1;
|
||||
@ -2412,12 +2442,12 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
|
||||
if (slotsRequired == 1) {
|
||||
gl.glVertexAttribPointer(loc,
|
||||
vb.getNumComponents(),
|
||||
convertFormat(vb.getFormat()),
|
||||
vb.isNormalized(),
|
||||
vb.getStride(),
|
||||
vb.getOffset());
|
||||
gl.glVertexAttribPointer(location,
|
||||
vb.getNumComponents(),
|
||||
convertFormat(vb.getFormat()),
|
||||
vb.isNormalized(),
|
||||
vb.getStride(),
|
||||
vb.getOffset());
|
||||
} else {
|
||||
for (int i = 0; i < slotsRequired; i++) {
|
||||
// The pointer maps the next 4 floats in the slot.
|
||||
@ -2428,17 +2458,17 @@ public final class GLRenderer implements Renderer {
|
||||
// P4: ____________XXXX____________XXXX
|
||||
// stride = 4 bytes in float * 4 floats in slot * num slots
|
||||
// offset = 4 bytes in float * 4 floats in slot * slot index
|
||||
gl.glVertexAttribPointer(loc + i,
|
||||
4,
|
||||
convertFormat(vb.getFormat()),
|
||||
vb.isNormalized(),
|
||||
4 * 4 * slotsRequired,
|
||||
4 * 4 * i);
|
||||
gl.glVertexAttribPointer(location + i,
|
||||
4,
|
||||
convertFormat(vb.getFormat()),
|
||||
vb.isNormalized(),
|
||||
4 * 4 * slotsRequired,
|
||||
4 * 4 * i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < slotsRequired; i++) {
|
||||
int slot = loc + i;
|
||||
int slot = location + i;
|
||||
if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
|
||||
// non-instanced -> instanced
|
||||
glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
|
||||
@ -2451,13 +2481,97 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set VBO on VAO. Assumes a brand new mesh or modified mesh with new buffer.
|
||||
*
|
||||
* @param vb
|
||||
* @param idb
|
||||
*/
|
||||
public void setVertexAttribVAO(VertexBuffer vb, VertexBuffer idb) {
|
||||
if (vb.getBufferType() == VertexBuffer.Type.Index) {
|
||||
throw new IllegalArgumentException("Index buffers not allowed to be set to vertex attrib");
|
||||
}
|
||||
|
||||
Shader shader = context.boundShader;
|
||||
int location = updateAttributeLocation(shader, vb.getBufferType());
|
||||
if (location == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vb.isInstanced()) {
|
||||
if (!caps.contains(Caps.MeshInstancing)) {
|
||||
throw new RendererException("Instancing is required, "
|
||||
+ "but not supported by the "
|
||||
+ "graphics hardware");
|
||||
}
|
||||
}
|
||||
int slotsRequired = 1;
|
||||
if (vb.getNumComponents() > 4) {
|
||||
if (vb.getNumComponents() % 4 != 0) {
|
||||
throw new RendererException("Number of components in multi-slot "
|
||||
+ "buffers must be divisible by 4");
|
||||
}
|
||||
slotsRequired = vb.getNumComponents() / 4;
|
||||
}
|
||||
|
||||
if (vb.isUpdateNeeded() && idb == null) {
|
||||
updateBufferData(vb);
|
||||
}
|
||||
|
||||
for (int i = 0; i < slotsRequired; i++) {
|
||||
gl.glEnableVertexAttribArray(location + i);
|
||||
}
|
||||
|
||||
// NOTE: Use id from interleaved buffer if specified
|
||||
int bufId = idb != null ? idb.getId() : vb.getId();
|
||||
assert bufId != -1;
|
||||
if (context.boundArrayVBO != bufId) {
|
||||
gl.glBindBuffer(GL.GL_ARRAY_BUFFER, bufId);
|
||||
context.boundArrayVBO = bufId;
|
||||
//statistics.onVertexBufferUse(vb, true);
|
||||
} else {
|
||||
//statistics.onVertexBufferUse(vb, false);
|
||||
}
|
||||
|
||||
if (slotsRequired == 1) {
|
||||
gl.glVertexAttribPointer(location,
|
||||
vb.getNumComponents(),
|
||||
convertFormat(vb.getFormat()),
|
||||
vb.isNormalized(),
|
||||
vb.getStride(),
|
||||
vb.getOffset());
|
||||
} else {
|
||||
for (int i = 0; i < slotsRequired; i++) {
|
||||
// The pointer maps the next 4 floats in the slot.
|
||||
// E.g.
|
||||
// P1: XXXX____________XXXX____________
|
||||
// P2: ____XXXX____________XXXX________
|
||||
// P3: ________XXXX____________XXXX____
|
||||
// P4: ____________XXXX____________XXXX
|
||||
// stride = 4 bytes in float * 4 floats in slot * num slots
|
||||
// offset = 4 bytes in float * 4 floats in slot * slot index
|
||||
gl.glVertexAttribPointer(location + i,
|
||||
4,
|
||||
convertFormat(vb.getFormat()),
|
||||
vb.isNormalized(),
|
||||
4 * 4 * slotsRequired,
|
||||
4 * 4 * i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < slotsRequired; i++) {
|
||||
int slot = location + i;
|
||||
if (vb.isInstanced()) {
|
||||
glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan());
|
||||
}
|
||||
}
|
||||
}
|
||||
public void setVertexAttrib(VertexBuffer vb) {
|
||||
setVertexAttrib(vb, null);
|
||||
}
|
||||
|
||||
public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
|
||||
boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
|
||||
if (useInstancing) {
|
||||
if (count > 1) {
|
||||
glext.glDrawArraysInstancedARB(convertElementMode(mode), 0,
|
||||
vertCount, count);
|
||||
} else {
|
||||
@ -2503,57 +2617,19 @@ public final class GLRenderer implements Renderer {
|
||||
int vertCount = mesh.getVertexCount();
|
||||
boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
|
||||
|
||||
if (mesh.getMode() == Mode.Hybrid) {
|
||||
int[] modeStart = mesh.getModeStart();
|
||||
int[] elementLengths = mesh.getElementLengths();
|
||||
|
||||
int elMode = convertElementMode(Mode.Triangles);
|
||||
int fmt = convertFormat(indexBuf.getFormat());
|
||||
int elSize = indexBuf.getFormat().getComponentSize();
|
||||
int listStart = modeStart[0];
|
||||
int stripStart = modeStart[1];
|
||||
int fanStart = modeStart[2];
|
||||
int curOffset = 0;
|
||||
for (int i = 0; i < elementLengths.length; i++) {
|
||||
if (i == stripStart) {
|
||||
elMode = convertElementMode(Mode.TriangleStrip);
|
||||
} else if (i == fanStart) {
|
||||
elMode = convertElementMode(Mode.TriangleFan);
|
||||
}
|
||||
int elementLength = elementLengths[i];
|
||||
|
||||
if (useInstancing) {
|
||||
glext.glDrawElementsInstancedARB(elMode,
|
||||
elementLength,
|
||||
fmt,
|
||||
curOffset,
|
||||
count);
|
||||
} else {
|
||||
gl.glDrawRangeElements(elMode,
|
||||
0,
|
||||
vertCount,
|
||||
elementLength,
|
||||
fmt,
|
||||
curOffset);
|
||||
}
|
||||
|
||||
curOffset += elementLength * elSize;
|
||||
}
|
||||
if (useInstancing) {
|
||||
glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
|
||||
indexBuf.getData().limit(),
|
||||
convertFormat(indexBuf.getFormat()),
|
||||
0,
|
||||
count);
|
||||
} else {
|
||||
if (useInstancing) {
|
||||
glext.glDrawElementsInstancedARB(convertElementMode(mesh.getMode()),
|
||||
indexBuf.getData().limit(),
|
||||
convertFormat(indexBuf.getFormat()),
|
||||
0,
|
||||
count);
|
||||
} else {
|
||||
gl.glDrawRangeElements(convertElementMode(mesh.getMode()),
|
||||
0,
|
||||
vertCount,
|
||||
indexBuf.getData().limit(),
|
||||
convertFormat(indexBuf.getFormat()),
|
||||
0);
|
||||
}
|
||||
gl.glDrawRangeElements(convertElementMode(mesh.getMode()),
|
||||
0,
|
||||
vertCount,
|
||||
indexBuf.getData().limit(),
|
||||
convertFormat(indexBuf.getFormat()),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2583,91 +2659,12 @@ public final class GLRenderer implements Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) {
|
||||
int id = mesh.getId();
|
||||
if (id == -1) {
|
||||
IntBuffer temp = intBuf1;
|
||||
gl3.glGenVertexArrays(temp);
|
||||
id = temp.get(0);
|
||||
mesh.setId(id);
|
||||
}
|
||||
|
||||
if (context.boundVertexArray != id) {
|
||||
gl3.glBindVertexArray(id);
|
||||
context.boundVertexArray = id;
|
||||
}
|
||||
|
||||
private void setupVertexBuffersLegacy(Mesh mesh, VertexBuffer[] instanceData) {
|
||||
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
|
||||
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
|
||||
updateBufferData(interleavedData);
|
||||
}
|
||||
|
||||
if (instanceData != null) {
|
||||
setVertexAttrib(instanceData, null);
|
||||
}
|
||||
|
||||
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
|
||||
if (vb.getBufferType() == Type.InterleavedData
|
||||
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|
||||
|| vb.getBufferType() == Type.Index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vb.getStride() == 0) {
|
||||
// not interleaved
|
||||
setVertexAttrib(vb);
|
||||
} else {
|
||||
// interleaved
|
||||
setVertexAttrib(vb, interleavedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) {
|
||||
if (mesh.getId() == -1) {
|
||||
updateVertexArray(mesh, instanceData);
|
||||
} else {
|
||||
// TODO: Check if it was updated
|
||||
}
|
||||
|
||||
if (context.boundVertexArray != mesh.getId()) {
|
||||
gl3.glBindVertexArray(mesh.getId());
|
||||
context.boundVertexArray = mesh.getId();
|
||||
}
|
||||
|
||||
// IntMap<VertexBuffer> buffers = mesh.getBuffers();
|
||||
VertexBuffer indices;
|
||||
if (mesh.getNumLodLevels() > 0) {
|
||||
indices = mesh.getLodLevel(lod);
|
||||
} else {
|
||||
indices = mesh.getBuffer(Type.Index);
|
||||
}
|
||||
if (indices != null) {
|
||||
drawTriangleList(indices, mesh, count);
|
||||
} else {
|
||||
drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
|
||||
}
|
||||
clearVertexAttribs();
|
||||
}
|
||||
|
||||
private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
|
||||
|
||||
// Here while count is still passed in. Can be removed when/if
|
||||
// the method is collapsed again. -pspeed
|
||||
count = Math.max(mesh.getInstanceCount(), count);
|
||||
|
||||
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
|
||||
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
|
||||
updateBufferData(interleavedData);
|
||||
}
|
||||
|
||||
VertexBuffer indices;
|
||||
if (mesh.getNumLodLevels() > 0) {
|
||||
indices = mesh.getLodLevel(lod);
|
||||
} else {
|
||||
indices = mesh.getBuffer(Type.Index);
|
||||
}
|
||||
|
||||
if (instanceData != null) {
|
||||
for (VertexBuffer vb : instanceData) {
|
||||
setVertexAttrib(vb, null);
|
||||
@ -2689,9 +2686,136 @@ public final class GLRenderer implements Renderer {
|
||||
setVertexAttrib(vb, interleavedData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
|
||||
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
|
||||
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
|
||||
updateBufferData(interleavedData);
|
||||
}
|
||||
|
||||
if (instanceData != null) {
|
||||
for (VertexBuffer vb : instanceData) {
|
||||
setVertexAttribVAO(vb, null);
|
||||
}
|
||||
}
|
||||
|
||||
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
|
||||
if (vb.getBufferType() == Type.InterleavedData
|
||||
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|
||||
|| vb.getBufferType() == Type.Index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (vb.getStride() == 0) {
|
||||
// not interleaved
|
||||
setVertexAttribVAO(vb, null);
|
||||
} else {
|
||||
// interleaved
|
||||
setVertexAttribVAO(vb, interleavedData);
|
||||
}
|
||||
}
|
||||
mesh.clearUpdateNeeded();
|
||||
}
|
||||
|
||||
private void updateVertexBuffers(Mesh mesh, VertexBuffer[] instanceData) {
|
||||
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
|
||||
|
||||
if (instanceData != null) {
|
||||
for (VertexBuffer vb : instanceData) {
|
||||
if (vb.isUpdateNeeded()) {
|
||||
updateBufferData(vb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (interleavedData != null) {
|
||||
if (interleavedData.isUpdateNeeded()) {
|
||||
updateBufferData(interleavedData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
|
||||
if (vb.getBufferType() == Type.InterleavedData
|
||||
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
|
||||
|| vb.getBufferType() == Type.Index
|
||||
|| !vb.isUpdateNeeded()
|
||||
|| !context.boundShader.isAttributeDefined(vb.getBufferType())) {
|
||||
continue;
|
||||
}
|
||||
updateBufferData(vb);
|
||||
}
|
||||
}
|
||||
|
||||
private VertexBuffer getIndexBuffer(Mesh mesh, int lod) {
|
||||
VertexBuffer indices;
|
||||
if (mesh.getNumLodLevels() > 0) {
|
||||
indices = mesh.getLodLevel(lod);
|
||||
} else {
|
||||
indices = mesh.getBuffer(Type.Index);
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
private void setVertexArrayObject(Mesh mesh) {
|
||||
int id = mesh.getId();
|
||||
|
||||
if (id == -1) {
|
||||
IntBuffer temp = intBuf1;
|
||||
gl3.glGenVertexArrays(temp);
|
||||
id = temp.get(0);
|
||||
mesh.setId(id);
|
||||
|
||||
objManager.registerObject(mesh);
|
||||
}
|
||||
|
||||
if (context.boundVertexArray != id) {
|
||||
gl3.glBindVertexArray(id);
|
||||
context.boundVertexArray = id;
|
||||
}
|
||||
}
|
||||
|
||||
private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
|
||||
setVertexArrayObject(mesh);
|
||||
|
||||
// VAO clears current bound VBO automatically
|
||||
context.boundElementArrayVBO = 0;
|
||||
context.boundArrayVBO = 0;
|
||||
|
||||
VertexBuffer indices = getIndexBuffer(mesh, lod);
|
||||
if (mesh.isUpdateNeeded()) {
|
||||
setupVertexBuffers(mesh, instanceData);
|
||||
if (indices != null) {
|
||||
updateBufferData(indices);
|
||||
}
|
||||
} else {
|
||||
updateVertexBuffers(mesh, instanceData);
|
||||
if (indices != null) {
|
||||
// NOTE: context.boundElementArrayVBO gets captured in the VAO.
|
||||
// Make everyone think its already bound.
|
||||
context.boundElementArrayVBO = indices.getId();
|
||||
}
|
||||
}
|
||||
|
||||
if (indices != null) {
|
||||
if (indices.isUpdateNeeded()) {
|
||||
updateBufferData(indices);
|
||||
}
|
||||
|
||||
drawTriangleList(indices, mesh, count);
|
||||
|
||||
context.boundElementArrayVBO = 0;
|
||||
} else {
|
||||
drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount());
|
||||
}
|
||||
}
|
||||
|
||||
private void renderMeshLegacy(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
|
||||
setupVertexBuffersLegacy(mesh, instanceData);
|
||||
VertexBuffer indices = getIndexBuffer(mesh, lod);
|
||||
|
||||
clearVertexAttribs();
|
||||
|
||||
if (indices != null) {
|
||||
drawTriangleList(indices, mesh, count);
|
||||
} else {
|
||||
@ -2707,7 +2831,7 @@ public final class GLRenderer implements Renderer {
|
||||
if (count > 1 && !caps.contains(Caps.MeshInstancing)) {
|
||||
throw new RendererException("Mesh instancing is not supported by the video hardware");
|
||||
}
|
||||
|
||||
//this is kept for backward compatibility.
|
||||
if (mesh.getLineWidth() != 1f && context.lineWidth != mesh.getLineWidth()) {
|
||||
gl.glLineWidth(mesh.getLineWidth());
|
||||
context.lineWidth = mesh.getLineWidth();
|
||||
@ -2717,11 +2841,27 @@ public final class GLRenderer implements Renderer {
|
||||
gl4.glPatchParameter(mesh.getPatchVertexCount());
|
||||
}
|
||||
statistics.onMeshDrawn(mesh, lod, count);
|
||||
// if (ctxCaps.GL_ARB_vertex_array_object){
|
||||
// renderMeshVertexArray(mesh, lod, count);
|
||||
// }else{
|
||||
renderMeshDefault(mesh, lod, count, instanceData);
|
||||
// }
|
||||
// Here while count is still passed in. Can be removed when/if
|
||||
// the method is collapsed again. -pspeed
|
||||
count = Math.max(mesh.getInstanceCount(), count);
|
||||
|
||||
if (caps.contains(Caps.VertexBufferArray)) {
|
||||
renderMeshDefault(mesh, lod, count, instanceData);
|
||||
} else {
|
||||
renderMeshLegacy(mesh, lod, count, instanceData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMesh(Mesh mesh) {
|
||||
int bufId = mesh.getId();
|
||||
if (bufId != -1) {
|
||||
// delete vertex array object
|
||||
intBuf1.put(0, bufId);
|
||||
intBuf1.position(0).limit(1);
|
||||
gl3.glDeleteVertexArrays(intBuf1);
|
||||
mesh.resetObject();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainFrameBufferSrgb(boolean enableSrgb) {
|
||||
|
@ -43,6 +43,7 @@ import com.jme3.math.Matrix4f;
|
||||
import com.jme3.math.Triangle;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Renderer;
|
||||
import com.jme3.scene.VertexBuffer.Format;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.VertexBuffer.Usage;
|
||||
@ -50,6 +51,7 @@ import com.jme3.scene.mesh.*;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.IntMap;
|
||||
import com.jme3.util.IntMap.Entry;
|
||||
import com.jme3.util.NativeObject;
|
||||
import com.jme3.util.SafeArrayList;
|
||||
import com.jme3.util.clone.Cloner;
|
||||
import com.jme3.util.clone.JmeCloneable;
|
||||
@ -74,7 +76,7 @@ import java.util.ArrayList;
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
public class Mesh extends NativeObject implements Savable, Cloneable, JmeCloneable {
|
||||
|
||||
/**
|
||||
* The mode of the Mesh specifies both the type of primitive represented
|
||||
@ -129,15 +131,9 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* with the very first vertex to make a triangle.
|
||||
*/
|
||||
TriangleFan(false),
|
||||
|
||||
/**
|
||||
* A combination of various triangle modes. It is best to avoid
|
||||
* using this mode as it may not be supported by all renderers.
|
||||
* The {@link Mesh#setModeStart(int[]) mode start points} and
|
||||
* {@link Mesh#setElementLengths(int[]) element lengths} must
|
||||
* be specified for this mode.
|
||||
*/
|
||||
Hybrid(false),
|
||||
|
||||
Reserved(false),
|
||||
|
||||
/**
|
||||
* Used for Tesselation only. Requires to set the number of vertices
|
||||
* for each patch (default is 3 for triangle tesselation)
|
||||
@ -185,9 +181,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
private int patchVertexCount=3; //only used for tesselation
|
||||
private int maxNumWeights = -1; // only if using skeletal animation
|
||||
|
||||
private int[] elementLengths;
|
||||
private int[] modeStart;
|
||||
|
||||
private Mode mode = Mode.Triangles;
|
||||
|
||||
/**
|
||||
@ -196,6 +189,10 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
public Mesh(){
|
||||
}
|
||||
|
||||
protected Mesh(int id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
|
||||
* buffers} are shared between this and the clone mesh, the rest
|
||||
@ -205,23 +202,12 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
@Override
|
||||
public Mesh clone() {
|
||||
try {
|
||||
Mesh clone = (Mesh) super.clone();
|
||||
clone.meshBound = meshBound.clone();
|
||||
clone.collisionTree = collisionTree != null ? collisionTree : null;
|
||||
clone.buffers = buffers.clone();
|
||||
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
|
||||
clone.vertexArrayID = -1;
|
||||
if (elementLengths != null) {
|
||||
clone.elementLengths = elementLengths.clone();
|
||||
}
|
||||
if (modeStart != null) {
|
||||
clone.modeStart = modeStart.clone();
|
||||
}
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
Mesh clone = (Mesh) super.clone();
|
||||
clone.meshBound = meshBound.clone();
|
||||
clone.collisionTree = collisionTree != null ? collisionTree : null;
|
||||
clone.buffers = buffers.clone();
|
||||
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -232,37 +218,30 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* @return a deep clone of this mesh.
|
||||
*/
|
||||
public Mesh deepClone(){
|
||||
try{
|
||||
Mesh clone = (Mesh) super.clone();
|
||||
clone.meshBound = meshBound != null ? meshBound.clone() : null;
|
||||
Mesh clone = (Mesh) super.clone();
|
||||
clone.meshBound = meshBound != null ? meshBound.clone() : null;
|
||||
|
||||
// TODO: Collision tree cloning
|
||||
//clone.collisionTree = collisionTree != null ? collisionTree : null;
|
||||
clone.collisionTree = null; // it will get re-generated in any case
|
||||
// TODO: Collision tree cloning
|
||||
//clone.collisionTree = collisionTree != null ? collisionTree : null;
|
||||
clone.collisionTree = null; // it will get re-generated in any case
|
||||
|
||||
clone.buffers = new IntMap<VertexBuffer>();
|
||||
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
|
||||
for (VertexBuffer vb : buffersList.getArray()){
|
||||
VertexBuffer bufClone = vb.clone();
|
||||
clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
|
||||
clone.buffersList.add(bufClone);
|
||||
}
|
||||
|
||||
clone.vertexArrayID = -1;
|
||||
clone.vertCount = vertCount;
|
||||
clone.elementCount = elementCount;
|
||||
clone.instanceCount = instanceCount;
|
||||
|
||||
// although this could change
|
||||
// if the bone weight/index buffers are modified
|
||||
clone.maxNumWeights = maxNumWeights;
|
||||
|
||||
clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
|
||||
clone.modeStart = modeStart != null ? modeStart.clone() : null;
|
||||
return clone;
|
||||
}catch (CloneNotSupportedException ex){
|
||||
throw new AssertionError();
|
||||
clone.buffers = new IntMap<VertexBuffer>();
|
||||
clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
|
||||
for (VertexBuffer vb : buffersList.getArray()){
|
||||
VertexBuffer bufClone = vb.clone();
|
||||
clone.buffers.put(vb.getBufferType().ordinal(), bufClone);
|
||||
clone.buffersList.add(bufClone);
|
||||
}
|
||||
|
||||
clone.vertCount = vertCount;
|
||||
clone.elementCount = elementCount;
|
||||
clone.instanceCount = instanceCount;
|
||||
|
||||
// although this could change
|
||||
// if the bone weight/index buffers are modified
|
||||
clone.maxNumWeights = maxNumWeights;
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,17 +281,11 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
|
||||
* Called internally by com.jme3.util.clone.Cloner. Do not call directly.
|
||||
*/
|
||||
@Override
|
||||
public Mesh jmeClone() {
|
||||
try {
|
||||
Mesh clone = (Mesh)super.clone();
|
||||
clone.vertexArrayID = -1;
|
||||
return clone;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
return (Mesh) super.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -328,8 +301,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
this.buffersList = cloner.clone(buffersList);
|
||||
this.buffers = cloner.clone(buffers);
|
||||
this.lodLevels = cloner.clone(lodLevels);
|
||||
this.elementLengths = cloner.clone(elementLengths);
|
||||
this.modeStart = cloner.clone(modeStart);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,26 +365,31 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
*/
|
||||
public void prepareForAnim(boolean forSoftwareAnim){
|
||||
if (forSoftwareAnim) {
|
||||
// convert indices to ubytes on the heap
|
||||
VertexBuffer indices = getBuffer(Type.BoneIndex);
|
||||
if (!indices.getData().hasArray()) {
|
||||
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
|
||||
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
|
||||
originalIndex.clear();
|
||||
arrayIndex.put(originalIndex);
|
||||
indices.updateData(arrayIndex);
|
||||
}
|
||||
indices.setUsage(Usage.CpuOnly);
|
||||
// convert indices to ubytes on the heap
|
||||
VertexBuffer indices = getBuffer(Type.BoneIndex);
|
||||
if (!indices.getData().hasArray()) {
|
||||
ByteBuffer originalIndex = (ByteBuffer) indices.getData();
|
||||
ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
|
||||
originalIndex.clear();
|
||||
arrayIndex.put(originalIndex);
|
||||
indices.updateData(arrayIndex);
|
||||
}
|
||||
indices.setUsage(Usage.CpuOnly);
|
||||
|
||||
// convert weights on the heap
|
||||
VertexBuffer weights = getBuffer(Type.BoneWeight);
|
||||
// convert weights on the heap
|
||||
VertexBuffer weights = getBuffer(Type.BoneWeight);
|
||||
if (!weights.getData().hasArray()) {
|
||||
FloatBuffer originalWeight = (FloatBuffer) weights.getData();
|
||||
FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
|
||||
originalWeight.clear();
|
||||
arrayWeight.put(originalWeight);
|
||||
weights.updateData(arrayWeight);
|
||||
}
|
||||
if (weights.getFormat() == Format.Float) {
|
||||
FloatBuffer originalWeight = (FloatBuffer) weights.getData();
|
||||
FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
|
||||
originalWeight.clear();
|
||||
arrayWeight.put(originalWeight);
|
||||
weights.updateData(arrayWeight);
|
||||
} else {
|
||||
// UByte to Float conversion
|
||||
throw new UnsupportedOperationException("Not yet supported");
|
||||
}
|
||||
}
|
||||
weights.setUsage(Usage.CpuOnly);
|
||||
// position, normal, and tanget buffers to be in "Stream" mode
|
||||
VertexBuffer positions = getBuffer(Type.Position);
|
||||
@ -474,6 +450,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
tangents.setUpdateNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
this.setUpdateNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -508,41 +486,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
public VertexBuffer getLodLevel(int lod){
|
||||
return lodLevels[lod];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element lengths for {@link Mode#Hybrid} mesh mode.
|
||||
*
|
||||
* @return element lengths
|
||||
*/
|
||||
public int[] getElementLengths() {
|
||||
return elementLengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the element lengths for {@link Mode#Hybrid} mesh mode.
|
||||
*
|
||||
* @param elementLengths The element lengths to set
|
||||
*/
|
||||
public void setElementLengths(int[] elementLengths) {
|
||||
this.elementLengths = elementLengths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode start indices for {@link Mode#Hybrid} mesh mode.
|
||||
*
|
||||
* @return mode start indices
|
||||
*/
|
||||
public int[] getModeStart() {
|
||||
return modeStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mode start indices for {@link Mode#Hybrid} mesh mode.
|
||||
*/
|
||||
public void setModeStart(int[] modeStart) {
|
||||
this.modeStart = modeStart;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mesh mode
|
||||
*
|
||||
@ -803,19 +747,27 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* {@link #setInterleaved() interleaved} format.
|
||||
*/
|
||||
public void updateCounts(){
|
||||
if (getBuffer(Type.InterleavedData) != null)
|
||||
throw new IllegalStateException("Should update counts before interleave");
|
||||
// if (getBuffer(Type.InterleavedData) != null) {
|
||||
// throw new IllegalStateException("Should update counts before interleave");
|
||||
// }
|
||||
|
||||
VertexBuffer pb = getBuffer(Type.Position);
|
||||
VertexBuffer ib = getBuffer(Type.Index);
|
||||
if (pb != null){
|
||||
vertCount = pb.getData().limit() / pb.getNumComponents();
|
||||
if (pb != null) {
|
||||
VertexBuffer ip = getBuffer(Type.InterleavedData);
|
||||
if (ip != null) {
|
||||
int limitBytes = ip.getData().limit();
|
||||
int elementSizeWithOthers = pb.getStride();
|
||||
vertCount = limitBytes / elementSizeWithOthers;
|
||||
} else {
|
||||
vertCount = pb.getData().limit() / pb.getNumComponents();
|
||||
}
|
||||
}
|
||||
if (ib != null){
|
||||
elementCount = computeNumElements(ib.getData().limit());
|
||||
}else{
|
||||
elementCount = computeNumElements(vertCount);
|
||||
}
|
||||
}
|
||||
instanceCount = computeInstanceCount();
|
||||
}
|
||||
|
||||
@ -940,23 +892,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
indices[2] = ib.get(vertIndex+2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the mesh's VAO ID. Internal use only.
|
||||
*/
|
||||
public int getId(){
|
||||
return vertexArrayID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mesh's VAO ID. Internal use only.
|
||||
*/
|
||||
public void setId(int id){
|
||||
if (vertexArrayID != -1)
|
||||
throw new IllegalStateException("ID has already been set.");
|
||||
|
||||
vertexArrayID = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a collision tree for the mesh.
|
||||
* Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
|
||||
@ -1150,9 +1085,6 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
* @return A virtual or wrapped index buffer to read the data as a list
|
||||
*/
|
||||
public IndexBuffer getIndicesAsList(){
|
||||
if (mode == Mode.Hybrid)
|
||||
throw new UnsupportedOperationException("Hybrid mode not supported");
|
||||
|
||||
IndexBuffer ib = getIndexBuffer();
|
||||
if (ib != null){
|
||||
if (mode.isListMode()){
|
||||
@ -1426,16 +1358,30 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
return patchVertexCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetObject() {
|
||||
id = -1;
|
||||
setUpdateNeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteObject(Object rendererObject) {
|
||||
((Renderer)rendererObject).deleteMesh(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NativeObject createDestructableClone() {
|
||||
return new Mesh(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getUniqueId() {
|
||||
return ((long)OBJTYPE_MESH << 32) | ((long)id);
|
||||
}
|
||||
|
||||
public void write(JmeExporter ex) throws IOException {
|
||||
OutputCapsule out = ex.getCapsule(this);
|
||||
|
||||
// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
|
||||
// for (Entry<VertexBuffer> buf : buffers){
|
||||
// if (buf.getValue() != null)
|
||||
// map.put(buf.getKey()+"a", buf.getValue());
|
||||
// }
|
||||
// out.writeStringSavableMap(map, "buffers", null);
|
||||
|
||||
out.write(meshBound, "modelBound", null);
|
||||
out.write(vertCount, "vertCount", -1);
|
||||
out.write(elementCount, "elementCount", -1);
|
||||
@ -1443,10 +1389,8 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
out.write(maxNumWeights, "max_num_weights", -1);
|
||||
out.write(mode, "mode", Mode.Triangles);
|
||||
out.write(collisionTree, "collisionTree", null);
|
||||
out.write(elementLengths, "elementLengths", null);
|
||||
out.write(modeStart, "modeStart", null);
|
||||
out.write(pointSize, "pointSize", 1f);
|
||||
|
||||
|
||||
//Removing HW skinning buffers to not save them
|
||||
VertexBuffer hwBoneIndex = null;
|
||||
VertexBuffer hwBoneWeight = null;
|
||||
@ -1480,19 +1424,14 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
instanceCount = in.readInt("instanceCount", -1);
|
||||
maxNumWeights = in.readInt("max_num_weights", -1);
|
||||
mode = in.readEnum("mode", Mode.class, Mode.Triangles);
|
||||
elementLengths = in.readIntArray("elementLengths", null);
|
||||
modeStart = in.readIntArray("modeStart", null);
|
||||
collisionTree = (BIHTree) in.readSavable("collisionTree", null);
|
||||
elementLengths = in.readIntArray("elementLengths", null);
|
||||
modeStart = in.readIntArray("modeStart", null);
|
||||
pointSize = in.readFloat("pointSize", 1f);
|
||||
|
||||
// in.readStringSavableMap("buffers", null);
|
||||
buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
|
||||
for (Entry<VertexBuffer> entry : buffers){
|
||||
buffersList.add(entry.getValue());
|
||||
}
|
||||
|
||||
|
||||
//creating hw animation buffers empty so that they are put in the cache
|
||||
if(isAnimated()){
|
||||
VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex);
|
||||
@ -1502,7 +1441,7 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
|
||||
hwBoneWeight.setUsage(Usage.CpuOnly);
|
||||
setBuffer(hwBoneWeight);
|
||||
}
|
||||
|
||||
|
||||
Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null);
|
||||
if (lodLevelsSavable != null) {
|
||||
lodLevels = new VertexBuffer[lodLevelsSavable.length];
|
||||
|
@ -75,6 +75,7 @@ public class Node extends Spatial {
|
||||
* requiresUpdate() method.
|
||||
*/
|
||||
private SafeArrayList<Spatial> updateList = null;
|
||||
|
||||
/**
|
||||
* False if the update list requires rebuilding. This is Node.class
|
||||
* specific and therefore not included as part of the Spatial update flags.
|
||||
@ -99,6 +100,7 @@ public class Node extends Spatial {
|
||||
*/
|
||||
public Node(String name) {
|
||||
super(name);
|
||||
|
||||
// For backwards compatibility, only clear the "requires
|
||||
// update" flag if we are not a subclass of Node.
|
||||
// This prevents subclass from silently failing to receive
|
||||
@ -154,6 +156,7 @@ public class Node extends Spatial {
|
||||
@Override
|
||||
protected void updateWorldBound(){
|
||||
super.updateWorldBound();
|
||||
|
||||
// for a node, the world bound is a combination of all it's children
|
||||
// bounds
|
||||
BoundingVolume resultBound = null;
|
||||
@ -248,6 +251,7 @@ public class Node extends Spatial {
|
||||
// This branch has no geometric state that requires updates.
|
||||
return;
|
||||
}
|
||||
|
||||
if ((refreshFlags & RF_LIGHTLIST) != 0){
|
||||
updateWorldLightList();
|
||||
}
|
||||
@ -262,6 +266,7 @@ public class Node extends Spatial {
|
||||
}
|
||||
|
||||
refreshFlags &= ~RF_CHILD_LIGHTLIST;
|
||||
|
||||
if (!children.isEmpty()) {
|
||||
// the important part- make sure child geometric state is refreshed
|
||||
// first before updating own world bound. This saves
|
||||
@ -297,6 +302,7 @@ public class Node extends Spatial {
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* <code>getVertexCount</code> returns the number of vertices contained
|
||||
* in all sub-branches of this node that contain geometry.
|
||||
@ -330,6 +336,7 @@ public class Node extends Spatial {
|
||||
public int attachChild(Spatial child) {
|
||||
return attachChildAt(child, children.size());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* <code>attachChildAt</code> attaches a child to this node at an index. This node
|
||||
@ -353,6 +360,7 @@ public class Node extends Spatial {
|
||||
}
|
||||
child.setParent(this);
|
||||
children.add(index, child);
|
||||
|
||||
// XXX: Not entirely correct? Forces bound update up the
|
||||
// tree stemming from the attached child. Also forces
|
||||
// transform update down the tree-
|
||||
@ -363,8 +371,10 @@ public class Node extends Spatial {
|
||||
logger.log(Level.FINE,"Child ({0}) attached to this node ({1})",
|
||||
new Object[]{child.getName(), getName()});
|
||||
}
|
||||
|
||||
invalidateUpdateList();
|
||||
}
|
||||
|
||||
return children.size();
|
||||
}
|
||||
|
||||
@ -439,6 +449,7 @@ public class Node extends Spatial {
|
||||
child.setTransformRefresh();
|
||||
// lights are also inherited from parent
|
||||
child.setLightListRefresh();
|
||||
|
||||
child.setMatParamOverrideRefresh();
|
||||
|
||||
invalidateUpdateList();
|
||||
@ -526,6 +537,7 @@ public class Node extends Spatial {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* determines if the provided Spatial is contained in the children list of
|
||||
* this node.
|
||||
@ -573,32 +585,39 @@ public class Node extends Spatial {
|
||||
|
||||
public int collideWith(Collidable other, CollisionResults results){
|
||||
int total = 0;
|
||||
|
||||
// optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children
|
||||
// number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all.
|
||||
// The idea is when there are few children, it can be too expensive to test boundingVolume first.
|
||||
/*
|
||||
I'm removing this change until some issues can be addressed and I really
|
||||
think it needs to be implemented a better way anyway.
|
||||
|
||||
First, it causes issues for anyone doing collideWith() with BoundingVolumes
|
||||
and expecting it to trickle down to the children. For example, children
|
||||
with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing
|
||||
a collision check at the parent level then has to do a BoundingSphere to BoundingBox
|
||||
collision which isn't resolved. (Having to come up with a collision point in that
|
||||
case is tricky and the first sign that this is the wrong approach.)
|
||||
|
||||
Second, the rippling changes this caused to 'optimize' collideWith() for this
|
||||
special use-case are another sign that this approach was a bit dodgy. The whole
|
||||
idea of calculating a full collision just to see if the two shapes collide at all
|
||||
is very wasteful.
|
||||
|
||||
A proper implementation should support a simpler boolean check that doesn't do
|
||||
all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9%
|
||||
of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much
|
||||
faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done.
|
||||
|
||||
I don't have time to do it right now but I'll at least un-break a bunch of peoples'
|
||||
code until it can be 'optimized' properly. Hopefully it's not too late to back out
|
||||
the other dodgy ripples this caused. -pspeed (hindsight-expert ;))
|
||||
|
||||
Note: the code itself is relatively simple to implement but I don't have time to
|
||||
a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast
|
||||
enough to do all the time for > 1.
|
||||
|
||||
if (children.size() > 4)
|
||||
{
|
||||
BoundingVolume bv = this.getWorldBound();
|
||||
@ -691,6 +710,7 @@ public class Node extends Spatial {
|
||||
// Reset the fields of the clone that should be in a 'new' state.
|
||||
nodeClone.updateList = null;
|
||||
nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone()
|
||||
|
||||
return nodeClone;
|
||||
}
|
||||
|
||||
@ -730,6 +750,7 @@ public class Node extends Spatial {
|
||||
// cloning this list is fine.
|
||||
this.updateList = cloner.clone(updateList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JmeExporter e) throws IOException {
|
||||
super.write(e);
|
||||
@ -741,6 +762,7 @@ public class Node extends Spatial {
|
||||
// XXX: Load children before loading itself!!
|
||||
// This prevents empty children list if controls query
|
||||
// it in Control.setSpatial().
|
||||
|
||||
children = new SafeArrayList( Spatial.class,
|
||||
e.getCapsule(this).readSavableArrayList("children", null) );
|
||||
|
||||
@ -750,6 +772,7 @@ public class Node extends Spatial {
|
||||
child.parent = this;
|
||||
}
|
||||
}
|
||||
|
||||
super.read(e);
|
||||
}
|
||||
|
||||
@ -770,6 +793,7 @@ public class Node extends Spatial {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthFirstTraversal(SceneGraphVisitor visitor) {
|
||||
for (Spatial child : children.getArray()) {
|
||||
@ -777,6 +801,7 @@ public class Node extends Spatial {
|
||||
}
|
||||
visitor.visit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
|
||||
queue.addAll(children);
|
||||
|
@ -122,10 +122,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
*/
|
||||
protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
|
||||
RF_BOUND = 0x02,
|
||||
RF_LIGHTLIST = 0x04, // changes in light lists
|
||||
RF_LIGHTLIST = 0x04, // changes in light lists
|
||||
RF_CHILD_LIGHTLIST = 0x08, // some child need geometry update
|
||||
RF_MATPARAM_OVERRIDE = 0x10;
|
||||
|
||||
|
||||
protected CullHint cullHint = CullHint.Inherit;
|
||||
protected BatchHint batchHint = BatchHint.Inherit;
|
||||
/**
|
||||
@ -137,11 +138,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
*/
|
||||
protected LightList localLights;
|
||||
protected transient LightList worldLights;
|
||||
|
||||
protected ArrayList<MatParamOverride> localOverrides;
|
||||
protected ArrayList<MatParamOverride> worldOverrides;
|
||||
|
||||
/**
|
||||
/**
|
||||
* This spatial's name.
|
||||
*/
|
||||
protected String name;
|
||||
@ -201,6 +200,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
*/
|
||||
protected Spatial(String name) {
|
||||
this.name = name;
|
||||
|
||||
localTransform = new Transform();
|
||||
worldTransform = new Transform();
|
||||
|
||||
@ -209,7 +209,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
|
||||
localOverrides = new ArrayList<MatParamOverride>();
|
||||
worldOverrides = new ArrayList<MatParamOverride>();
|
||||
|
||||
refreshFlags |= RF_BOUND;
|
||||
}
|
||||
|
||||
@ -230,6 +229,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
boolean requiresUpdates() {
|
||||
return requiresUpdates | !controls.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can call this with true to denote that they require
|
||||
* updateLogicalState() to be called even if they contain no controls.
|
||||
@ -279,15 +279,18 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
|
||||
protected void setLightListRefresh() {
|
||||
refreshFlags |= RF_LIGHTLIST;
|
||||
|
||||
// Make sure next updateGeometricState() visits this branch
|
||||
// to update lights.
|
||||
Spatial p = parent;
|
||||
while (p != null) {
|
||||
|
||||
if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) {
|
||||
// The parent already has this flag,
|
||||
// so must all ancestors.
|
||||
return;
|
||||
}
|
||||
|
||||
p.refreshFlags |= RF_CHILD_LIGHTLIST;
|
||||
p = p.parent;
|
||||
}
|
||||
@ -305,7 +308,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
p = p.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the bounding of this spatial has changed and that
|
||||
* a refresh is required.
|
||||
@ -323,6 +325,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
p = p.parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* (Internal use only) Forces a refresh of the given types of data.
|
||||
*
|
||||
@ -552,8 +555,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
TempVars vars = TempVars.get();
|
||||
|
||||
Vector3f compVecA = vars.vect4;
|
||||
|
||||
compVecA.set(position).subtractLocal(worldTranslation);
|
||||
getLocalRotation().lookAt(compVecA, upVector);
|
||||
|
||||
if ( getParent() != null ) {
|
||||
Quaternion rot=vars.quat1;
|
||||
rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation());
|
||||
@ -636,7 +641,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
}
|
||||
localOverrides.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called from updateGeometricState().
|
||||
* In most cases should not be subclassed.
|
||||
@ -769,6 +773,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
controls.add(control);
|
||||
control.setSpatial(this);
|
||||
boolean after = requiresUpdates();
|
||||
|
||||
// If the requirement to be updated has changed
|
||||
// then we need to let the parent node know so it
|
||||
// can rebuild its update list.
|
||||
@ -792,6 +797,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
}
|
||||
}
|
||||
boolean after = requiresUpdates();
|
||||
|
||||
// If the requirement to be updated has changed
|
||||
// then we need to let the parent node know so it
|
||||
// can rebuild its update list.
|
||||
@ -817,12 +823,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
}
|
||||
|
||||
boolean after = requiresUpdates();
|
||||
|
||||
// If the requirement to be updated has changed
|
||||
// then we need to let the parent node know so it
|
||||
// can rebuild its update list.
|
||||
if( parent != null && before != after ) {
|
||||
parent.invalidateUpdateList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -907,6 +915,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
if ((refreshFlags & RF_BOUND) != 0) {
|
||||
updateWorldBound();
|
||||
}
|
||||
|
||||
if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) {
|
||||
updateMatParamOverrides();
|
||||
}
|
||||
@ -1391,7 +1400,6 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
for (MatParamOverride override : localOverrides) {
|
||||
clone.localOverrides.add((MatParamOverride) override.clone());
|
||||
}
|
||||
|
||||
// No need to force cloned to update.
|
||||
// This node already has the refresh flags
|
||||
// set below so it will have to update anyway.
|
||||
@ -1469,6 +1477,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
// the transforms and stuff get refreshed.
|
||||
clone.setTransformRefresh();
|
||||
clone.setLightListRefresh();
|
||||
clone.setMatParamOverrideRefresh();
|
||||
|
||||
return clone;
|
||||
}
|
||||
@ -1621,12 +1630,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab
|
||||
|
||||
localLights = (LightList) ic.readSavable("lights", null);
|
||||
localLights.setOwner(this);
|
||||
|
||||
localOverrides = ic.readSavableArrayList("overrides", null);
|
||||
if (localOverrides == null) {
|
||||
localOverrides = new ArrayList<MatParamOverride>();
|
||||
localOverrides = new ArrayList<>();
|
||||
}
|
||||
worldOverrides = new ArrayList<MatParamOverride>();
|
||||
worldOverrides = new ArrayList<>();
|
||||
|
||||
//changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
|
||||
//the AnimControl creates the SkeletonControl for old files and add it to the spatial.
|
||||
|
@ -524,6 +524,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* The size of an element in bytes.
|
||||
*
|
||||
* The number of components multiplied by the size of a component.
|
||||
*
|
||||
* @return size of an element in bytes.
|
||||
*/
|
||||
public int getElementSize() {
|
||||
return componentsLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param normalized Set to true if integer components should be converted
|
||||
* from their maximal range into the range 0.0 - 1.0 when converted to
|
||||
@ -976,6 +987,10 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* of the parameters. The buffer will be of the type specified by
|
||||
* {@link Format format} and would be able to contain the given number
|
||||
* of elements with the given number of components in each element.
|
||||
* @param format The format of the buffer to create
|
||||
* @param components The number of components (aka dimensions)
|
||||
* @param numElements Capacity of the buffer in number of elements.
|
||||
* @return A buffer satisfying the given requirements.
|
||||
*/
|
||||
public static Buffer createBuffer(Format format, int components, int numElements){
|
||||
if (components < 1 || components > 4)
|
||||
@ -1010,7 +1025,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
|
||||
* @return Deep clone of this buffer
|
||||
*/
|
||||
@Override
|
||||
public VertexBuffer clone(){
|
||||
public VertexBuffer clone() {
|
||||
// NOTE: Superclass GLObject automatically creates shallow clone
|
||||
// e.g re-use ID.
|
||||
VertexBuffer vb = (VertexBuffer) super.clone();
|
||||
|
@ -81,7 +81,7 @@ public class VirtualIndexBuffer extends IndexBuffer {
|
||||
case Triangles:
|
||||
numIndices = numVerts;
|
||||
return;
|
||||
case Hybrid:
|
||||
case Reserved:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
@ -303,6 +303,16 @@ public final class Shader extends NativeObject {
|
||||
return attrib;
|
||||
}
|
||||
|
||||
public boolean isAttributeDefined(VertexBuffer.Type attribType) {
|
||||
int ordinal = attribType.ordinal();
|
||||
Attribute attrib = attribs.get(ordinal);
|
||||
if (attrib == null){
|
||||
return false;
|
||||
} else {
|
||||
return attrib.location != -1 && attrib.location != 2;
|
||||
}
|
||||
}
|
||||
|
||||
public ListMap<String, Uniform> getUniformMap(){
|
||||
return uniforms;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public abstract class ShaderGenerator {
|
||||
/**
|
||||
* the technique def to use for the shader generation
|
||||
*/
|
||||
protected TechniqueDef techniqueDef = null;
|
||||
protected TechniqueDef techniqueDef = null;
|
||||
|
||||
/**
|
||||
* Build a shaderGenerator
|
||||
@ -67,16 +67,17 @@ public abstract class ShaderGenerator {
|
||||
* @param assetManager
|
||||
*/
|
||||
protected ShaderGenerator(AssetManager assetManager) {
|
||||
this.assetManager = assetManager;
|
||||
this.assetManager = assetManager;
|
||||
}
|
||||
|
||||
public void initialize(TechniqueDef techniqueDef){
|
||||
public void initialize(TechniqueDef techniqueDef) {
|
||||
this.techniqueDef = techniqueDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate vertex and fragment shaders for the given technique
|
||||
*
|
||||
* @param definesSourceCode Defines to include alongside the shader. May be
|
||||
* null.
|
||||
* @return a Shader program
|
||||
*/
|
||||
public Shader generateShader(String definesSourceCode) {
|
||||
@ -94,13 +95,13 @@ public abstract class ShaderGenerator {
|
||||
String extension = type.getExtension();
|
||||
String language = getLanguageAndVersion(type);
|
||||
String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type);
|
||||
|
||||
|
||||
if (shaderSourceCode != null) {
|
||||
String shaderSourceAssetName = techniqueName + "." + extension;
|
||||
shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
techniqueDef = null;
|
||||
return shader;
|
||||
}
|
||||
@ -114,14 +115,13 @@ public abstract class ShaderGenerator {
|
||||
* @return the code of the generated vertex shader
|
||||
*/
|
||||
protected String buildShader(List<ShaderNode> shaderNodes, ShaderGenerationInfo info, ShaderType type) {
|
||||
if (type == ShaderType.TessellationControl ||
|
||||
type == ShaderType.TessellationEvaluation ||
|
||||
type == ShaderType.Geometry) {
|
||||
if (type == ShaderType.TessellationControl
|
||||
|| type == ShaderType.TessellationEvaluation
|
||||
|| type == ShaderType.Geometry) {
|
||||
// TODO: Those are not supported.
|
||||
// Too much code assumes that type is either Vertex or Fragment
|
||||
return null;
|
||||
}
|
||||
|
||||
indent = 0;
|
||||
|
||||
StringBuilder sourceDeclaration = new StringBuilder();
|
||||
@ -192,8 +192,8 @@ public abstract class ShaderGenerator {
|
||||
if (loadedSource.length() > 1) {
|
||||
loadedSource = loadedSource.substring(0, loadedSource.lastIndexOf("}"));
|
||||
String[] sourceParts = loadedSource.split("\\s*void\\s*main\\s*\\(\\s*\\)\\s*\\{");
|
||||
if(sourceParts.length<2){
|
||||
throw new IllegalArgumentException("Syntax error in "+ shaderPath +". Cannot find 'void main(){' in \n"+ loadedSource);
|
||||
if (sourceParts.length < 2) {
|
||||
throw new IllegalArgumentException("Syntax error in " + shaderPath + ". Cannot find 'void main(){' in \n" + loadedSource);
|
||||
}
|
||||
generateDeclarativeSection(sourceDeclaration, shaderNode, sourceParts[0], info);
|
||||
generateNodeMainSection(source, shaderNode, sourceParts[1], info);
|
||||
@ -250,7 +250,7 @@ public abstract class ShaderGenerator {
|
||||
*
|
||||
* @see ShaderNode#getDefinition()
|
||||
* @see ShaderNodeDefinition#getType()
|
||||
*
|
||||
*
|
||||
* @param nodeDecalarationSource the declaration part of the node
|
||||
* @param source the StringBuilder to append generated code.
|
||||
* @param shaderNode the shaderNode.
|
||||
@ -319,5 +319,5 @@ public abstract class ShaderGenerator {
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,4 +164,7 @@ public class NullRenderer implements Renderer {
|
||||
public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteMesh(Mesh mesh) {
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +299,21 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ {
|
||||
*
|
||||
* Requires {@link Caps#TextureCompressionETC1}.
|
||||
*/
|
||||
ETC1(4, false, true, false);
|
||||
ETC1(4, false, true, false),
|
||||
|
||||
/**
|
||||
* RGTC with red channel only.
|
||||
*
|
||||
* Requires {@link Caps#TextureCompressionRGTC}.
|
||||
*/
|
||||
RTC(4, false, true, false),
|
||||
|
||||
/**
|
||||
* RGTC with red and green channels.
|
||||
*
|
||||
* Requires {@link Caps#TextureCompressionRGTC}.
|
||||
*/
|
||||
RGTC(8, false, true, false);
|
||||
|
||||
private int bpp;
|
||||
private boolean isDepth;
|
||||
|
@ -52,7 +52,8 @@ public abstract class NativeObject implements Cloneable {
|
||||
OBJTYPE_SHADERSOURCE = 5,
|
||||
OBJTYPE_AUDIOBUFFER = 6,
|
||||
OBJTYPE_AUDIOSTREAM = 7,
|
||||
OBJTYPE_FILTER = 8;
|
||||
OBJTYPE_FILTER = 8,
|
||||
OBJTYPE_MESH = 9;
|
||||
|
||||
/**
|
||||
* The object manager to which this NativeObject is registered to.
|
||||
|
@ -22,5 +22,5 @@ LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material
|
||||
LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
|
||||
LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
|
||||
LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib
|
||||
LOADER com.jme3.scene.plugins.fbx.SceneLoader : fbx
|
||||
LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba
|
||||
LOADER com.jme3.scene.plugins.fbx.FbxLoader : fbx
|
||||
# LOADER com.jme3.scene.plugins.fbx.SceneWithAnimationLoader : fba
|
||||
|
@ -85,6 +85,8 @@ public class DDSLoader implements AssetLoader {
|
||||
private static final int PF_DXT1 = 0x31545844;
|
||||
private static final int PF_DXT3 = 0x33545844;
|
||||
private static final int PF_DXT5 = 0x35545844;
|
||||
private static final int PF_ETC1 = 0x31435445;
|
||||
private static final int PF_ETC_ = 0x20435445; // the underscore represents a space
|
||||
private static final int PF_ATI1 = 0x31495441;
|
||||
private static final int PF_ATI2 = 0x32495441; // 0x41544932;
|
||||
private static final int PF_DX10 = 0x30315844; // a DX10 format
|
||||
@ -94,6 +96,9 @@ public class DDSLoader implements AssetLoader {
|
||||
DX10DIM_TEXTURE3D = 0x4;
|
||||
private static final int DX10MISC_GENERATE_MIPS = 0x1,
|
||||
DX10MISC_TEXTURECUBE = 0x4;
|
||||
private static final int DXGI_FORMAT_BC4_TYPELESS = 79;
|
||||
private static final int DXGI_FORMAT_BC4_UNORM = 80;
|
||||
private static final int DXGI_FORMAT_BC4_SNORM = 81;
|
||||
private static final double LOG2 = Math.log(2);
|
||||
private int width;
|
||||
private int height;
|
||||
@ -105,9 +110,11 @@ public class DDSLoader implements AssetLoader {
|
||||
private int caps2;
|
||||
private boolean directx10;
|
||||
private boolean compressed;
|
||||
private boolean dxtOrRgtc;
|
||||
private boolean texture3D;
|
||||
private boolean grayscaleOrAlpha;
|
||||
private boolean normal;
|
||||
private ColorSpace colorSpace;
|
||||
private Format pixelFormat;
|
||||
private int bpp;
|
||||
private int[] sizes;
|
||||
@ -133,7 +140,8 @@ public class DDSLoader implements AssetLoader {
|
||||
((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap);
|
||||
}
|
||||
ArrayList<ByteBuffer> data = readData(((TextureKey) info.getKey()).isFlipY());
|
||||
return new Image(pixelFormat, width, height, depth, data, sizes, ColorSpace.sRGB);
|
||||
|
||||
return new Image(pixelFormat, width, height, depth, data, sizes, colorSpace);
|
||||
} finally {
|
||||
if (stream != null){
|
||||
stream.close();
|
||||
@ -145,18 +153,24 @@ public class DDSLoader implements AssetLoader {
|
||||
in = new LittleEndien(stream);
|
||||
loadHeader();
|
||||
ArrayList<ByteBuffer> data = readData(false);
|
||||
return new Image(pixelFormat, width, height, depth, data, sizes, ColorSpace.sRGB);
|
||||
return new Image(pixelFormat, width, height, depth, data, sizes, colorSpace);
|
||||
}
|
||||
|
||||
private void loadDX10Header() throws IOException {
|
||||
int dxgiFormat = in.readInt();
|
||||
|
||||
if (dxgiFormat == 0) {
|
||||
pixelFormat = Format.ETC1;
|
||||
bpp = 4;
|
||||
pixelFormat = Format.ETC1;
|
||||
compressed = true;
|
||||
bpp = 4;
|
||||
} else {
|
||||
pixelFormat = null; // DXGIFormat.getJmeFormat(dxgiFormat);
|
||||
if (pixelFormat == null) {
|
||||
throw new IOException("Unsupported DX10 format: " + dxgiFormat);
|
||||
}
|
||||
bpp = pixelFormat.getBitsPerPixel();
|
||||
compressed = pixelFormat.isCompressed();
|
||||
}
|
||||
compressed = true;
|
||||
|
||||
int resDim = in.readInt();
|
||||
if (resDim == DX10DIM_TEXTURE3D) {
|
||||
@ -201,6 +215,7 @@ public class DDSLoader implements AssetLoader {
|
||||
caps2 = in.readInt();
|
||||
in.skipBytes(12);
|
||||
texture3D = false;
|
||||
colorSpace = ColorSpace.sRGB;
|
||||
|
||||
if (!directx10) {
|
||||
if (!is(caps1, DDSCAPS_TEXTURE)) {
|
||||
@ -268,10 +283,12 @@ public class DDSLoader implements AssetLoader {
|
||||
} else {
|
||||
pixelFormat = Image.Format.DXT1;
|
||||
}
|
||||
dxtOrRgtc = true;
|
||||
break;
|
||||
case PF_DXT3:
|
||||
bpp = 8;
|
||||
pixelFormat = Image.Format.DXT3;
|
||||
dxtOrRgtc = true;
|
||||
break;
|
||||
case PF_DXT5:
|
||||
bpp = 8;
|
||||
@ -279,17 +296,24 @@ public class DDSLoader implements AssetLoader {
|
||||
if (swizzle == SWIZZLE_xGxR) {
|
||||
normal = true;
|
||||
}
|
||||
dxtOrRgtc = true;
|
||||
break;
|
||||
/*
|
||||
case PF_ATI1:
|
||||
bpp = 4;
|
||||
pixelFormat = Image.Format.LTC;
|
||||
pixelFormat = Image.Format.RTC;
|
||||
dxtOrRgtc = true;
|
||||
break;
|
||||
case PF_ATI2:
|
||||
bpp = 8;
|
||||
pixelFormat = Image.Format.LATC;
|
||||
pixelFormat = Image.Format.RGTC;
|
||||
dxtOrRgtc = true;
|
||||
break;
|
||||
case PF_ETC1:
|
||||
case PF_ETC_:
|
||||
bpp = 4;
|
||||
pixelFormat = Image.Format.ETC1;
|
||||
dxtOrRgtc = false;
|
||||
break;
|
||||
*/
|
||||
case PF_DX10:
|
||||
compressed = false;
|
||||
directx10 = true;
|
||||
@ -530,6 +554,30 @@ public class DDSLoader implements AssetLoader {
|
||||
return dataBuffer;
|
||||
}
|
||||
|
||||
public ByteBuffer readCompressed2Dor3D(boolean flip, int totalSize) throws IOException {
|
||||
logger.log(Level.FINEST, "Source image format: {0}", pixelFormat);
|
||||
|
||||
ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize * depth);
|
||||
|
||||
// TODO: add support for flipping ETC1
|
||||
|
||||
for (int i = 0; i < depth; i++) {
|
||||
int mipWidth = width;
|
||||
int mipHeight = height;
|
||||
for (int mip = 0; mip < mipMapCount; mip++) {
|
||||
byte[] data = new byte[sizes[mip]];
|
||||
in.readFully(data);
|
||||
buffer.put(data);
|
||||
|
||||
mipWidth = Math.max(mipWidth / 2, 1);
|
||||
mipHeight = Math.max(mipHeight / 2, 1);
|
||||
}
|
||||
}
|
||||
buffer.rewind();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a DXT compressed image from the InputStream
|
||||
*
|
||||
@ -738,8 +786,10 @@ public class DDSLoader implements AssetLoader {
|
||||
ArrayList<ByteBuffer> allMaps = new ArrayList<ByteBuffer>();
|
||||
if (depth > 1 && !texture3D) {
|
||||
for (int i = 0; i < depth; i++) {
|
||||
if (compressed) {
|
||||
if (compressed && dxtOrRgtc) {
|
||||
allMaps.add(readDXT2D(flip, totalSize));
|
||||
} else if (compressed) {
|
||||
allMaps.add(readCompressed2Dor3D(flip, totalSize));
|
||||
} else if (grayscaleOrAlpha) {
|
||||
allMaps.add(readGrayscale2D(flip, totalSize));
|
||||
} else {
|
||||
@ -747,8 +797,10 @@ public class DDSLoader implements AssetLoader {
|
||||
}
|
||||
}
|
||||
} else if (texture3D) {
|
||||
if (compressed) {
|
||||
if (compressed && dxtOrRgtc) {
|
||||
allMaps.add(readDXT3D(flip, totalSize));
|
||||
} else if (compressed) {
|
||||
allMaps.add(readCompressed2Dor3D(flip, totalSize));
|
||||
} else if (grayscaleOrAlpha) {
|
||||
allMaps.add(readGrayscale3D(flip, totalSize));
|
||||
} else {
|
||||
@ -756,8 +808,10 @@ public class DDSLoader implements AssetLoader {
|
||||
}
|
||||
|
||||
} else {
|
||||
if (compressed) {
|
||||
if (compressed && dxtOrRgtc) {
|
||||
allMaps.add(readDXT2D(flip, totalSize));
|
||||
} else if (compressed) {
|
||||
allMaps.add(readCompressed2Dor3D(flip, totalSize));
|
||||
} else if (grayscaleOrAlpha) {
|
||||
allMaps.add(readGrayscale2D(flip, totalSize));
|
||||
} else {
|
||||
@ -822,7 +876,7 @@ public class DDSLoader implements AssetLoader {
|
||||
buf.append((char) (value & 0xFF));
|
||||
buf.append((char) ((value & 0xFF00) >> 8));
|
||||
buf.append((char) ((value & 0xFF0000) >> 16));
|
||||
buf.append((char) ((value & 0xFF00000) >> 24));
|
||||
buf.append((char) ((value & 0xFF000000) >> 24));
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
@ -213,20 +213,18 @@ public class DXTFlipper {
|
||||
case DXT5:
|
||||
type = 3;
|
||||
break;
|
||||
/*
|
||||
case LATC:
|
||||
case RGTC:
|
||||
type = 4;
|
||||
break;
|
||||
case LTC:
|
||||
case RTC:
|
||||
type = 5;
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
// DXT1 uses 8 bytes per block,
|
||||
// DXT3, DXT5, LATC use 16 bytes per block
|
||||
// DXT3, DXT5, RGTC use 16 bytes per block
|
||||
int bpb = type == 1 || type == 5 ? 8 : 16;
|
||||
|
||||
ByteBuffer retImg = BufferUtils.createByteBuffer(blocksX * blocksY * bpb);
|
||||
|
35
jme3-core/src/test/java/com/jme3/IntegrationTest.java
Normal file
35
jme3-core/src/test/java/com/jme3/IntegrationTest.java
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public interface IntegrationTest {
|
||||
}
|
60
jme3-core/src/test/java/com/jme3/app/AppSettingsIT.java
Normal file
60
jme3-core/src/test/java/com/jme3/app/AppSettingsIT.java
Normal file
@ -0,0 +1,60 @@
|
||||
package com.jme3.app;
|
||||
|
||||
import com.jme3.IntegrationTest;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.system.AppSettings;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
@Category(IntegrationTest.class)
|
||||
public class AppSettingsIT {
|
||||
|
||||
private static final String APPSETTINGS_KEY = "JME_AppSettingsTest";
|
||||
|
||||
@Test
|
||||
public void testPreferencesSaveLoad() throws BackingStoreException {
|
||||
AppSettings settings = new AppSettings(false);
|
||||
settings.putBoolean("TestBool", true);
|
||||
settings.putInteger("TestInt", 123);
|
||||
settings.putString("TestStr", "HelloWorld");
|
||||
settings.putFloat("TestFloat", 123.567f);
|
||||
settings.put("TestObj", new Mesh()); // Objects not supported by preferences
|
||||
settings.save(APPSETTINGS_KEY);
|
||||
|
||||
AppSettings loadedSettings = new AppSettings(false);
|
||||
loadedSettings.load(APPSETTINGS_KEY);
|
||||
|
||||
assertEquals(true, loadedSettings.getBoolean("TestBool"));
|
||||
assertEquals(123, loadedSettings.getInteger("TestInt"));
|
||||
assertEquals("HelloWorld", loadedSettings.getString("TestStr"));
|
||||
assertEquals(123.567f, loadedSettings.get("TestFloat"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamSaveLoad() throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
AppSettings settings = new AppSettings(false);
|
||||
settings.putBoolean("TestBool", true);
|
||||
settings.putInteger("TestInt", 123);
|
||||
settings.putString("TestStr", "HelloWorld");
|
||||
settings.putFloat("TestFloat", 123.567f);
|
||||
settings.put("TestObj", new Mesh()); // Objects not supported by file settings
|
||||
settings.save(baos);
|
||||
|
||||
AppSettings loadedSettings = new AppSettings(false);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||||
loadedSettings.load(bais);
|
||||
|
||||
assertEquals(true, loadedSettings.getBoolean("TestBool"));
|
||||
assertEquals(123, loadedSettings.getInteger("TestInt"));
|
||||
assertEquals("HelloWorld", loadedSettings.getString("TestStr"));
|
||||
assertEquals(123.567f, loadedSettings.get("TestFloat"));
|
||||
}
|
||||
}
|
@ -4,4 +4,5 @@ if (!hasProperty('mainClass')) {
|
||||
|
||||
dependencies {
|
||||
compile project(':jme3-core')
|
||||
testCompile project(path: ':jme3-core', configuration: 'testOutput')
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.OverlappingFileLockException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@ -44,9 +46,9 @@ import java.util.logging.Logger;
|
||||
/**
|
||||
* Utility class to register, extract, and load native libraries.
|
||||
* <br>
|
||||
* Register your own libraries via the {@link #registerNativeLibrary(String, Platform, String, String)} method, for
|
||||
* each platform.
|
||||
* You can then extract this library (depending on platform), by
|
||||
* Register your own libraries via the
|
||||
* {@link #registerNativeLibrary(String, Platform, String, String)} method, for
|
||||
* each platform. You can then extract this library (depending on platform), by
|
||||
* using {@link #loadNativeLibrary(java.lang.String, boolean) }.
|
||||
* <br>
|
||||
* Example:<br>
|
||||
@ -62,80 +64,82 @@ import java.util.logging.Logger;
|
||||
* This will register the library. Load it via: <br>
|
||||
* <code><pre>
|
||||
* NativeLibraryLoader.loadNativeLibrary("mystuff", true);
|
||||
* </pre></code>
|
||||
* It will load the right library automatically based on the platform.
|
||||
*
|
||||
* </pre></code> It will load the right library automatically based on the
|
||||
* platform.
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
public final class NativeLibraryLoader {
|
||||
|
||||
|
||||
private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
|
||||
private static final byte[] buf = new byte[1024 * 100];
|
||||
private static File extractionFolderOverride = null;
|
||||
private static File extractionFolder = null;
|
||||
|
||||
|
||||
private static final HashMap<NativeLibrary.Key, NativeLibrary> nativeLibraryMap
|
||||
= new HashMap<NativeLibrary.Key, NativeLibrary>();
|
||||
|
||||
|
||||
/**
|
||||
* Register a new known library.
|
||||
*
|
||||
*
|
||||
* This simply registers a known library, the actual extraction and loading
|
||||
* is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }.
|
||||
*
|
||||
* @param name The name / ID of the library (not OS or architecture specific).
|
||||
* @param platform The platform for which the in-natives-jar path has
|
||||
* been specified for.
|
||||
* @param path The path inside the natives-jar or classpath
|
||||
* corresponding to this library. Must be compatible with the platform
|
||||
* argument.
|
||||
* @param extractAsName The filename that the library should be extracted as,
|
||||
* if null, use the same name as in the path.
|
||||
* is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean)
|
||||
* }.
|
||||
*
|
||||
* @param name The name / ID of the library (not OS or architecture
|
||||
* specific).
|
||||
* @param platform The platform for which the in-natives-jar path has been
|
||||
* specified for.
|
||||
* @param path The path inside the natives-jar or classpath corresponding to
|
||||
* this library. Must be compatible with the platform argument.
|
||||
* @param extractAsName The filename that the library should be extracted
|
||||
* as, if null, use the same name as in the path.
|
||||
*/
|
||||
public static void registerNativeLibrary(String name, Platform platform,
|
||||
String path, String extractAsName) {
|
||||
nativeLibraryMap.put(new NativeLibrary.Key(name, platform),
|
||||
new NativeLibrary(name, platform, path, extractAsName));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register a new known JNI library.
|
||||
*
|
||||
*
|
||||
* This simply registers a known library, the actual extraction and loading
|
||||
* is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean) }.
|
||||
*
|
||||
* This method should be called several times for each library name,
|
||||
* each time specifying a different platform + path combination.
|
||||
*
|
||||
* @param name The name / ID of the library (not OS or architecture specific).
|
||||
* @param platform The platform for which the in-natives-jar path has
|
||||
* been specified for.
|
||||
* @param path The path inside the natives-jar or classpath
|
||||
* corresponding to this library. Must be compatible with the platform
|
||||
* argument.
|
||||
* is performed by calling {@link #loadNativeLibrary(java.lang.String, boolean)
|
||||
* }.
|
||||
*
|
||||
* This method should be called several times for each library name, each
|
||||
* time specifying a different platform + path combination.
|
||||
*
|
||||
* @param name The name / ID of the library (not OS or architecture
|
||||
* specific).
|
||||
* @param platform The platform for which the in-natives-jar path has been
|
||||
* specified for.
|
||||
* @param path The path inside the natives-jar or classpath corresponding to
|
||||
* this library. Must be compatible with the platform argument.
|
||||
*/
|
||||
public static void registerNativeLibrary(String name, Platform platform,
|
||||
String path) {
|
||||
registerNativeLibrary(name, platform, path, null);
|
||||
}
|
||||
|
||||
|
||||
static {
|
||||
// LWJGL
|
||||
registerNativeLibrary("lwjgl", Platform.Windows32, "native/windows/lwjgl.dll");
|
||||
registerNativeLibrary("lwjgl", Platform.Windows64, "native/windows/lwjgl64.dll");
|
||||
registerNativeLibrary("lwjgl", Platform.Linux32, "native/linux/liblwjgl.so");
|
||||
registerNativeLibrary("lwjgl", Platform.Linux64, "native/linux/liblwjgl64.so");
|
||||
registerNativeLibrary("lwjgl", Platform.MacOSX32, "native/macosx/liblwjgl.dylib");
|
||||
registerNativeLibrary("lwjgl", Platform.MacOSX64, "native/macosx/liblwjgl.dylib");
|
||||
registerNativeLibrary("lwjgl", Platform.Linux32, "native/linux/liblwjgl.so");
|
||||
registerNativeLibrary("lwjgl", Platform.Linux64, "native/linux/liblwjgl64.so");
|
||||
registerNativeLibrary("lwjgl", Platform.MacOSX32, "native/macosx/liblwjgl.dylib");
|
||||
registerNativeLibrary("lwjgl", Platform.MacOSX64, "native/macosx/liblwjgl.dylib");
|
||||
|
||||
// OpenAL
|
||||
// For OSX: Need to add lib prefix when extracting
|
||||
registerNativeLibrary("openal", Platform.Windows32, "native/windows/OpenAL32.dll");
|
||||
registerNativeLibrary("openal", Platform.Windows64, "native/windows/OpenAL64.dll");
|
||||
registerNativeLibrary("openal", Platform.Linux32, "native/linux/libopenal.so");
|
||||
registerNativeLibrary("openal", Platform.Linux64, "native/linux/libopenal64.so");
|
||||
registerNativeLibrary("openal", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib");
|
||||
registerNativeLibrary("openal", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib");
|
||||
registerNativeLibrary("openal", Platform.Linux32, "native/linux/libopenal.so");
|
||||
registerNativeLibrary("openal", Platform.Linux64, "native/linux/libopenal64.so");
|
||||
registerNativeLibrary("openal", Platform.MacOSX32, "native/macosx/openal.dylib", "libopenal.dylib");
|
||||
registerNativeLibrary("openal", Platform.MacOSX64, "native/macosx/openal.dylib", "libopenal.dylib");
|
||||
|
||||
// LWJGL 3.x
|
||||
registerNativeLibrary("lwjgl3", Platform.Windows32, "native/windows/lwjgl32.dll");
|
||||
@ -173,39 +177,39 @@ public final class NativeLibraryLoader {
|
||||
// BulletJme
|
||||
registerNativeLibrary("bulletjme", Platform.Windows32, "native/windows/x86/bulletjme.dll");
|
||||
registerNativeLibrary("bulletjme", Platform.Windows64, "native/windows/x86_64/bulletjme.dll");
|
||||
registerNativeLibrary("bulletjme", Platform.Linux32, "native/linux/x86/libbulletjme.so");
|
||||
registerNativeLibrary("bulletjme", Platform.Linux64, "native/linux/x86_64/libbulletjme.so");
|
||||
registerNativeLibrary("bulletjme", Platform.MacOSX32, "native/osx/x86/libbulletjme.dylib");
|
||||
registerNativeLibrary("bulletjme", Platform.MacOSX64, "native/osx/x86_64/libbulletjme.dylib");
|
||||
|
||||
registerNativeLibrary("bulletjme", Platform.Linux32, "native/linux/x86/libbulletjme.so");
|
||||
registerNativeLibrary("bulletjme", Platform.Linux64, "native/linux/x86_64/libbulletjme.so");
|
||||
registerNativeLibrary("bulletjme", Platform.MacOSX32, "native/osx/x86/libbulletjme.dylib");
|
||||
registerNativeLibrary("bulletjme", Platform.MacOSX64, "native/osx/x86_64/libbulletjme.dylib");
|
||||
|
||||
// JInput
|
||||
// For OSX: Need to rename extension jnilib -> dylib when extracting
|
||||
registerNativeLibrary("jinput", Platform.Windows32, "native/windows/jinput-raw.dll");
|
||||
registerNativeLibrary("jinput", Platform.Windows64, "native/windows/jinput-raw_64.dll");
|
||||
registerNativeLibrary("jinput", Platform.Linux32, "native/windows/libjinput-linux.so");
|
||||
registerNativeLibrary("jinput", Platform.Linux64, "native/windows/libjinput-linux64.so");
|
||||
registerNativeLibrary("jinput", Platform.MacOSX32, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib");
|
||||
registerNativeLibrary("jinput", Platform.MacOSX64, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib");
|
||||
|
||||
registerNativeLibrary("jinput", Platform.Linux32, "native/windows/libjinput-linux.so");
|
||||
registerNativeLibrary("jinput", Platform.Linux64, "native/windows/libjinput-linux64.so");
|
||||
registerNativeLibrary("jinput", Platform.MacOSX32, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib");
|
||||
registerNativeLibrary("jinput", Platform.MacOSX64, "native/macosx/libjinput-osx.jnilib", "libjinput-osx.dylib");
|
||||
|
||||
// JInput Auxiliary (only required on Windows)
|
||||
registerNativeLibrary("jinput-dx8", Platform.Windows32, "native/windows/jinput-dx8.dll");
|
||||
registerNativeLibrary("jinput-dx8", Platform.Windows64, "native/windows/jinput-dx8_64.dll");
|
||||
registerNativeLibrary("jinput-dx8", Platform.Linux32, null);
|
||||
registerNativeLibrary("jinput-dx8", Platform.Linux64, null);
|
||||
registerNativeLibrary("jinput-dx8", Platform.MacOSX32, null);
|
||||
registerNativeLibrary("jinput-dx8", Platform.MacOSX64, null);
|
||||
registerNativeLibrary("jinput-dx8", Platform.Linux32, null);
|
||||
registerNativeLibrary("jinput-dx8", Platform.Linux64, null);
|
||||
registerNativeLibrary("jinput-dx8", Platform.MacOSX32, null);
|
||||
registerNativeLibrary("jinput-dx8", Platform.MacOSX64, null);
|
||||
}
|
||||
|
||||
|
||||
private NativeLibraryLoader() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine if native bullet is on the classpath.
|
||||
*
|
||||
* Currently the context extracts the native bullet libraries, so
|
||||
* this method is needed to determine if it is needed.
|
||||
* Ideally, native bullet should be responsible for its own natives.
|
||||
*
|
||||
*
|
||||
* Currently the context extracts the native bullet libraries, so this
|
||||
* method is needed to determine if it is needed. Ideally, native bullet
|
||||
* should be responsible for its own natives.
|
||||
*
|
||||
* @return True native bullet is on the classpath, false otherwise.
|
||||
*/
|
||||
public static boolean isUsingNativeBullet() {
|
||||
@ -216,35 +220,37 @@ public final class NativeLibraryLoader {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specify a custom location where native libraries should
|
||||
* be extracted to. Ensure this is a unique path not used
|
||||
* by other applications to extract their libraries.
|
||||
* Set to <code>null</code> to restore default
|
||||
* Specify a custom location where native libraries should be extracted to.
|
||||
* Ensure this is a unique path not used by other applications to extract
|
||||
* their libraries. Set to <code>null</code> to restore default
|
||||
* functionality.
|
||||
*
|
||||
*
|
||||
* @param path Path where to extract native libraries.
|
||||
*/
|
||||
public static void setCustomExtractionFolder(String path) {
|
||||
extractionFolderOverride = new File(path).getAbsoluteFile();
|
||||
if (path != null) {
|
||||
extractionFolderOverride = new File(path).getAbsoluteFile();
|
||||
} else {
|
||||
extractionFolderOverride = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the folder where native libraries will be extracted.
|
||||
* This is automatically determined at run-time based on the
|
||||
* following criteria:<br>
|
||||
* Returns the folder where native libraries will be extracted. This is
|
||||
* automatically determined at run-time based on the following criteria:<br>
|
||||
* <ul>
|
||||
* <li>If a {@link #setCustomExtractionFolder(java.lang.String) custom
|
||||
* extraction folder} has been specified, it is returned.
|
||||
* <li>If the user can write to the working folder, then it
|
||||
* is returned.</li>
|
||||
* <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder}
|
||||
* is used, to prevent collisions, a special subfolder is used
|
||||
* called <code>natives_<hash></code> where <hash>
|
||||
* is computed automatically as the XOR of the classpath hash code
|
||||
* and the last modified date of this class.
|
||||
*
|
||||
* <li>If the user can write to the working folder, then it is
|
||||
* returned.</li>
|
||||
* <li>Otherwise, the {@link JmeSystem#getStorageFolder() storage folder} is
|
||||
* used, to prevent collisions, a special subfolder is used called
|
||||
* <code>natives_<hash></code> where <hash> is computed
|
||||
* automatically as the XOR of the classpath hash code and the last modified
|
||||
* date of this class.
|
||||
*
|
||||
* @return Path where natives will be extracted to.
|
||||
*/
|
||||
public static File getExtractionFolder() {
|
||||
@ -268,27 +274,27 @@ public final class NativeLibraryLoader {
|
||||
}
|
||||
return extractionFolder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine jME3's cache folder for the user account based on the OS.
|
||||
*
|
||||
* If the OS cache folder is missing, the assumption is that this
|
||||
* particular version of the OS does not have a dedicated cache folder,
|
||||
* hence, we use the user's home folder instead as the root.
|
||||
*
|
||||
*
|
||||
* If the OS cache folder is missing, the assumption is that this particular
|
||||
* version of the OS does not have a dedicated cache folder, hence, we use
|
||||
* the user's home folder instead as the root.
|
||||
*
|
||||
* The folder returned is as follows:<br>
|
||||
* <ul>
|
||||
* <li>Windows: ~\AppData\Local\jme3</li>
|
||||
* <li>Mac OS X: ~/Library/Caches/jme3</li>
|
||||
* <li>Linux: ~/.cache/jme3</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @return the user cache folder.
|
||||
*/
|
||||
private static File getJmeUserCacheFolder() {
|
||||
File userHomeFolder = new File(System.getProperty("user.home"));
|
||||
File userCacheFolder = null;
|
||||
|
||||
|
||||
switch (JmeSystem.getPlatform()) {
|
||||
case Linux32:
|
||||
case Linux64:
|
||||
@ -305,31 +311,31 @@ public final class NativeLibraryLoader {
|
||||
userCacheFolder = new File(new File(userHomeFolder, "AppData"), "Local");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (userCacheFolder == null || !userCacheFolder.exists()) {
|
||||
// Fallback to home directory if cache folder is missing
|
||||
return new File(userHomeFolder, ".jme3");
|
||||
}
|
||||
|
||||
|
||||
return new File(userCacheFolder, "jme3");
|
||||
}
|
||||
|
||||
private static void setExtractionFolderToUserCache() {
|
||||
File extractFolderInHome = getJmeUserCacheFolder();
|
||||
|
||||
|
||||
if (!extractFolderInHome.exists()) {
|
||||
extractFolderInHome.mkdir();
|
||||
}
|
||||
|
||||
|
||||
extractionFolder = new File(extractFolderInHome, "natives_" + Integer.toHexString(computeNativesHash()));
|
||||
|
||||
|
||||
if (!extractionFolder.exists()) {
|
||||
extractionFolder.mkdir();
|
||||
}
|
||||
|
||||
|
||||
logger.log(Level.WARNING, "Working directory is not writable. "
|
||||
+ "Natives will be extracted to:\n{0}",
|
||||
extractionFolder);
|
||||
+ "Natives will be extracted to:\n{0}",
|
||||
extractionFolder);
|
||||
}
|
||||
|
||||
private static int computeNativesHash() {
|
||||
@ -360,11 +366,12 @@ public final class NativeLibraryLoader {
|
||||
try {
|
||||
conn.getInputStream().close();
|
||||
conn.getOutputStream().close();
|
||||
} catch (IOException ex) { }
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static File[] getJarsWithNatives() {
|
||||
HashSet<File> jarFiles = new HashSet<File>();
|
||||
for (Map.Entry<NativeLibrary.Key, NativeLibrary> lib : nativeLibraryMap.entrySet()) {
|
||||
@ -375,7 +382,7 @@ public final class NativeLibraryLoader {
|
||||
}
|
||||
return jarFiles.toArray(new File[0]);
|
||||
}
|
||||
|
||||
|
||||
public static void extractNativeLibraries(Platform platform, File targetDir) throws IOException {
|
||||
for (Map.Entry<NativeLibrary.Key, NativeLibrary> lib : nativeLibraryMap.entrySet()) {
|
||||
if (lib.getValue().getPlatform() == platform) {
|
||||
@ -386,7 +393,7 @@ public final class NativeLibraryLoader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String mapLibraryName_emulated(String name, Platform platform) {
|
||||
switch (platform) {
|
||||
case MacOSX32:
|
||||
@ -399,10 +406,10 @@ public final class NativeLibraryLoader {
|
||||
return name + ".so";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes platform-specific portions of a library file name so
|
||||
* that it can be accepted by {@link System#loadLibrary(java.lang.String) }.
|
||||
* Removes platform-specific portions of a library file name so that it can
|
||||
* be accepted by {@link System#loadLibrary(java.lang.String) }.
|
||||
* <p>
|
||||
* E.g.<br>
|
||||
* <ul>
|
||||
@ -410,7 +417,7 @@ public final class NativeLibraryLoader {
|
||||
* <li>liblwjgl64.so => lwjgl64</li>
|
||||
* <li>libopenal.so => openal</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param filename The filename to strip platform-specific parts
|
||||
* @return The stripped library name
|
||||
*/
|
||||
@ -425,7 +432,7 @@ public final class NativeLibraryLoader {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static File getJarForNativeLibrary(Platform platform, String name) {
|
||||
NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform));
|
||||
if (library == null) {
|
||||
@ -436,23 +443,23 @@ public final class NativeLibraryLoader {
|
||||
if (pathInJar == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
String fileNameInJar;
|
||||
if (pathInJar.contains("/")) {
|
||||
fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1);
|
||||
} else {
|
||||
fileNameInJar = pathInJar;
|
||||
}
|
||||
|
||||
|
||||
URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
|
||||
if (url == null) {
|
||||
url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
|
||||
}
|
||||
|
||||
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
StringBuilder sb = new StringBuilder(url.toString());
|
||||
if (sb.indexOf("jar:file:/") == 0) {
|
||||
sb.delete(0, 9);
|
||||
@ -462,7 +469,7 @@ public final class NativeLibraryLoader {
|
||||
return null; // not a jar
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void extractNativeLibrary(Platform platform, String name, File targetDir) throws IOException {
|
||||
NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform));
|
||||
if (library == null) {
|
||||
@ -473,19 +480,19 @@ public final class NativeLibraryLoader {
|
||||
if (pathInJar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
String fileNameInJar;
|
||||
if (pathInJar.contains("/")) {
|
||||
fileNameInJar = pathInJar.substring(pathInJar.lastIndexOf("/") + 1);
|
||||
} else {
|
||||
fileNameInJar = pathInJar;
|
||||
}
|
||||
|
||||
|
||||
URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
|
||||
if (url == null) {
|
||||
url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
|
||||
}
|
||||
|
||||
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
@ -496,10 +503,10 @@ public final class NativeLibraryLoader {
|
||||
} else {
|
||||
loadedAsFileName = fileNameInJar;
|
||||
}
|
||||
|
||||
|
||||
URLConnection conn = url.openConnection();
|
||||
InputStream in = conn.getInputStream();
|
||||
|
||||
|
||||
File targetFile = new File(targetDir, loadedAsFileName);
|
||||
OutputStream out = null;
|
||||
try {
|
||||
@ -526,12 +533,26 @@ public final class NativeLibraryLoader {
|
||||
|
||||
/**
|
||||
* First extracts the native library and then loads it.
|
||||
*
|
||||
*
|
||||
* @param name The name of the library to load.
|
||||
* @param isRequired If true and the library fails to load, throw exception. If
|
||||
* false, do nothing if it fails to load.
|
||||
* @param isRequired If true and the library fails to load, throw exception.
|
||||
* If false, do nothing if it fails to load.
|
||||
*/
|
||||
public static void loadNativeLibrary(String name, boolean isRequired) {
|
||||
loadNativeLibrary(name, isRequired, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* First extracts the native library and then (optionally) loads it.
|
||||
*
|
||||
* @param name The name of the library to load.
|
||||
* @param isRequired If true and the library fails to load, throw exception.
|
||||
* If false, do nothing if it fails to load.
|
||||
* @param loadLibrary If true, call
|
||||
* {@link System#loadLibrary(java.lang.String)} on the library, otherwise,
|
||||
* do nothing after extraction.
|
||||
*/
|
||||
public static void loadNativeLibrary(String name, boolean isRequired, boolean loadLibrary) {
|
||||
if (JmeSystem.isLowPermissions()) {
|
||||
throw new UnsupportedOperationException("JVM is running under "
|
||||
+ "reduced permissions. Cannot load native libraries.");
|
||||
@ -539,7 +560,7 @@ public final class NativeLibraryLoader {
|
||||
|
||||
Platform platform = JmeSystem.getPlatform();
|
||||
NativeLibrary library = nativeLibraryMap.get(new NativeLibrary.Key(name, platform));
|
||||
|
||||
|
||||
if (library == null) {
|
||||
// No library exists for this platform.
|
||||
if (isRequired) {
|
||||
@ -547,20 +568,20 @@ public final class NativeLibraryLoader {
|
||||
"The required native library '" + name + "'"
|
||||
+ " is not available for your OS: " + platform);
|
||||
} else {
|
||||
logger.log(Level.FINE, "The optional native library ''{0}''" +
|
||||
" is not available for your OS: {1}",
|
||||
new Object[]{name, platform});
|
||||
logger.log(Level.FINE, "The optional native library ''{0}''"
|
||||
+ " is not available for your OS: {1}",
|
||||
new Object[]{name, platform});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final String pathInJar = library.getPathInNativesJar();
|
||||
|
||||
if (pathInJar == null) {
|
||||
// This platform does not require the native library to be loaded.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final String fileNameInJar;
|
||||
|
||||
if (pathInJar.contains("/")) {
|
||||
@ -570,7 +591,7 @@ public final class NativeLibraryLoader {
|
||||
}
|
||||
|
||||
URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar);
|
||||
|
||||
|
||||
if (url == null) {
|
||||
// Try the root of the classpath as well.
|
||||
url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar);
|
||||
@ -578,15 +599,15 @@ public final class NativeLibraryLoader {
|
||||
|
||||
if (url == null) {
|
||||
// Attempt to load it as a system library.
|
||||
// Need to unmap it from library specific parts.
|
||||
String unmappedName = unmapLibraryName(fileNameInJar);
|
||||
try {
|
||||
// XXX: HACK. Vary loading method based on library name..
|
||||
// lwjgl and jinput handle loading by themselves.
|
||||
if (!name.equals("lwjgl") && !name.equals("jinput")) {
|
||||
// Need to unmap it from library specific parts.
|
||||
if (loadLibrary) {
|
||||
System.loadLibrary(unmappedName);
|
||||
logger.log(Level.FINE, "Loaded system installed "
|
||||
+ "version of native library: {0}", unmappedName);
|
||||
} else {
|
||||
throw new UnsatisfiedLinkError();
|
||||
}
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
if (isRequired) {
|
||||
@ -595,16 +616,16 @@ public final class NativeLibraryLoader {
|
||||
+ " was not found in the classpath via '" + pathInJar
|
||||
+ "'. Error message: " + e.getMessage());
|
||||
} else {
|
||||
logger.log(Level.FINE, "The optional native library ''{0}''" +
|
||||
" was not found in the classpath via ''{1}''" +
|
||||
". Error message: {2}",
|
||||
new Object[]{unmappedName, pathInJar, e.getMessage()});
|
||||
logger.log(Level.FINE, "The optional native library ''{0}''"
|
||||
+ " was not found in the classpath via ''{1}''"
|
||||
+ ". Error message: {2}",
|
||||
new Object[]{unmappedName, pathInJar, e.getMessage()});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// The library has been found and is ready to be extracted.
|
||||
// Determine what filename it should be extracted as.
|
||||
String loadedAsFileName;
|
||||
@ -614,53 +635,57 @@ public final class NativeLibraryLoader {
|
||||
// Just use the original filename as it is in the JAR.
|
||||
loadedAsFileName = fileNameInJar;
|
||||
}
|
||||
|
||||
|
||||
File extactionDirectory = getExtractionFolder();
|
||||
URLConnection conn;
|
||||
InputStream in;
|
||||
|
||||
|
||||
try {
|
||||
conn = url.openConnection();
|
||||
in = conn.getInputStream();
|
||||
} catch (IOException ex) {
|
||||
// Maybe put more detail here? Not sure..
|
||||
throw new UnsatisfiedLinkError("Failed to open file: '" + url +
|
||||
"'. Error: " + ex);
|
||||
throw new UnsatisfiedLinkError("Failed to open file: '" + url
|
||||
+ "'. Error: " + ex);
|
||||
}
|
||||
|
||||
|
||||
File targetFile = new File(extactionDirectory, loadedAsFileName);
|
||||
OutputStream out = null;
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
if (targetFile.exists()) {
|
||||
// OK, compare last modified date of this file to
|
||||
// OK, compare last modified date of this file to
|
||||
// file in jar
|
||||
long targetLastModified = targetFile.lastModified();
|
||||
long sourceLastModified = conn.getLastModified();
|
||||
|
||||
// Allow ~1 second range for OSes that only support low precision
|
||||
if (targetLastModified + 1000 > sourceLastModified) {
|
||||
logger.log(Level.FINE, "Not copying library {0}. " +
|
||||
"Latest already extracted.",
|
||||
loadedAsFileName);
|
||||
if (Math.abs(targetLastModified - sourceLastModified) < 1000) {
|
||||
logger.log(Level.FINE, "Not copying library {0}. "
|
||||
+ "Identical version already extracted.",
|
||||
loadedAsFileName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
out = new FileOutputStream(targetFile);
|
||||
FileLock lock = out.getChannel().lock();
|
||||
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
|
||||
|
||||
in.close();
|
||||
in = null;
|
||||
out.close();
|
||||
out = null;
|
||||
|
||||
// NOTE: On OSes that support "Date Created" property,
|
||||
// NOTE: On OSes that support "Date Created" property,
|
||||
// this will cause the last modified date to be lower than
|
||||
// date created which makes no sense
|
||||
targetFile.setLastModified(conn.getLastModified());
|
||||
} catch (OverlappingFileLockException ex) {
|
||||
// do nothing with ex
|
||||
} catch (IOException ex) {
|
||||
if (ex.getMessage().contains("used by another process")) {
|
||||
return;
|
||||
@ -669,30 +694,26 @@ public final class NativeLibraryLoader {
|
||||
+ "library to: " + targetFile);
|
||||
}
|
||||
} finally {
|
||||
// XXX: HACK. Vary loading method based on library name..
|
||||
// lwjgl and jinput handle loading by themselves.
|
||||
if (name.equals("lwjgl") || name.equals("lwjgl3")) {
|
||||
System.setProperty("org.lwjgl.librarypath",
|
||||
extactionDirectory.getAbsolutePath());
|
||||
} else if (name.equals("jinput")) {
|
||||
System.setProperty("net.java.games.input.librarypath",
|
||||
extactionDirectory.getAbsolutePath());
|
||||
} else {
|
||||
// all other libraries (openal, bulletjme, custom)
|
||||
// will load directly in here.
|
||||
if (loadLibrary) {
|
||||
System.load(targetFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
if(in != null){
|
||||
try { in.close(); } catch (IOException ex) { }
|
||||
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
if(out != null){
|
||||
try { out.close(); } catch (IOException ex) { }
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.log(Level.FINE, "Loaded native library from ''{0}'' into ''{1}''",
|
||||
new Object[]{url, targetFile});
|
||||
|
||||
logger.log(Level.FINE, "Loaded native library from ''{0}'' into ''{1}''",
|
||||
new Object[]{url, targetFile});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ package com.jme3.system.awt;
|
||||
import com.jme3.post.SceneProcessor;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.opengl.GLRenderer;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.Image.Format;
|
||||
@ -47,20 +48,19 @@ import java.awt.image.AffineTransformOp;
|
||||
import java.awt.image.BufferStrategy;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
public class AwtPanel extends Canvas implements JmePanel, SceneProcessor {
|
||||
|
||||
private boolean attachAsMain = false;
|
||||
|
||||
private BufferedImage img;
|
||||
private FrameBuffer fb;
|
||||
private boolean srgb = false;
|
||||
private ByteBuffer byteBuf;
|
||||
private IntBuffer intBuf;
|
||||
// private FrameBuffer fb;
|
||||
private RenderManager rm;
|
||||
private PaintMode paintMode;
|
||||
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
|
||||
@ -75,35 +75,47 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
// Reshape vars
|
||||
private int newWidth = 1;
|
||||
private int newHeight = 1;
|
||||
private AtomicBoolean reshapeNeeded = new AtomicBoolean(false);
|
||||
private AtomicBoolean reshapeNeeded = new AtomicBoolean(true);
|
||||
private final Object lock = new Object();
|
||||
|
||||
public AwtPanel(PaintMode paintMode){
|
||||
this(paintMode, false);
|
||||
}
|
||||
// Buffer pool and pending buffers
|
||||
private int NUM_FRAMES = 3;
|
||||
private final ArrayBlockingQueue<Future<ByteBuffer>> pendingFrames = new ArrayBlockingQueue<Future<ByteBuffer>>(NUM_FRAMES);
|
||||
private final ArrayBlockingQueue<ByteBuffer> bufferPool = new ArrayBlockingQueue<ByteBuffer>(NUM_FRAMES);
|
||||
private final ArrayList<FrameBuffer> fbs = new ArrayList<FrameBuffer>(NUM_FRAMES);
|
||||
private int frameIndex = 0;
|
||||
|
||||
|
||||
private final ComponentAdapter resizeListener = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
onResize(e);
|
||||
}
|
||||
};
|
||||
|
||||
public AwtPanel(PaintMode paintMode, boolean srgb){
|
||||
this.paintMode = paintMode;
|
||||
this.srgb = srgb;
|
||||
|
||||
invalidatePendingFrames();
|
||||
|
||||
if (paintMode == PaintMode.Accelerated){
|
||||
setIgnoreRepaint(true);
|
||||
}
|
||||
|
||||
addComponentListener(new ComponentAdapter(){
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
synchronized (lock){
|
||||
int newWidth2 = Math.max(getWidth(), 1);
|
||||
int newHeight2 = Math.max(getHeight(), 1);
|
||||
if (newWidth != newWidth2 || newHeight != newHeight2){
|
||||
newWidth = newWidth2;
|
||||
newHeight = newHeight2;
|
||||
reshapeNeeded.set(true);
|
||||
System.out.println("EDT: componentResized " + newWidth + ", " + newHeight);
|
||||
}
|
||||
}
|
||||
addComponentListener(resizeListener);
|
||||
}
|
||||
|
||||
public void onResize(ComponentEvent e) {
|
||||
synchronized (lock) {
|
||||
int newWidth2 = Math.max(getWidth(), 1);
|
||||
int newHeight2 = Math.max(getHeight(), 1);
|
||||
if (newWidth != newWidth2 || newHeight != newHeight2) {
|
||||
newWidth = newWidth2;
|
||||
newHeight = newHeight2;
|
||||
reshapeNeeded.set(true);
|
||||
System.out.println("EDT: componentResized " + newWidth + ", " + newHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,13 +140,13 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g){
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
synchronized (lock){
|
||||
g2d.drawImage(img, transformOp, 0, 0);
|
||||
}
|
||||
}
|
||||
// @Override
|
||||
// public void paint(Graphics g){
|
||||
// Graphics2D g2d = (Graphics2D) g;
|
||||
// synchronized (lock){
|
||||
// g2d.drawImage(img, transformOp, 0, 0);
|
||||
// }
|
||||
// }
|
||||
|
||||
public boolean checkVisibilityState(){
|
||||
if (!hasNativePeer.get()){
|
||||
@ -157,24 +169,85 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
return currentShowing;
|
||||
}
|
||||
|
||||
public void repaintInThread(){
|
||||
// Convert screenshot.
|
||||
byteBuf.clear();
|
||||
rm.getRenderer().readFrameBuffer(fb, byteBuf);
|
||||
// public void repaintInThread(){
|
||||
// // Convert screenshot.
|
||||
// byteBuf.clear();
|
||||
// rm.getRenderer().readFrameBuffer(fb, byteBuf);
|
||||
//
|
||||
// synchronized (lock){
|
||||
// // All operations on img must be synchronized
|
||||
// // as it is accessed from EDT.
|
||||
// Screenshots.convertScreenShot2(intBuf, img);
|
||||
// repaint();
|
||||
// }
|
||||
// }
|
||||
|
||||
public ByteBuffer acquireNextFrame() {
|
||||
if (pendingFrames.isEmpty()) {
|
||||
System.out.println("!!! No pending frames, returning null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
synchronized (lock){
|
||||
// All operations on img must be synchronized
|
||||
// as it is accessed from EDT.
|
||||
Screenshots.convertScreenShot2(intBuf, img);
|
||||
repaint();
|
||||
try {
|
||||
ByteBuffer nextFrame = null;
|
||||
|
||||
// while (!pendingFrames.isEmpty() && pendingFrames.peek().isDone()) {
|
||||
// nextFrame = pendingFrames.take().get();
|
||||
// }
|
||||
//
|
||||
// if (nextFrame != null) {
|
||||
// return nextFrame;
|
||||
// }
|
||||
//
|
||||
// if (pendingFrames.remainingCapacity() == 0) {
|
||||
// Force it to finish ..
|
||||
return pendingFrames.take().get();
|
||||
// }
|
||||
|
||||
// Some frames are pending, none are finished though.
|
||||
// return null;
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} catch (ExecutionException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawFrameInThread(){
|
||||
// Convert screenshot.
|
||||
byteBuf.clear();
|
||||
rm.getRenderer().readFrameBuffer(fb, byteBuf);
|
||||
Screenshots.convertScreenShot2(intBuf, img);
|
||||
public void readNextFrame() {
|
||||
if (bufferPool.isEmpty()) {
|
||||
System.out.println("??? Too many pending frames!");
|
||||
return; // need to draw more frames ..
|
||||
}
|
||||
|
||||
try {
|
||||
int size = fbs.get(frameIndex).getWidth() * fbs.get(frameIndex).getHeight() * 4;
|
||||
ByteBuffer byteBuf = bufferPool.take();
|
||||
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, size);
|
||||
byteBuf.clear();
|
||||
|
||||
GLRenderer renderer = (GLRenderer) rm.getRenderer();
|
||||
Future<ByteBuffer> future = renderer.readFrameBufferLater(fbs.get(frameIndex), byteBuf);
|
||||
if (!pendingFrames.offer(future)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
frameIndex ++;
|
||||
if (frameIndex >= NUM_FRAMES) {
|
||||
frameIndex = 0;
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawFrameInThread(ByteBuffer byteBuf){
|
||||
// Convert the frame into the image so it can be rendered.
|
||||
Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img);
|
||||
|
||||
// return the frame back to its rightful owner.
|
||||
if (!bufferPool.offer(byteBuf)) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
synchronized (lock){
|
||||
// All operations on strategy should be synchronized (?)
|
||||
@ -235,44 +308,65 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
if (this.rm == null){
|
||||
// First time called in OGL thread
|
||||
this.rm = rm;
|
||||
reshapeInThread(1, 1);
|
||||
// reshapeInThread(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAccelerated() {
|
||||
readNextFrame();
|
||||
ByteBuffer byteBuf = acquireNextFrame();
|
||||
if (byteBuf != null) {
|
||||
drawFrameInThread(byteBuf);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidatePendingFrames() {
|
||||
// NOTE: all pending read requests are invalid!
|
||||
for (Future<ByteBuffer> pendingRequest : pendingFrames) {
|
||||
pendingRequest.cancel(true);
|
||||
}
|
||||
pendingFrames.clear();
|
||||
bufferPool.clear();
|
||||
|
||||
// Populate buffer pool.
|
||||
int cap = bufferPool.remainingCapacity();
|
||||
for (int i = 0; i < cap; i++) {
|
||||
bufferPool.add(BufferUtils.createByteBuffer(1 * 1 * 4));
|
||||
}
|
||||
}
|
||||
|
||||
private void reshapeInThread(int width, int height) {
|
||||
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4);
|
||||
intBuf = byteBuf.asIntBuffer();
|
||||
|
||||
if (fb != null) {
|
||||
invalidatePendingFrames();
|
||||
|
||||
for (FrameBuffer fb : fbs) {
|
||||
fb.dispose();
|
||||
fb = null;
|
||||
}
|
||||
fbs.clear();
|
||||
|
||||
for (int i = 0; i < NUM_FRAMES; i++) {
|
||||
FrameBuffer fb = new FrameBuffer(width, height, 1);
|
||||
fb.setDepthBuffer(Format.Depth);
|
||||
fb.setColorBuffer(Format.RGBA8);
|
||||
fbs.add(fb);
|
||||
}
|
||||
|
||||
fb = new FrameBuffer(width, height, 1);
|
||||
fb.setDepthBuffer(Format.Depth);
|
||||
fb.setColorBuffer(Format.RGB8);
|
||||
fb.setSrgb(srgb);
|
||||
|
||||
if (attachAsMain){
|
||||
rm.getRenderer().setMainFrameBufferOverride(fb);
|
||||
}
|
||||
// if (attachAsMain){
|
||||
// rm.getRenderer().setMainFrameBufferOverride(fb);
|
||||
// }
|
||||
|
||||
synchronized (lock){
|
||||
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
|
||||
}
|
||||
|
||||
// synchronized (lock){
|
||||
// img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height);
|
||||
// }
|
||||
|
||||
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
|
||||
tx.translate(0, -img.getHeight());
|
||||
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||
|
||||
for (ViewPort vp : viewPorts){
|
||||
if (!attachAsMain){
|
||||
vp.setOutputFrameBuffer(fb);
|
||||
}
|
||||
// if (!attachAsMain){
|
||||
// vp.setOutputFrameBuffer(fb);
|
||||
// }
|
||||
vp.getCamera().resize(width, height, true);
|
||||
|
||||
// NOTE: Hack alert. This is done ONLY for custom framebuffers.
|
||||
@ -283,8 +377,9 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return fb != null;
|
||||
return rm != null;
|
||||
}
|
||||
|
||||
public void preFrame(float tpf) {
|
||||
@ -298,8 +393,21 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
// For "PaintMode.OnDemand" only.
|
||||
repaintRequest.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getComponent() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameBegin() {
|
||||
if (attachAsMain && rm != null){
|
||||
rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex));
|
||||
}
|
||||
}
|
||||
|
||||
void onFrameEnd() {
|
||||
@Override
|
||||
public void onFrameEnd() {
|
||||
if (reshapeNeeded.getAndSet(false)) {
|
||||
reshapeInThread(newWidth, newHeight);
|
||||
} else {
|
||||
@ -309,26 +417,24 @@ public class AwtPanel extends Canvas implements SceneProcessor {
|
||||
|
||||
switch (paintMode) {
|
||||
case Accelerated:
|
||||
drawFrameInThread();
|
||||
break;
|
||||
case Repaint:
|
||||
repaintInThread();
|
||||
break;
|
||||
case OnRequest:
|
||||
if (repaintRequest.getAndSet(false)) {
|
||||
repaintInThread();
|
||||
}
|
||||
updateAccelerated();
|
||||
break;
|
||||
// case Repaint:
|
||||
// repaintInThread();
|
||||
// break;
|
||||
// case OnRequest:
|
||||
// if (repaintRequest.getAndSet(false)) {
|
||||
// repaintInThread();
|
||||
// }
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void postFrame(FrameBuffer out) {
|
||||
if (!attachAsMain && out != fb){
|
||||
throw new IllegalStateException("Why did you change the output framebuffer?");
|
||||
}
|
||||
|
||||
// onFrameEnd();
|
||||
// if (!attachAsMain && out != fb){
|
||||
// throw new IllegalStateException("Why did you change the output framebuffer?");
|
||||
// }
|
||||
}
|
||||
|
||||
public void reshape(ViewPort vp, int w, int h) {
|
||||
|
@ -46,8 +46,8 @@ public class AwtPanelsContext implements JmeContext {
|
||||
protected JmeContext actualContext;
|
||||
protected AppSettings settings = new AppSettings(true);
|
||||
protected SystemListener listener;
|
||||
protected ArrayList<AwtPanel> panels = new ArrayList<AwtPanel>();
|
||||
protected AwtPanel inputSource;
|
||||
protected ArrayList<JmePanel> panels = new ArrayList<JmePanel>();
|
||||
protected JmePanel inputSource;
|
||||
|
||||
protected AwtMouseInput mouseInput = new AwtMouseInput();
|
||||
protected AwtKeyInput keyInput = new AwtKeyInput();
|
||||
@ -92,13 +92,13 @@ public class AwtPanelsContext implements JmeContext {
|
||||
}
|
||||
}
|
||||
|
||||
public void setInputSource(AwtPanel panel){
|
||||
public void setInputSource(JmePanel panel){
|
||||
if (!panels.contains(panel))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
inputSource = panel;
|
||||
mouseInput.setInputSource(panel);
|
||||
keyInput.setInputSource(panel);
|
||||
mouseInput.setInputSource(panel.getComponent());
|
||||
keyInput.setInputSource(panel.getComponent());
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
@ -148,14 +148,14 @@ public class AwtPanelsContext implements JmeContext {
|
||||
public AwtPanelsContext(){
|
||||
}
|
||||
|
||||
public AwtPanel createPanel(PaintMode paintMode){
|
||||
AwtPanel panel = new AwtPanel(paintMode);
|
||||
public JmePanel createPanel(PaintMode paintMode){
|
||||
JmePanel panel = new SwingPanel(paintMode, true);
|
||||
panels.add(panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
public AwtPanel createPanel(PaintMode paintMode, boolean srgb){
|
||||
AwtPanel panel = new AwtPanel(paintMode, srgb);
|
||||
public JmePanel createPanel(PaintMode paintMode, boolean srgb){
|
||||
JmePanel panel = new SwingPanel(paintMode, srgb);
|
||||
panels.add(panel);
|
||||
return panel;
|
||||
}
|
||||
@ -168,18 +168,18 @@ public class AwtPanelsContext implements JmeContext {
|
||||
// Check if throttle required
|
||||
boolean needThrottle = true;
|
||||
|
||||
for (AwtPanel panel : panels){
|
||||
for (JmePanel panel : panels){
|
||||
if (panel.isActiveDrawing()){
|
||||
needThrottle = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastThrottleState != needThrottle){
|
||||
if (lastThrottleState != needThrottle) {
|
||||
lastThrottleState = needThrottle;
|
||||
if (lastThrottleState){
|
||||
if (lastThrottleState) {
|
||||
System.out.println("OGL: Throttling update loop.");
|
||||
}else{
|
||||
} else {
|
||||
System.out.println("OGL: Ceased throttling update loop.");
|
||||
}
|
||||
}
|
||||
@ -191,9 +191,13 @@ public class AwtPanelsContext implements JmeContext {
|
||||
}
|
||||
}
|
||||
|
||||
for (JmePanel panel : panels){
|
||||
panel.onFrameBegin();
|
||||
}
|
||||
|
||||
listener.update();
|
||||
|
||||
for (AwtPanel panel : panels){
|
||||
for (JmePanel panel : panels){
|
||||
panel.onFrameEnd();
|
||||
}
|
||||
}
|
||||
|
48
jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java
Executable file
48
jme3-desktop/src/main/java/com/jme3/system/awt/JmePanel.java
Executable file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.awt;
|
||||
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import java.awt.Component;
|
||||
|
||||
public interface JmePanel {
|
||||
|
||||
public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps);
|
||||
|
||||
public boolean isActiveDrawing();
|
||||
|
||||
public void onFrameBegin();
|
||||
|
||||
public void onFrameEnd();
|
||||
|
||||
public Component getComponent();
|
||||
}
|
382
jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java
Executable file
382
jme3-desktop/src/main/java/com/jme3/system/awt/SwingPanel.java
Executable file
@ -0,0 +1,382 @@
|
||||
/*
|
||||
* 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.awt;
|
||||
|
||||
import com.jme3.post.SceneProcessor;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.ViewPort;
|
||||
import com.jme3.renderer.opengl.GLRenderer;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.texture.FrameBuffer;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.Screenshots;
|
||||
import java.awt.AWTException;
|
||||
import java.awt.BufferCapabilities;
|
||||
import java.awt.Canvas;
|
||||
import java.awt.Component;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.ImageCapabilities;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.AffineTransformOp;
|
||||
import java.awt.image.BufferStrategy;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
public class SwingPanel extends JPanel implements JmePanel, SceneProcessor {
|
||||
|
||||
private boolean attachAsMain = false;
|
||||
|
||||
private BufferedImage img;
|
||||
// private FrameBuffer fb;
|
||||
private RenderManager rm;
|
||||
private PaintMode paintMode;
|
||||
private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
|
||||
|
||||
// Visibility/drawing vars
|
||||
private AffineTransformOp transformOp;
|
||||
private AtomicBoolean hasNativePeer = new AtomicBoolean(false);
|
||||
private AtomicBoolean showing = new AtomicBoolean(false);
|
||||
private AtomicBoolean repaintRequest = new AtomicBoolean(false);
|
||||
|
||||
// Reshape vars
|
||||
private int newWidth = 1;
|
||||
private int newHeight = 1;
|
||||
private AtomicBoolean reshapeNeeded = new AtomicBoolean(true);
|
||||
private final Object lock = new Object();
|
||||
|
||||
// Buffer pool and pending buffers
|
||||
private final int NUM_FRAMES = 2;
|
||||
private final ArrayBlockingQueue<Future<ByteBuffer>> pendingFrames = new ArrayBlockingQueue<Future<ByteBuffer>>(NUM_FRAMES);
|
||||
private final ArrayBlockingQueue<ByteBuffer> bufferPool = new ArrayBlockingQueue<ByteBuffer>(NUM_FRAMES);
|
||||
private final ArrayList<FrameBuffer> fbs = new ArrayList<FrameBuffer>(NUM_FRAMES);
|
||||
private int frameIndex = 0;
|
||||
|
||||
private final ComponentAdapter resizeListener = new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
onResize(e);
|
||||
}
|
||||
};
|
||||
|
||||
public SwingPanel(PaintMode paintMode, boolean srgb){
|
||||
this.paintMode = paintMode;
|
||||
invalidatePendingFrames();
|
||||
addComponentListener(resizeListener);
|
||||
}
|
||||
|
||||
public void onResize(ComponentEvent e) {
|
||||
synchronized (lock) {
|
||||
int newWidth2 = Math.max(getWidth(), 1);
|
||||
int newHeight2 = Math.max(getHeight(), 1);
|
||||
if (newWidth != newWidth2 || newHeight != newHeight2) {
|
||||
newWidth = newWidth2;
|
||||
newHeight = newHeight2;
|
||||
reshapeNeeded.set(true);
|
||||
System.out.println("EDT: componentResized " + newWidth + ", " + newHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getComponent() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify(){
|
||||
super.addNotify();
|
||||
|
||||
synchronized (lock){
|
||||
hasNativePeer.set(true);
|
||||
System.out.println("EDT: addNotify");
|
||||
}
|
||||
|
||||
requestFocusInWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify(){
|
||||
synchronized (lock){
|
||||
hasNativePeer.set(false);
|
||||
System.out.println("EDT: removeNotify");
|
||||
}
|
||||
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
public boolean checkVisibilityState() {
|
||||
if (!hasNativePeer.get()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean currentShowing = isShowing();
|
||||
if (showing.getAndSet(currentShowing) != currentShowing) {
|
||||
if (currentShowing) {
|
||||
System.out.println("OGL: Enter showing state.");
|
||||
} else {
|
||||
System.out.println("OGL: Exit showing state.");
|
||||
}
|
||||
}
|
||||
return currentShowing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g){
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_SPEED);
|
||||
|
||||
ByteBuffer byteBuf = null;
|
||||
|
||||
synchronized (lock){
|
||||
if (pendingFrames.size() > NUM_FRAMES - 1) {
|
||||
byteBuf = acquireNextFrame();
|
||||
}
|
||||
|
||||
if (byteBuf != null) {
|
||||
// Convert the frame into the image so it can be rendered.
|
||||
Screenshots.convertScreenShot2(byteBuf.asIntBuffer(), img);
|
||||
|
||||
try {
|
||||
// return the frame back to its rightful owner.
|
||||
bufferPool.put(byteBuf);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(SwingPanel.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g2d.drawImage(img, transformOp, 0, 0);
|
||||
}
|
||||
|
||||
public ByteBuffer acquireNextFrame() {
|
||||
if (pendingFrames.isEmpty()) {
|
||||
System.out.println("!!! No pending frames, returning null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return pendingFrames.take().get();
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} catch (ExecutionException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs an available buffer from the available frames pool,
|
||||
* reads the OpenGL backbuffer into it, then adds it to the pending frames pool.
|
||||
*/
|
||||
public void readNextFrame() {
|
||||
if (bufferPool.isEmpty()) {
|
||||
System.out.println("??? Too many pending frames!");
|
||||
return; // need to draw more frames ..
|
||||
}
|
||||
|
||||
try {
|
||||
int size = fbs.get(frameIndex).getWidth() * fbs.get(frameIndex).getHeight() * 4;
|
||||
ByteBuffer byteBuf = bufferPool.take();
|
||||
byteBuf = BufferUtils.ensureLargeEnough(byteBuf, size);
|
||||
byteBuf.clear();
|
||||
|
||||
GLRenderer renderer = (GLRenderer) rm.getRenderer();
|
||||
// Future<ByteBuffer> future = renderer.readFrameBufferLater(fbs.get(frameIndex), byteBuf);
|
||||
// if (!pendingFrames.offer(future)) {
|
||||
// throw new AssertionError();
|
||||
// }
|
||||
|
||||
frameIndex ++;
|
||||
if (frameIndex >= NUM_FRAMES) {
|
||||
frameIndex = 0;
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActiveDrawing() {
|
||||
return paintMode != PaintMode.OnRequest && showing.get();
|
||||
}
|
||||
|
||||
public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps){
|
||||
if (viewPorts.size() > 0){
|
||||
for (ViewPort vp : viewPorts){
|
||||
vp.setOutputFrameBuffer(null);
|
||||
}
|
||||
viewPorts.get(viewPorts.size()-1).removeProcessor(this);
|
||||
}
|
||||
|
||||
viewPorts.addAll(Arrays.asList(vps));
|
||||
viewPorts.get(viewPorts.size()-1).addProcessor(this);
|
||||
|
||||
this.attachAsMain = overrideMainFramebuffer;
|
||||
}
|
||||
|
||||
public void initialize(RenderManager rm, ViewPort vp) {
|
||||
if (this.rm == null){
|
||||
// First time called in OGL thread
|
||||
this.rm = rm;
|
||||
// reshapeInThread(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidatePendingFrames() {
|
||||
// NOTE: all pending read requests are invalid!
|
||||
for (Future<ByteBuffer> pendingRequest : pendingFrames) {
|
||||
pendingRequest.cancel(true);
|
||||
}
|
||||
pendingFrames.clear();
|
||||
bufferPool.clear();
|
||||
|
||||
// Populate buffer pool.
|
||||
int cap = bufferPool.remainingCapacity();
|
||||
for (int i = 0; i < cap; i++) {
|
||||
bufferPool.add(BufferUtils.createByteBuffer(1 * 1 * 4));
|
||||
}
|
||||
}
|
||||
|
||||
private void reshapeInThread(int width, int height) {
|
||||
invalidatePendingFrames();
|
||||
|
||||
for (FrameBuffer fb : fbs) {
|
||||
fb.dispose();
|
||||
}
|
||||
|
||||
fbs.clear();
|
||||
|
||||
for (int i = 0; i < NUM_FRAMES; i++) {
|
||||
FrameBuffer fb = new FrameBuffer(width, height, 1);
|
||||
fb.setDepthBuffer(Image.Format.Depth);
|
||||
fb.setColorBuffer(Image.Format.RGBA8);
|
||||
fbs.add(fb);
|
||||
}
|
||||
|
||||
synchronized (lock){
|
||||
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
|
||||
tx.translate(0, -img.getHeight());
|
||||
transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||
}
|
||||
|
||||
if (attachAsMain) {
|
||||
rm.notifyReshape(width, height);
|
||||
} else {
|
||||
for (ViewPort vp : viewPorts){
|
||||
vp.getCamera().resize(width, height, true);
|
||||
|
||||
// NOTE: Hack alert. This is done ONLY for custom framebuffers.
|
||||
// Main framebuffer should use RenderManager.notifyReshape().
|
||||
for (SceneProcessor sp : vp.getProcessors()){
|
||||
sp.reshape(vp, width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return rm != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preFrame(float tpf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postQueue(RenderQueue rq) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate(){
|
||||
// For "PaintMode.OnDemand" only.
|
||||
repaintRequest.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameBegin() {
|
||||
if (attachAsMain && rm != null){
|
||||
rm.getRenderer().setMainFrameBufferOverride(fbs.get(frameIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFrameEnd() {
|
||||
if (reshapeNeeded.getAndSet(false)) {
|
||||
reshapeInThread(newWidth, newHeight);
|
||||
} else {
|
||||
if (!checkVisibilityState()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (paintMode) {
|
||||
case Accelerated:
|
||||
case Repaint:
|
||||
readNextFrame();
|
||||
repaint();
|
||||
break;
|
||||
case OnRequest:
|
||||
if (repaintRequest.getAndSet(false)) {
|
||||
readNextFrame();
|
||||
repaint();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void postFrame(FrameBuffer out) {
|
||||
}
|
||||
|
||||
public void reshape(ViewPort vp, int w, int h) {
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileLock;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.experimental.categories.Category;
|
||||
|
||||
import com.jme3.IntegrationTest;
|
||||
import org.junit.Ignore;
|
||||
|
||||
/**
|
||||
* Integration test for {@link NativeLibraryLoader}.
|
||||
*
|
||||
* Note that it uses the file system.
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
@Ignore
|
||||
@Category(IntegrationTest.class)
|
||||
@FixMethodOrder
|
||||
public class NativeLibraryLoaderIT {
|
||||
|
||||
private File extractFolder;
|
||||
|
||||
static {
|
||||
NativeLibraryLoader.registerNativeLibrary("test", Platform.Linux64, "natives/linux64/libtest.so");
|
||||
NativeLibraryLoader.registerNativeLibrary("notexist", Platform.Linux64, "natives/linux64/libnotexist.so");
|
||||
NativeLibraryLoader.registerNativeLibrary("nativesfolder", Platform.Linux64, "natives/linux64/libnativesfolder.so");
|
||||
NativeLibraryLoader.registerNativeLibrary("jarroot", Platform.Linux64, "natives/linux64/libjarroot.so");
|
||||
NativeLibraryLoader.registerNativeLibrary("nullpath", Platform.Linux64, null);
|
||||
NativeLibraryLoader.registerNativeLibrary("jawt", Platform.Linux64, "whatever/doesnt/matter/libjawt.so");
|
||||
NativeLibraryLoader.registerNativeLibrary("asname", Platform.Linux64, "natives/linux64/libasname.so", "other.name");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
extractFolder = NativeLibraryLoader.getExtractionFolder();
|
||||
}
|
||||
|
||||
@Test(expected = UnsatisfiedLinkError.class)
|
||||
public void testRequiredNonExistentFile() {
|
||||
NativeLibraryLoader.loadNativeLibrary("notexist", true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalNonExistentFile() throws Exception {
|
||||
NativeLibraryLoader.loadNativeLibrary("notexist", false, false);
|
||||
}
|
||||
|
||||
@Test(expected = UnsatisfiedLinkError.class)
|
||||
public void testRequiredUnregisteredLibrary() {
|
||||
NativeLibraryLoader.loadNativeLibrary("unregistered", true, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalUnregisteredLibrary() {
|
||||
NativeLibraryLoader.loadNativeLibrary("unregistered", false, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLibraryNullPath() {
|
||||
NativeLibraryLoader.loadNativeLibrary("nullpath", true, false);
|
||||
NativeLibraryLoader.loadNativeLibrary("nullpath", false, false);
|
||||
}
|
||||
|
||||
private static void fudgeLastModifiedTime(File file) {
|
||||
// fudge last modified date to force extraction attempt
|
||||
long yesterdayModifiedtime = file.lastModified() - 24 * 60 * 60 * 1000;
|
||||
assertTrue(file.setLastModified(yesterdayModifiedtime));
|
||||
assertTrue(Math.abs(file.lastModified() - yesterdayModifiedtime) < 10000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentLastModifiedDates() throws IOException {
|
||||
File libFile = new File(extractFolder, "libtest.so");
|
||||
|
||||
assertTrue(libFile.createNewFile());
|
||||
assertTrue(libFile.exists() && libFile.length() == 0);
|
||||
|
||||
fudgeLastModifiedTime(libFile);
|
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false);
|
||||
assertTrue(libFile.length() == 12);
|
||||
|
||||
assertTrue(libFile.delete());
|
||||
assertTrue(!libFile.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLibraryInUse() throws IOException {
|
||||
File libFile = new File(extractFolder, "libtest.so");
|
||||
|
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false);
|
||||
assertTrue(libFile.exists());
|
||||
|
||||
fudgeLastModifiedTime(libFile);
|
||||
|
||||
FileOutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(libFile);
|
||||
FileLock lock = out.getChannel().lock();
|
||||
assertTrue(lock.isValid());
|
||||
|
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
libFile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSystemLibrary() {
|
||||
NativeLibraryLoader.loadNativeLibrary("jawt", true, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractAsName() {
|
||||
NativeLibraryLoader.loadNativeLibrary("asname", true, false);
|
||||
assertTrue(new File(extractFolder, "other.name").exists());
|
||||
assertTrue(new File(extractFolder, "other.name").delete());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomExtractFolder() {
|
||||
File customExtractFolder = new File(System.getProperty("java.io.tmpdir"), "jme3_test_tmp");
|
||||
if (!customExtractFolder.exists()) {
|
||||
assertTrue(customExtractFolder.mkdir());
|
||||
}
|
||||
|
||||
NativeLibraryLoader.setCustomExtractionFolder(customExtractFolder.getAbsolutePath());
|
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false);
|
||||
|
||||
assertTrue(new File(customExtractFolder, "libtest.so").exists());
|
||||
assertTrue(new File(customExtractFolder, "libtest.so").delete());
|
||||
assertTrue(!new File(customExtractFolder, "libtest.so").exists());
|
||||
|
||||
NativeLibraryLoader.setCustomExtractionFolder(null);
|
||||
NativeLibraryLoader.loadNativeLibrary("test", true, false);
|
||||
|
||||
assertTrue(new File(extractFolder, "libtest.so").exists());
|
||||
new File(extractFolder, "libtest.so").delete();
|
||||
customExtractFolder.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractFromNativesFolderInJar() {
|
||||
NativeLibraryLoader.loadNativeLibrary("nativesfolder", true, false);
|
||||
|
||||
File libFile = new File(extractFolder, "libnativesfolder.so");
|
||||
assertTrue(libFile.exists() && libFile.length() == 12);
|
||||
|
||||
libFile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractFromJarRoot() {
|
||||
NativeLibraryLoader.loadNativeLibrary("jarroot", true, false);
|
||||
|
||||
File libFile = new File(extractFolder, "libjarroot.so");
|
||||
assertTrue(libFile.exists() && libFile.length() == 12);
|
||||
|
||||
libFile.delete();
|
||||
}
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package jme3test.app;
|
||||
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.system.AppSettings;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
|
||||
public class TestCustomAppSettings {
|
||||
|
||||
private static final String APPSETTINGS_KEY = "JME_AppSettingsTest";
|
||||
|
||||
private static void assertEqual(Object a, Object b) {
|
||||
if (!a.equals(b)){
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests preference based AppSettings.
|
||||
*/
|
||||
private static void testPreferenceSettings() {
|
||||
AppSettings settings = new AppSettings(false);
|
||||
settings.putBoolean("TestBool", true);
|
||||
settings.putInteger("TestInt", 123);
|
||||
settings.putString("TestStr", "HelloWorld");
|
||||
settings.putFloat("TestFloat", 123.567f);
|
||||
settings.put("TestObj", new Mesh()); // Objects not supported by preferences
|
||||
|
||||
try {
|
||||
settings.save(APPSETTINGS_KEY);
|
||||
} catch (BackingStoreException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
AppSettings loadedSettings = new AppSettings(false);
|
||||
try {
|
||||
loadedSettings.load(APPSETTINGS_KEY);
|
||||
} catch (BackingStoreException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
assertEqual(loadedSettings.getBoolean("TestBool"), true);
|
||||
assertEqual(loadedSettings.getInteger("TestInt"), 123);
|
||||
assertEqual(loadedSettings.getString("TestStr"), "HelloWorld");
|
||||
assertEqual(loadedSettings.get("TestFloat"), 123.567f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Java properties file based AppSettings.
|
||||
*/
|
||||
private static void testFileSettings() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
AppSettings settings = new AppSettings(false);
|
||||
settings.putBoolean("TestBool", true);
|
||||
settings.putInteger("TestInt", 123);
|
||||
settings.putString("TestStr", "HelloWorld");
|
||||
settings.putFloat("TestFloat", 123.567f);
|
||||
settings.put("TestObj", new Mesh()); // Objects not supported by file settings
|
||||
|
||||
try {
|
||||
settings.save(baos);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
AppSettings loadedSettings = new AppSettings(false);
|
||||
try {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||||
loadedSettings.load(bais);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
assertEqual(loadedSettings.getBoolean("TestBool"), true);
|
||||
assertEqual(loadedSettings.getInteger("TestInt"), 123);
|
||||
assertEqual(loadedSettings.getString("TestStr"), "HelloWorld");
|
||||
assertEqual(loadedSettings.get("TestFloat"), 123.567f);
|
||||
}
|
||||
|
||||
public static void main(String[] args){
|
||||
testPreferenceSettings();
|
||||
testFileSettings();
|
||||
System.out.println("All OK");
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package jme3test.awt;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.shape.Box;
|
||||
import com.jme3.system.AppSettings;
|
||||
import com.jme3.system.awt.AwtPanel;
|
||||
import com.jme3.system.awt.AwtPanelsContext;
|
||||
import com.jme3.system.awt.PaintMode;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
public class TestAwtPanels extends SimpleApplication {
|
||||
|
||||
final private static CountDownLatch panelsAreReady = new CountDownLatch(1);
|
||||
private static TestAwtPanels app;
|
||||
private static AwtPanel panel, panel2;
|
||||
private static int panelsClosed = 0;
|
||||
|
||||
private static void createWindowForPanel(AwtPanel panel, int location){
|
||||
JFrame frame = new JFrame("Render Display " + location);
|
||||
frame.getContentPane().setLayout(new BorderLayout());
|
||||
frame.getContentPane().add(panel, BorderLayout.CENTER);
|
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
if (++panelsClosed == 2){
|
||||
app.stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
frame.pack();
|
||||
frame.setLocation(location, Toolkit.getDefaultToolkit().getScreenSize().height - 400);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
public static void main(String[] args){
|
||||
Logger.getLogger("com.jme3").setLevel(Level.WARNING);
|
||||
|
||||
app = new TestAwtPanels();
|
||||
app.setShowSettings(false);
|
||||
AppSettings settings = new AppSettings(true);
|
||||
settings.setCustomRenderer(AwtPanelsContext.class);
|
||||
settings.setFrameRate(60);
|
||||
app.setSettings(settings);
|
||||
app.start();
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable(){
|
||||
public void run(){
|
||||
/*
|
||||
* Sleep 2 seconds to ensure there's no race condition.
|
||||
* The sleep is not required for correctness.
|
||||
*/
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
final AwtPanelsContext ctx = (AwtPanelsContext) app.getContext();
|
||||
panel = ctx.createPanel(PaintMode.Accelerated);
|
||||
panel.setPreferredSize(new Dimension(400, 300));
|
||||
ctx.setInputSource(panel);
|
||||
|
||||
panel2 = ctx.createPanel(PaintMode.Accelerated);
|
||||
panel2.setPreferredSize(new Dimension(400, 300));
|
||||
|
||||
createWindowForPanel(panel, 300);
|
||||
createWindowForPanel(panel2, 700);
|
||||
/*
|
||||
* Both panels are ready.
|
||||
*/
|
||||
panelsAreReady.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
flyCam.setDragToRotate(true);
|
||||
|
||||
Box b = new Box(Vector3f.ZERO, 1, 1, 1);
|
||||
Geometry geom = new Geometry("Box", b);
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
|
||||
geom.setMaterial(mat);
|
||||
rootNode.attachChild(geom);
|
||||
/*
|
||||
* Wait until both AWT panels are ready.
|
||||
*/
|
||||
try {
|
||||
panelsAreReady.await();
|
||||
} catch (InterruptedException exception) {
|
||||
throw new RuntimeException("Interrupted while waiting for panels", exception);
|
||||
}
|
||||
|
||||
panel.attachTo(true, viewPort);
|
||||
guiViewPort.setClearFlags(true, true, true);
|
||||
panel2.attachTo(false, guiViewPort);
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 jme3test.stress;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.light.DirectionalLight;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.shape.Box;
|
||||
import com.jme3.system.AppSettings;
|
||||
|
||||
// Let's see if we can render 2500 batches in 60 fps.
|
||||
// We'll use 50 materials with various combinations of textures and colors
|
||||
// to make things wild.
|
||||
public class TestUniqueGeometries extends SimpleApplication {
|
||||
|
||||
private Material[] randomMaterials = new Material[50];
|
||||
|
||||
private String[] textureList = new String[] {
|
||||
"Blender/2.4x/textures/Concrete_Wall.PNG",
|
||||
"Blender/2.4x/textures/Grass_256.png",
|
||||
"Blender/2.4x/textures/SandDesert_StartTower.png",
|
||||
"Blender/2.4x/textures/Tar_Cracked.png",
|
||||
"Blender/2.4x/textures/WarningStrip.png",
|
||||
"Blender/2.4x/WoodCrate_lighter.png",
|
||||
"Interface/Logo/Monkey.jpg",
|
||||
"Interface/Logo/Monkey.png",
|
||||
"Models/Boat/boat.png",
|
||||
"Models/Ninja/Ninja.jpg",
|
||||
"Models/Tree/BarkColor.jpg",
|
||||
"Textures/Terrain/BrickWall/BrickWall.jpg",
|
||||
"Textures/Terrain/Pond/Pond.jpg",
|
||||
"Textures/Terrain/Pond/Pond_normal.png",
|
||||
"Textures/Terrain/Rock/Rock.PNG",
|
||||
"Textures/Terrain/Rock/Rock_normal.png",
|
||||
"Textures/Terrain/Rock2/rock.jpg",
|
||||
"Textures/Terrain/Rocky/RockyNormals.jpg",
|
||||
"Textures/Terrain/Rocky/RockyTexture.jpg",
|
||||
"Textures/Terrain/splat/alpha1.png",
|
||||
"Textures/Terrain/splat/alpha2.png",
|
||||
"Textures/Terrain/splat/alphamap.png",
|
||||
"Textures/Terrain/splat/alphamap2.png",
|
||||
"Textures/Terrain/splat/dirt.jpg",
|
||||
"Textures/Terrain/splat/dirt_normal.png",
|
||||
"Textures/Terrain/splat/fortress512.png",
|
||||
"Textures/Terrain/splat/grass.jpg",
|
||||
"Textures/Terrain/splat/grass_normal.jpg",
|
||||
"Textures/Terrain/splat/mountains128.png",
|
||||
"Textures/Terrain/splat/road.jpg",
|
||||
"Textures/Terrain/splat/road_normal.png",
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestUniqueGeometries app = new TestUniqueGeometries();
|
||||
AppSettings settings = new AppSettings(true);
|
||||
settings.putBoolean("GraphicsTrace", false);
|
||||
settings.putBoolean("GraphicsTiming", true);
|
||||
app.setSettings(settings);
|
||||
app.start();
|
||||
}
|
||||
|
||||
private void loadRandomMaterials() {
|
||||
for (int i = 0; i < randomMaterials.length; i++) {
|
||||
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
mat.setBoolean("VertexLighting", true);
|
||||
mat.setBoolean("UseMaterialColors", true);
|
||||
mat.setColor("Ambient", ColorRGBA.Black);
|
||||
mat.setColor("Diffuse", ColorRGBA.White);
|
||||
mat.setColor("Specular", ColorRGBA.White);
|
||||
mat.setFloat("Shininess", 32);
|
||||
mat.setTexture("DiffuseMap", assetManager.loadTexture(textureList[i % textureList.length]));
|
||||
randomMaterials[i] = mat;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
flyCam.setDragToRotate(true);
|
||||
|
||||
cam.setLocation(new Vector3f(22.717342f, 18.366547f, 22.043106f));
|
||||
cam.setRotation(new Quaternion(-0.11630201f, 0.8794429f, -0.27703872f, -0.36919326f));
|
||||
|
||||
DirectionalLight dl = new DirectionalLight();
|
||||
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
|
||||
rootNode.addLight(dl);
|
||||
|
||||
flyCam.setMoveSpeed(5);
|
||||
|
||||
loadRandomMaterials();
|
||||
|
||||
// Box box = new Box(1,1,1);
|
||||
|
||||
for (int y = -25; y < 25; y++) {
|
||||
for (int x = -25; x < 25; x++) {
|
||||
Material mat = randomMaterials[0]; // randomMaterials[FastMath.nextRandomInt(0, randomMaterials.length - 1)];
|
||||
|
||||
Box box = new Box(1,1,1);
|
||||
Geometry boxClone = new Geometry("box", box);
|
||||
boxClone.setMaterial(mat);
|
||||
|
||||
boxClone.setLocalTranslation(x * .5f, 0, y * .5f);
|
||||
boxClone.setLocalScale(.15f);
|
||||
boxClone.setMaterial(mat);
|
||||
rootNode.attachChild(boxClone);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
jme3-jinput/build.gradle
Normal file
12
jme3-jinput/build.gradle
Normal file
@ -0,0 +1,12 @@
|
||||
if (!hasProperty('mainClass')) {
|
||||
ext.mainClass = ''
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':jme3-core')
|
||||
compile project(':jme3-desktop')
|
||||
compile 'net.java.jinput:jinput:2.0.6'
|
||||
compile 'net.java.jinput:jinput-platform:2.0.6'
|
||||
|
||||
testCompile project(path: ':jme3-core', configuration: 'testOutput')
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.jme3.input.lwjgl;
|
||||
package com.jme3.input.jinput;
|
||||
|
||||
import com.jme3.input.AbstractJoystick;
|
||||
import com.jme3.input.DefaultJoystickAxis;
|
||||
@ -12,6 +12,7 @@ import com.jme3.input.JoystickCompatibilityMappings;
|
||||
import com.jme3.input.RawInputListener;
|
||||
import com.jme3.input.event.JoyAxisEvent;
|
||||
import com.jme3.input.event.JoyButtonEvent;
|
||||
import com.jme3.system.NativeLibraryLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -32,25 +33,13 @@ public class JInputJoyInput implements JoyInput {
|
||||
private JInputJoystick[] joysticks;
|
||||
private RawInputListener listener;
|
||||
|
||||
private Map<Controller, JInputJoystick> joystickIndex = new HashMap<Controller, JInputJoystick>();
|
||||
|
||||
public void setJoyRumble(int joyId, float amount){
|
||||
|
||||
if( joyId >= joysticks.length )
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
Controller c = joysticks[joyId].controller;
|
||||
for (Rumbler r : c.getRumblers()){
|
||||
r.rumble(amount);
|
||||
}
|
||||
}
|
||||
private final Map<Controller, JInputJoystick> joystickIndex = new HashMap<Controller, JInputJoystick>();
|
||||
|
||||
@Override
|
||||
public Joystick[] loadJoysticks(InputManager inputManager){
|
||||
ControllerEnvironment ce =
|
||||
ControllerEnvironment.getDefaultEnvironment();
|
||||
|
||||
Controller[] cs = ce.getControllers();
|
||||
|
||||
List<Joystick> list = new ArrayList<Joystick>();
|
||||
for( Controller c : ce.getControllers() ) {
|
||||
if (c.getType() == Controller.Type.KEYBOARD
|
||||
@ -82,10 +71,18 @@ public class JInputJoyInput implements JoyInput {
|
||||
return joysticks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
inited = true;
|
||||
|
||||
// Load natives
|
||||
String extractPath = NativeLibraryLoader.getExtractionFolder().getAbsolutePath();
|
||||
System.setProperty("net.java.games.input.librarypath", extractPath);
|
||||
NativeLibraryLoader.loadNativeLibrary("jinput", true, false);
|
||||
NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
ControllerEnvironment ce =
|
||||
ControllerEnvironment.getDefaultEnvironment();
|
||||
@ -151,25 +148,42 @@ public class JInputJoyInput implements JoyInput {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJoyRumble(int joyId, float amount) {
|
||||
|
||||
if (joyId >= joysticks.length) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
Controller c = joysticks[joyId].controller;
|
||||
for (Rumbler r : c.getRumblers()) {
|
||||
r.rumble(amount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
inited = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return inited;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputListener(RawInputListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getInputTimeNanos() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected class JInputJoystick extends AbstractJoystick {
|
||||
private static class JInputJoystick extends AbstractJoystick {
|
||||
|
||||
private JoystickAxis nullAxis;
|
||||
private final JoystickAxis nullAxis;
|
||||
private Controller controller;
|
||||
private JoystickAxis xAxis;
|
||||
private JoystickAxis yAxis;
|
||||
@ -216,8 +230,8 @@ public class JInputJoyInput implements JoyInput {
|
||||
String name = comp.getName();
|
||||
String original = id.getName();
|
||||
String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
|
||||
if( name != original ) {
|
||||
logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId);
|
||||
if (!logicalId.equals(original)) {
|
||||
logger.log(Level.FINE, "Remapped:{0} to:{1}", new Object[]{original, logicalId});
|
||||
}
|
||||
|
||||
JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(),
|
||||
@ -238,8 +252,8 @@ public class JInputJoyInput implements JoyInput {
|
||||
String name = comp.getName();
|
||||
String original = id.getName();
|
||||
String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original );
|
||||
if( name != original ) {
|
||||
logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId);
|
||||
if (!logicalId.equals(original)) {
|
||||
logger.log(Level.FINE, "Remapped:{0} to:{1}", new Object[]{original, logicalId});
|
||||
}
|
||||
|
||||
JoystickAxis axis = new DefaultJoystickAxis( getInputManager(),
|
@ -1,529 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.jme3.renderer.jogl;
|
||||
|
||||
import com.jme3.renderer.RendererException;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Image.Format;
|
||||
import com.jme3.texture.image.ColorSpace;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jogamp.opengl.GL;
|
||||
import com.jogamp.opengl.GL2;
|
||||
import com.jogamp.opengl.GL2ES2;
|
||||
import com.jogamp.opengl.GL2ES3;
|
||||
import com.jogamp.opengl.GL2GL3;
|
||||
import com.jogamp.opengl.GLContext;
|
||||
|
||||
public class TextureUtil {
|
||||
|
||||
private static boolean abgrToRgbaConversionEnabled = false;
|
||||
|
||||
public static int convertTextureFormat(Format fmt) {
|
||||
switch (fmt) {
|
||||
case Alpha8:
|
||||
return GL.GL_ALPHA;
|
||||
case Luminance8Alpha8:
|
||||
return GL.GL_LUMINANCE_ALPHA;
|
||||
case Luminance8:
|
||||
return GL.GL_LUMINANCE;
|
||||
case BGR8:
|
||||
case RGB8:
|
||||
case RGB565:
|
||||
return GL.GL_RGB;
|
||||
case RGB5A1:
|
||||
case RGBA8:
|
||||
return GL.GL_RGBA;
|
||||
case Depth:
|
||||
return GL2ES2.GL_DEPTH_COMPONENT;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unrecognized format: " + fmt);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GLImageFormat {
|
||||
|
||||
int internalFormat;
|
||||
int format;
|
||||
int dataType;
|
||||
boolean compressed;
|
||||
|
||||
public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) {
|
||||
this.internalFormat = internalFormat;
|
||||
this.format = format;
|
||||
this.dataType = dataType;
|
||||
this.compressed = compressed;
|
||||
}
|
||||
}
|
||||
|
||||
private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length];
|
||||
|
||||
private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){
|
||||
formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed);
|
||||
}
|
||||
|
||||
static {
|
||||
// Alpha formats
|
||||
setFormat(Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, false);
|
||||
|
||||
// Luminance formats
|
||||
setFormat(Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.Luminance16F, GL2.GL_LUMINANCE16F, GL.GL_LUMINANCE, GL.GL_HALF_FLOAT, false);
|
||||
setFormat(Format.Luminance32F, GL2.GL_LUMINANCE32F, GL.GL_LUMINANCE, GL.GL_FLOAT, false);
|
||||
|
||||
// Luminance alpha formats
|
||||
setFormat(Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.Luminance16FAlpha16F, GL2.GL_LUMINANCE_ALPHA16F, GL.GL_LUMINANCE_ALPHA, GL.GL_HALF_FLOAT, false);
|
||||
|
||||
// Depth formats
|
||||
setFormat(Format.Depth, GL2ES2.GL_DEPTH_COMPONENT, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT, false);
|
||||
setFormat(Format.Depth24, GL.GL_DEPTH_COMPONENT24, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false);
|
||||
setFormat(Format.Depth32, GL.GL_DEPTH_COMPONENT32, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false);
|
||||
setFormat(Format.Depth32F, GL2GL3.GL_DEPTH_COMPONENT32F, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, false);
|
||||
|
||||
// Depth stencil formats
|
||||
setFormat(Format.Depth24Stencil8, GL.GL_DEPTH24_STENCIL8, GL.GL_DEPTH_STENCIL, GL.GL_UNSIGNED_INT_24_8, false);
|
||||
|
||||
// RGB formats
|
||||
setFormat(Format.BGR8, GL.GL_RGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.ARGB8, GL.GL_RGBA8, GL.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8_REV, false);
|
||||
setFormat(Format.BGRA8, GL.GL_RGBA8, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.RGB8, GL.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.RGB16F, GL2ES2.GL_RGB16F, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
|
||||
setFormat(Format.RGB32F, GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false);
|
||||
|
||||
// Special RGB formats
|
||||
setFormat(Format.RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false);
|
||||
setFormat(Format.RGB9E5, GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false);
|
||||
setFormat(Format.RGB16F_to_RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
|
||||
setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false);
|
||||
|
||||
// RGBA formats
|
||||
setFormat(Format.ABGR8, GL.GL_RGBA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1, false);
|
||||
setFormat(Format.RGBA8, GL.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
|
||||
setFormat(Format.RGBA16F, GL2ES2.GL_RGBA16F, GL.GL_RGBA, GL.GL_HALF_FLOAT, false);
|
||||
setFormat(Format.RGBA32F, GL.GL_RGBA32F, GL.GL_RGBA, GL.GL_FLOAT, false);
|
||||
|
||||
// DXT formats
|
||||
setFormat(Format.DXT1, GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true);
|
||||
setFormat(Format.DXT1A, GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
|
||||
setFormat(Format.DXT3, GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
|
||||
setFormat(Format.DXT5, GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
|
||||
}
|
||||
|
||||
//sRGB formats
|
||||
private static final GLImageFormat sRGB_RGB8 = new GLImageFormat(GL2.GL_SRGB8,GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false);
|
||||
private static final GLImageFormat sRGB_RGBA8 = new GLImageFormat(GL.GL_SRGB8_ALPHA8,GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
|
||||
private static final GLImageFormat sRGB_Luminance8 = new GLImageFormat(GL2.GL_SLUMINANCE8,GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false);
|
||||
private static final GLImageFormat sRGB_LuminanceAlpha8 = new GLImageFormat(GL2.GL_SLUMINANCE8_ALPHA8,GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false);
|
||||
private static final GLImageFormat sRGB_BGR8 = new GLImageFormat(GL2.GL_SRGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false);
|
||||
private static final GLImageFormat sRGB_ABGR8 = new GLImageFormat(GL2.GL_SRGB8_ALPHA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false);
|
||||
|
||||
//FIXME cannot find GL_COMPRESSED_RGB_S3TC_DXT1,GL_COMPRESSED_RGBA_S3TC_DXT1,GL_COMPRESSED_RGB_S3TC_DXT3,GL_COMPRESSED_RGB_S3TC_DXT5 in JOGL used constants
|
||||
//GL_COMPRESSED_RGB_S3TC_DXT1 = 33776;
|
||||
//GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 33777;
|
||||
//GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 33778;
|
||||
//GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 33779;
|
||||
private static final GLImageFormat sRGB_DXT1 = new GLImageFormat(33776, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true);
|
||||
private static final GLImageFormat sRGB_DXT1A = new GLImageFormat( 33777, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
|
||||
private static final GLImageFormat sRGB_DXT3 = new GLImageFormat(33778, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
|
||||
private static final GLImageFormat sRGB_DXT5 = new GLImageFormat( 33779, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true);
|
||||
|
||||
|
||||
public static GLImageFormat getImageFormat(Format fmt, boolean isSrgb){
|
||||
GL gl = GLContext.getCurrentGL();
|
||||
switch (fmt){
|
||||
case ABGR8:
|
||||
if (!gl.isExtensionAvailable("GL_EXT_abgr") && !abgrToRgbaConversionEnabled) {
|
||||
setFormat(Format.ABGR8, GL.GL_RGBA, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false);
|
||||
abgrToRgbaConversionEnabled = true;
|
||||
}
|
||||
break;
|
||||
case BGR8:
|
||||
if (!gl.isExtensionAvailable("GL_VERSION_1_2") && !gl.isExtensionAvailable("EXT_bgra")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case DXT1:
|
||||
case DXT1A:
|
||||
case DXT3:
|
||||
case DXT5:
|
||||
if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc")) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case Depth:
|
||||
case Depth16:
|
||||
case Depth24:
|
||||
case Depth32:
|
||||
if (!gl.isExtensionAvailable("GL_VERSION_1_4") && !gl.isExtensionAvailable("ARB_depth_texture")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case Depth24Stencil8:
|
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case Luminance16F:
|
||||
case Luminance16FAlpha16F:
|
||||
case Luminance32F:
|
||||
if (!gl.isExtensionAvailable("GL_ARB_texture_float")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case RGB16F:
|
||||
case RGB32F:
|
||||
case RGBA16F:
|
||||
case RGBA32F:
|
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_ARB_texture_float")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case Depth32F:
|
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_NV_depth_buffer_float")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case RGB9E5:
|
||||
case RGB16F_to_RGB9E5:
|
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case RGB111110F:
|
||||
case RGB16F_to_RGB111110F:
|
||||
if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_packed_float")){
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(isSrgb){
|
||||
return getSrgbFormat(fmt);
|
||||
}
|
||||
return formatToGL[fmt.ordinal()];
|
||||
}
|
||||
|
||||
public static GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) {
|
||||
GLImageFormat glFmt = getImageFormat(fmt, isSrgb);
|
||||
if (glFmt == null) {
|
||||
throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware.");
|
||||
}
|
||||
return glFmt;
|
||||
}
|
||||
|
||||
private static GLImageFormat getSrgbFormat(Format fmt){
|
||||
switch (fmt){
|
||||
case RGB8 : return sRGB_RGB8;
|
||||
case RGBA8 : return sRGB_RGBA8;
|
||||
case BGR8 : return sRGB_BGR8;
|
||||
case ABGR8 : return sRGB_ABGR8;
|
||||
case Luminance8 : return sRGB_Luminance8;
|
||||
case Luminance8Alpha8 : return sRGB_LuminanceAlpha8;
|
||||
case DXT1 : return sRGB_DXT1;
|
||||
case DXT1A : return sRGB_DXT1A;
|
||||
case DXT3 : return sRGB_DXT3;
|
||||
case DXT5 : return sRGB_DXT5;
|
||||
default : Logger.getLogger(TextureUtil.class.getName()).log(Level.WARNING, "Format {0} has no sRGB equivalent, using linear format.", fmt.toString());
|
||||
return formatToGL[fmt.ordinal()];
|
||||
}
|
||||
}
|
||||
|
||||
public static void uploadTexture(Image image,
|
||||
int target,
|
||||
int index,
|
||||
int border,
|
||||
boolean linearizeSrgb){
|
||||
GL gl = GLContext.getCurrentGL();
|
||||
Image.Format fmt = image.getFormat();
|
||||
GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb);
|
||||
|
||||
ByteBuffer data;
|
||||
if (index >= 0 && image.getData() != null && image.getData().size() > 0){
|
||||
data = image.getData(index);
|
||||
}else{
|
||||
data = null;
|
||||
}
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
int depth = image.getDepth();
|
||||
|
||||
if (data != null) {
|
||||
if (abgrToRgbaConversionEnabled) {
|
||||
convertABGRtoRGBA(data);
|
||||
}
|
||||
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
|
||||
}
|
||||
|
||||
int[] mipSizes = image.getMipMapSizes();
|
||||
int pos = 0;
|
||||
// TODO: Remove unneccessary allocation
|
||||
if (mipSizes == null){
|
||||
if (data != null)
|
||||
mipSizes = new int[]{ data.capacity() };
|
||||
else
|
||||
mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
|
||||
}
|
||||
|
||||
boolean subtex = false;
|
||||
int samples = image.getMultiSamples();
|
||||
|
||||
for (int i = 0; i < mipSizes.length; i++){
|
||||
int mipWidth = Math.max(1, width >> i);
|
||||
int mipHeight = Math.max(1, height >> i);
|
||||
int mipDepth = Math.max(1, depth >> i);
|
||||
|
||||
if (data != null){
|
||||
data.position(pos);
|
||||
data.limit(pos + mipSizes[i]);
|
||||
}
|
||||
|
||||
if (glFmt.compressed && data != null){
|
||||
if (target == GL2ES2.GL_TEXTURE_3D){
|
||||
gl.getGL2ES2().glCompressedTexImage3D(target,
|
||||
i,
|
||||
glFmt.internalFormat,
|
||||
mipWidth,
|
||||
mipHeight,
|
||||
mipDepth,
|
||||
data.remaining(),
|
||||
border,
|
||||
data);
|
||||
}else{
|
||||
//all other targets use 2D: array, cubemap, 2d
|
||||
gl.glCompressedTexImage2D(target,
|
||||
i,
|
||||
glFmt.internalFormat,
|
||||
mipWidth,
|
||||
mipHeight,
|
||||
data.remaining(),
|
||||
border,
|
||||
data);
|
||||
}
|
||||
}else{
|
||||
if (target == GL2ES2.GL_TEXTURE_3D){
|
||||
gl.getGL2ES2().glTexImage3D(target,
|
||||
i,
|
||||
glFmt.internalFormat,
|
||||
mipWidth,
|
||||
mipHeight,
|
||||
mipDepth,
|
||||
border,
|
||||
glFmt.format,
|
||||
glFmt.dataType,
|
||||
data);
|
||||
}else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY){
|
||||
// prepare data for 2D array
|
||||
// or upload slice
|
||||
if (index == -1){
|
||||
gl.getGL2ES2().glTexImage3D(target,
|
||||
0,
|
||||
glFmt.internalFormat,
|
||||
mipWidth,
|
||||
mipHeight,
|
||||
image.getData().size(), //# of slices
|
||||
border,
|
||||
glFmt.format,
|
||||
glFmt.dataType,
|
||||
data);
|
||||
}else{
|
||||
gl.getGL2ES2().glTexSubImage3D(target,
|
||||
i, // level
|
||||
0, // xoffset
|
||||
0, // yoffset
|
||||
index, // zoffset
|
||||
width, // width
|
||||
height, // height
|
||||
1, // depth
|
||||
glFmt.format,
|
||||
glFmt.dataType,
|
||||
data);
|
||||
}
|
||||
}else{
|
||||
if (subtex){
|
||||
if (samples > 1){
|
||||
throw new IllegalStateException("Cannot update multisample textures");
|
||||
}
|
||||
|
||||
gl.glTexSubImage2D(target,
|
||||
i,
|
||||
0, 0,
|
||||
mipWidth, mipHeight,
|
||||
glFmt.format,
|
||||
glFmt.dataType,
|
||||
data);
|
||||
}else{
|
||||
if (samples > 1){
|
||||
if (gl.isGL2GL3()) {
|
||||
gl.getGL3().glTexImage2DMultisample(target,
|
||||
samples,
|
||||
glFmt.internalFormat,
|
||||
mipWidth,
|
||||
mipHeight,
|
||||
true);
|
||||
}
|
||||
} else {
|
||||
gl.glTexImage2D(target,
|
||||
i,
|
||||
glFmt.internalFormat,
|
||||
mipWidth,
|
||||
mipHeight,
|
||||
border,
|
||||
glFmt.format,
|
||||
glFmt.dataType,
|
||||
data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += mipSizes[i];
|
||||
}
|
||||
}
|
||||
|
||||
private static void convertABGRtoRGBA(ByteBuffer buffer) {
|
||||
|
||||
for (int i = 0; i < buffer.capacity(); i++) {
|
||||
|
||||
int a = buffer.get(i++);
|
||||
int b = buffer.get(i++);
|
||||
int g = buffer.get(i++);
|
||||
int r = buffer.get(i);
|
||||
|
||||
buffer.put(i - 3, (byte) r);
|
||||
buffer.put(i - 2, (byte) g);
|
||||
buffer.put(i - 1, (byte) b);
|
||||
buffer.put(i, (byte) a);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 image,
|
||||
int target,
|
||||
int index,
|
||||
int x,
|
||||
int y,
|
||||
boolean linearizeSrgb) {
|
||||
GL gl = GLContext.getCurrentGL();
|
||||
Image.Format fmt = image.getFormat();
|
||||
GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb);
|
||||
|
||||
ByteBuffer data = null;
|
||||
if (index >= 0 && image.getData() != null && image.getData().size() > 0) {
|
||||
data = image.getData(index);
|
||||
}
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
int depth = image.getDepth();
|
||||
|
||||
if (data != null) {
|
||||
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
|
||||
}
|
||||
|
||||
int[] mipSizes = image.getMipMapSizes();
|
||||
int pos = 0;
|
||||
|
||||
// TODO: Remove unneccessary allocation
|
||||
if (mipSizes == null){
|
||||
if (data != null) {
|
||||
mipSizes = new int[]{ data.capacity() };
|
||||
} else {
|
||||
mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
|
||||
}
|
||||
}
|
||||
|
||||
int samples = image.getMultiSamples();
|
||||
|
||||
for (int i = 0; i < mipSizes.length; i++){
|
||||
int mipWidth = Math.max(1, width >> i);
|
||||
int mipHeight = Math.max(1, height >> i);
|
||||
int mipDepth = Math.max(1, depth >> i);
|
||||
|
||||
if (data != null){
|
||||
data.position(pos);
|
||||
data.limit(pos + mipSizes[i]);
|
||||
}
|
||||
|
||||
// to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each
|
||||
// gl*Image call in an attempt to unclutter things a bit
|
||||
pos += mipSizes[i];
|
||||
|
||||
int glFmtInternal = glFmt.internalFormat;
|
||||
int glFmtFormat = glFmt.format;
|
||||
int glFmtDataType = glFmt.dataType;
|
||||
|
||||
if (glFmt.compressed && data != null){
|
||||
if (target == GL2ES2.GL_TEXTURE_3D){
|
||||
gl.getGL2ES2().glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data.limit(), data);
|
||||
continue;
|
||||
}
|
||||
|
||||
// all other targets use 2D: array, cubemap, 2d
|
||||
gl.getGL2ES2().glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data.limit(), data);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (target == GL2ES2.GL_TEXTURE_3D){
|
||||
gl.getGL2ES2().glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (samples > 1){
|
||||
throw new IllegalStateException("Cannot update multisample textures");
|
||||
}
|
||||
|
||||
gl.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,5 +5,8 @@ if (!hasProperty('mainClass')) {
|
||||
dependencies {
|
||||
compile project(':jme3-core')
|
||||
compile project(':jme3-desktop')
|
||||
compile project(':jme3-jinput')
|
||||
compile 'org.lwjgl.lwjgl:lwjgl:2.9.3'
|
||||
|
||||
testCompile project(path: ':jme3-core', configuration: 'testOutput')
|
||||
}
|
||||
|
@ -36,9 +36,9 @@ import com.jme3.input.JoyInput;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.TouchInput;
|
||||
import com.jme3.input.lwjgl.JInputJoyInput;
|
||||
import com.jme3.input.lwjgl.LwjglKeyInput;
|
||||
import com.jme3.input.lwjgl.LwjglMouseInput;
|
||||
import com.jme3.input.jinput.JInputJoyInput;
|
||||
import com.jme3.system.AppSettings;
|
||||
import com.jme3.system.JmeSystem;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
package com.jme3.system.lwjgl;
|
||||
|
||||
import com.jme3.input.lwjgl.JInputJoyInput;
|
||||
import com.jme3.input.jinput.JInputJoyInput;
|
||||
import com.jme3.input.lwjgl.LwjglKeyInput;
|
||||
import com.jme3.input.lwjgl.LwjglMouseInput;
|
||||
import com.jme3.renderer.Renderer;
|
||||
@ -53,7 +53,6 @@ import com.jme3.renderer.opengl.GLTiming;
|
||||
import com.jme3.renderer.opengl.GLTimingState;
|
||||
import com.jme3.renderer.opengl.GLTracer;
|
||||
import com.jme3.system.*;
|
||||
import java.io.File;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
@ -163,17 +162,18 @@ public abstract class LwjglContext implements JmeContext {
|
||||
if (JmeSystem.isLowPermissions()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String extractPath = NativeLibraryLoader.getExtractionFolder().getAbsolutePath();
|
||||
|
||||
if ("LWJGL".equals(settings.getAudioRenderer())) {
|
||||
NativeLibraryLoader.loadNativeLibrary("openal", true);
|
||||
}
|
||||
if (settings.useJoysticks()) {
|
||||
NativeLibraryLoader.loadNativeLibrary("jinput", true);
|
||||
NativeLibraryLoader.loadNativeLibrary("jinput-dx8", true);
|
||||
}
|
||||
if (NativeLibraryLoader.isUsingNativeBullet()) {
|
||||
NativeLibraryLoader.loadNativeLibrary("bulletjme", true);
|
||||
}
|
||||
NativeLibraryLoader.loadNativeLibrary("lwjgl", true);
|
||||
|
||||
System.setProperty("org.lwjgl.librarypath", extractPath);
|
||||
NativeLibraryLoader.loadNativeLibrary("lwjgl", true, false);
|
||||
}
|
||||
protected int getNumSamplesToUse() {
|
||||
int samples = 0;
|
||||
|
@ -148,12 +148,16 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable {
|
||||
super.internalDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(){
|
||||
loadNatives();
|
||||
logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion());
|
||||
initInThread();
|
||||
while (!needClose.get()){
|
||||
while (true) {
|
||||
runLoop();
|
||||
if (needClose.get()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
deinitInThread();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ def lwjglVersion = '3.0.0b'
|
||||
dependencies {
|
||||
compile project(':jme3-core')
|
||||
compile project(':jme3-desktop')
|
||||
compile project(':jme3-jinput')
|
||||
|
||||
compile "org.lwjgl:lwjgl:${lwjglVersion}"
|
||||
compile "org.lwjgl:lwjgl-platform:${lwjglVersion}:natives-windows"
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
package com.jme3.system.lwjgl;
|
||||
|
||||
import com.jme3.input.jinput.JInputJoyInput;
|
||||
import com.jme3.input.lwjgl.GlfwJoystickInput;
|
||||
import com.jme3.input.lwjgl.GlfwKeyInput;
|
||||
import com.jme3.input.lwjgl.GlfwMouseInput;
|
||||
@ -74,7 +75,7 @@ public abstract class LwjglContext implements JmeContext {
|
||||
protected Renderer renderer;
|
||||
protected GlfwKeyInput keyInput;
|
||||
protected GlfwMouseInput mouseInput;
|
||||
protected GlfwJoystickInput joyInput;
|
||||
protected JInputJoyInput joyInput;
|
||||
protected Timer timer;
|
||||
protected SystemListener listener;
|
||||
|
||||
|
@ -36,6 +36,7 @@ import com.jme3.input.JoyInput;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.TouchInput;
|
||||
import com.jme3.input.jinput.JInputJoyInput;
|
||||
import com.jme3.input.lwjgl.GlfwJoystickInput;
|
||||
import com.jme3.input.lwjgl.GlfwKeyInput;
|
||||
import com.jme3.input.lwjgl.GlfwMouseInput;
|
||||
@ -465,7 +466,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable {
|
||||
|
||||
public JoyInput getJoyInput() {
|
||||
if (joyInput == null) {
|
||||
joyInput = new GlfwJoystickInput();
|
||||
joyInput = new JInputJoyInput();
|
||||
}
|
||||
return joyInput;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ if (!hasProperty('mainClass')) {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir 'src/main/java'
|
||||
srcDir 'src/ogre/java'
|
||||
srcDir 'src/fbx/java'
|
||||
srcDir 'src/xml/java'
|
||||
|
@ -150,7 +150,7 @@ public class FbxLoader implements AssetLoader {
|
||||
private void loadData(InputStream stream) throws IOException {
|
||||
FbxFile scene = FbxReader.readFBX(stream);
|
||||
|
||||
FbxDump.dumpFile(scene);
|
||||
// FbxDump.dumpFile(scene);
|
||||
|
||||
// TODO: Load FBX object templates
|
||||
|
||||
@ -346,33 +346,33 @@ public class FbxLoader implements AssetLoader {
|
||||
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);
|
||||
// // Find the spatial that has the skeleton for this limb.
|
||||
// FbxLimbNode limbNode = (FbxLimbNode) pair.node;
|
||||
// Bone bone = limbNode.getJmeBone();
|
||||
// Spatial jmeSpatial = limbNode.getSkeletonRoot().getJmeObject();
|
||||
// Skeleton skeleton = limbNode.getSkeletonRoot().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);
|
||||
|
@ -31,6 +31,8 @@
|
||||
*/
|
||||
package com.jme3.scene.plugins.fbx.anim;
|
||||
|
||||
import com.jme3.math.Matrix4f;
|
||||
|
||||
public class FbxAnimUtil {
|
||||
/**
|
||||
* Conversion factor from FBX animation time unit to seconds.
|
||||
@ -41,4 +43,12 @@ public class FbxAnimUtil {
|
||||
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";
|
||||
|
||||
public static Matrix4f toMatrix4(double[] matrixData) {
|
||||
float[] matrixDataFloat = new float[16];
|
||||
for (int i = 0; i < matrixData.length; i++) {
|
||||
matrixDataFloat[i] = (float) matrixData[i];
|
||||
}
|
||||
return new Matrix4f(matrixDataFloat);
|
||||
}
|
||||
}
|
||||
|
@ -56,31 +56,25 @@ public class FbxBindPose extends FbxObject<Map<FbxId, Matrix4f>> {
|
||||
}
|
||||
|
||||
FbxId node = null;
|
||||
float[] matData = null;
|
||||
double[] 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);
|
||||
matData = (double[]) e.properties.get(0);
|
||||
|
||||
if (matDataDoubles.length != 16) {
|
||||
if (matData.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];
|
||||
+ matData.length + ". Data is corrupt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (node != null && matData != null) {
|
||||
Matrix4f matrix = new Matrix4f(matData);
|
||||
bindPose.put(node, matrix);
|
||||
bindPose.put(node, FbxAnimUtil.toMatrix4(matData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
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.obj.FbxObject;
|
||||
import java.util.logging.Level;
|
||||
@ -45,6 +46,10 @@ public class FbxCluster extends FbxObject {
|
||||
private double[] weights;
|
||||
private FbxLimbNode limb;
|
||||
|
||||
private Matrix4f transformMatrix;
|
||||
private Matrix4f transformLinkMatrix;
|
||||
private Matrix4f transformAssociateModelMatrix;
|
||||
|
||||
public FbxCluster(AssetManager assetManager, String sceneFolderName) {
|
||||
super(assetManager, sceneFolderName);
|
||||
}
|
||||
@ -57,6 +62,15 @@ public class FbxCluster extends FbxObject {
|
||||
indexes = (int[]) e.properties.get(0);
|
||||
} else if (e.id.equals("Weights")) {
|
||||
weights = (double[]) e.properties.get(0);
|
||||
} else if (e.id.equals("Transform")) {
|
||||
double[] data = (double[]) e.properties.get(0);
|
||||
transformMatrix = FbxAnimUtil.toMatrix4(data);
|
||||
} else if (e.id.equals("TransformLink")) {
|
||||
double[] data = (double[]) e.properties.get(0);
|
||||
transformLinkMatrix = FbxAnimUtil.toMatrix4(data);
|
||||
} else if (e.id.equals("TransformAssociateModel")) {
|
||||
double[] data = (double[]) e.properties.get(0);
|
||||
transformAssociateModelMatrix = FbxAnimUtil.toMatrix4(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,6 +100,18 @@ public class FbxCluster extends FbxObject {
|
||||
return;
|
||||
}
|
||||
limb = (FbxLimbNode) object;
|
||||
|
||||
System.out.println(" ----- for limb: " + limb.getName());
|
||||
System.out.println(" transform : " + transformMatrix);
|
||||
System.out.println(" transform link : " + transformLinkMatrix);
|
||||
System.out.println(" transform associate model : " + transformAssociateModelMatrix);
|
||||
|
||||
// Invert(Invert(TransformLinkMatrix) * TransformMatrix * Geometry)
|
||||
Matrix4f accumMatrix = transformLinkMatrix.invert();
|
||||
accumMatrix.multLocal(transformMatrix);
|
||||
accumMatrix.invertLocal();
|
||||
|
||||
System.out.println(" limb bind pose : " + accumMatrix);
|
||||
} else {
|
||||
unsupportedConnectObject(object);
|
||||
}
|
||||
|
@ -31,64 +31,22 @@
|
||||
*/
|
||||
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;
|
||||
protected FbxNode skeletonRoot;
|
||||
|
||||
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 FbxNode getSkeletonRoot() {
|
||||
return skeletonRoot;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
public void setSkeletonRoot(FbxNode skeletonRoot) {
|
||||
this.skeletonRoot = skeletonRoot;
|
||||
}
|
||||
}
|
||||
|
112
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkeleton.java
Executable file
112
jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkeleton.java
Executable file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.scene.plugins.fbx.node.FbxNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Similar to {@link Skeleton jME skeleton} except
|
||||
* contains {@link FbxLimbNode limb nodes}.
|
||||
*
|
||||
* This is used to determine the bone indices (for assigning clusters to meshes)
|
||||
* as well as the limb hierarchy when creating the jME3 Skeleton.
|
||||
*
|
||||
* @author Kirill Vainer
|
||||
*/
|
||||
public class FbxSkeleton {
|
||||
|
||||
FbxLimbNode[] rootLimbs;
|
||||
FbxLimbNode[] allLimbs;
|
||||
HashMap<FbxLimbNode, Integer> limbToIndexMap = new HashMap<FbxLimbNode, Integer>();
|
||||
|
||||
private FbxSkeleton() {
|
||||
}
|
||||
|
||||
public static void populateSkeletonData(FbxNode skeletonRoot) {
|
||||
// if (skeletonRoot instanceof FbxLimbNode) {
|
||||
// throw new UnsupportedOperationException("Limb node cannot be a skeleton root");
|
||||
// }
|
||||
//
|
||||
// FbxSkeleton skeleton = new FbxSkeleton();
|
||||
// skeleton.scanLimbs(skeletonRoot);
|
||||
// skeletonRoot.setFbxSkeleton(skeleton);
|
||||
}
|
||||
|
||||
private void scanLimbs(FbxNode skeletonRoot, FbxLimbNode limb, List<FbxLimbNode> limbList) {
|
||||
// limb.skeletonRoot = skeletonRoot;
|
||||
// limbList.add(limb);
|
||||
// for (FbxNode child : limb.getChildren()) {
|
||||
// if (child instanceof FbxLimbNode) {
|
||||
// FbxLimbNode childLimb = (FbxLimbNode) child;
|
||||
// scanLimbs(skeletonRoot, childLimb, limbList);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private void scanLimbs(FbxNode skeletonRoot) {
|
||||
List<FbxLimbNode> limbList = new ArrayList<FbxLimbNode>();
|
||||
List<FbxLimbNode> rootList = new ArrayList<FbxLimbNode>();
|
||||
|
||||
for (FbxNode child : skeletonRoot.getChildren()) {
|
||||
if (child instanceof FbxLimbNode) {
|
||||
FbxLimbNode limb = (FbxLimbNode) child;
|
||||
rootList.add(limb);
|
||||
scanLimbs(skeletonRoot, limb, limbList);
|
||||
}
|
||||
}
|
||||
|
||||
allLimbs = limbList.toArray(new FbxLimbNode[0]);
|
||||
rootLimbs = rootList.toArray(new FbxLimbNode[0]);
|
||||
|
||||
for (int i = 0; i < allLimbs.length; i++) {
|
||||
limbToIndexMap.put(allLimbs[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
public int getLimbIndex(FbxLimbNode limbNode) {
|
||||
return limbToIndexMap.get(limbNode);
|
||||
}
|
||||
|
||||
public FbxLimbNode getLimb(int index) {
|
||||
return allLimbs[index];
|
||||
}
|
||||
|
||||
public FbxLimbNode[] getRootLimbs() {
|
||||
return rootLimbs;
|
||||
}
|
||||
|
||||
}
|
@ -31,24 +31,94 @@
|
||||
*/
|
||||
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.math.Transform;
|
||||
import com.jme3.scene.plugins.fbx.node.FbxNode;
|
||||
import com.jme3.scene.plugins.fbx.obj.FbxObject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> {
|
||||
public class FbxSkinDeformer extends FbxObject<Skeleton> {
|
||||
|
||||
private FbxNode skeletonRoot;
|
||||
private final List<FbxCluster> clusters = new ArrayList<FbxCluster>();
|
||||
|
||||
public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) {
|
||||
super(assetManager, sceneFolderName);
|
||||
}
|
||||
|
||||
private boolean isHierarchyCompatible(Bone thisBone, Bone otherBone) {
|
||||
Transform thisTransform = thisBone.getBindInverseTransform();
|
||||
Transform otherTransform = otherBone.getBindInverseTransform();
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if both skin deformers can share the same
|
||||
* Skeleton object and hence the same SkeletonControl / AnimControl.
|
||||
*
|
||||
* @param skinDeformer The skin deformer to test compatibility against.
|
||||
* @return True if the skeletons are identical and can be shared, false
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isCompatible(FbxSkinDeformer skinDeformer) {
|
||||
Skeleton thisSkeleton = this.getJmeObject();
|
||||
Skeleton otherSkeleton = skinDeformer.getJmeObject();
|
||||
Bone[] thisRoots = thisSkeleton.getRoots();
|
||||
Bone[] otherRoots = otherSkeleton.getRoots();
|
||||
for (int i = 0; i < thisRoots.length; i++) {
|
||||
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root FbxNode containing the skeleton.
|
||||
*
|
||||
* The node should have one or more FbxLimbNodes which are
|
||||
* the root limbs of the skeleton structure.
|
||||
*
|
||||
* This is null until prepareSkeletonData() is called.
|
||||
*
|
||||
* @return The root node containing the skeleton.
|
||||
*/
|
||||
public FbxNode getSkeletonRoot() {
|
||||
return skeletonRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the skeleton from the skin deformer.
|
||||
*
|
||||
* The Skeleton hierarchy is derived via the {@link #getSkeletonRoot() skeleton root}
|
||||
* whereas the bind poses for the bones is derived from the
|
||||
* {@link #getClusters() clusters}.
|
||||
*
|
||||
* FbxLimbNode.prepareSkeletonData() must have been called first
|
||||
* The bone's bind pose depends on each cluster's TransformLinkMatrix
|
||||
* and TransformMatrix.
|
||||
* The bone's bind pose is derived as follows:
|
||||
* <code><pre>
|
||||
* Invert(Invert(TransformLinkMatrix) * TransformMatrix * Geometry)
|
||||
* </code></pre>
|
||||
*
|
||||
* @return The skeleton as described by this skin deformer.
|
||||
*/
|
||||
@Override
|
||||
protected List<FbxCluster> toJmeObject() {
|
||||
protected Skeleton toJmeObject() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clusters attached to this skin deformer.
|
||||
*
|
||||
* @return The skin deformer's clusters.
|
||||
*/
|
||||
public List<FbxCluster> getClusters() {
|
||||
return clusters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void connectObject(FbxObject object) {
|
||||
if (object instanceof FbxCluster) {
|
||||
@ -62,5 +132,5 @@ public class FbxSkinDeformer extends FbxObject<List<FbxCluster>> {
|
||||
public void connectObjectProperty(FbxObject object, String property) {
|
||||
unsupportedConnectObjectProperty(object, property);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ 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.Matrix4f;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
@ -98,6 +99,7 @@ public final class FbxToJmeTrack {
|
||||
}
|
||||
|
||||
private static void applyInverse(Vector3f translation, Quaternion rotation, Vector3f scale, Transform inverseBindPose) {
|
||||
/*
|
||||
Transform t = new Transform();
|
||||
t.setTranslation(translation);
|
||||
t.setRotation(rotation);
|
||||
@ -111,6 +113,24 @@ public final class FbxToJmeTrack {
|
||||
if (scale != null) {
|
||||
t.getScale(scale);
|
||||
}
|
||||
*/
|
||||
|
||||
Matrix4f mat = new Matrix4f();
|
||||
mat.setTranslation(translation);
|
||||
mat.setRotationQuaternion(rotation);
|
||||
if (scale != null) {
|
||||
mat.setScale(scale);
|
||||
}
|
||||
|
||||
Matrix4f mat2 = inverseBindPose.toTransformMatrix();
|
||||
mat2.multLocal(mat);
|
||||
mat = mat2;
|
||||
|
||||
mat.toTranslationVector(translation);
|
||||
mat.toRotationQuat(rotation);
|
||||
if (scale != null) {
|
||||
mat.toScaleVector(scale);
|
||||
}
|
||||
}
|
||||
|
||||
private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) {
|
||||
@ -154,7 +174,7 @@ public final class FbxToJmeTrack {
|
||||
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();
|
||||
rotations[i].negate();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -31,6 +31,9 @@
|
||||
*/
|
||||
package com.jme3.scene.plugins.fbx.file;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Array;
|
||||
@ -88,6 +91,28 @@ public final class FbxDump {
|
||||
dumpFile(file, System.out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump FBX to standard output.
|
||||
*
|
||||
* @param file the file to dump.
|
||||
*/
|
||||
public static void dumpFile(String file) {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
FbxFile scene = FbxReader.readFBX(in);
|
||||
FbxDump.dumpFile(scene);
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump FBX to the given output stream.
|
||||
*
|
||||
|
@ -307,7 +307,11 @@ public class FbxMaterial extends FbxObject<Material> {
|
||||
if (useAlphaBlend) {
|
||||
// No idea if this is a transparent or translucent model, gotta guess..
|
||||
mat.setTransparent(true);
|
||||
mat.setFloat("AlphaDiscardThreshold", 0.01f);
|
||||
|
||||
// Commenting this out for now. It causes extra shaders to be
|
||||
// used and is less efficient due to usage of "discard".
|
||||
// mat.setFloat("AlphaDiscardThreshold", 0.01f);
|
||||
|
||||
mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
|
||||
}
|
||||
|
||||
|
@ -111,43 +111,44 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
// if (boneIndices == null) {
|
||||
// boneIndices = new ArrayList[positions.length];
|
||||
// boneWeights = new ArrayList[positions.length];
|
||||
// }
|
||||
//
|
||||
// FbxLimbNode limb = cluster.getLimb();
|
||||
// Bone bone = limb.getJmeBone();
|
||||
// Skeleton skeleton = limb.getSkeletonRoot().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.");
|
||||
logger.log(Level.WARNING, "This mesh already has a skin "
|
||||
+ "deformer attached: {0}. Ignoring.", this);
|
||||
return;
|
||||
}
|
||||
skinDeformer = (FbxSkinDeformer) object;
|
||||
@ -209,7 +210,7 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
|
||||
protected IntMap<Mesh> toJmeObject() {
|
||||
// Load clusters from SkinDeformer
|
||||
if (skinDeformer != null) {
|
||||
for (FbxCluster cluster : skinDeformer.getJmeObject()) {
|
||||
for (FbxCluster cluster : skinDeformer.getClusters()) {
|
||||
applyCluster(cluster);
|
||||
}
|
||||
}
|
||||
@ -237,7 +238,7 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
|
||||
|
||||
if (jmeMeshes.size() == 0) {
|
||||
// When will this actually happen? Not sure.
|
||||
logger.log(Level.WARNING, "Empty FBX mesh found (unusual).");
|
||||
logger.log(Level.WARNING, "Empty FBX mesh found: {0} (unusual).", this);
|
||||
}
|
||||
|
||||
// IMPORTANT: If we have a -1 entry, those are triangles
|
||||
@ -245,7 +246,8 @@ public final class FbxMesh extends FbxNodeAttribute<IntMap<Mesh>> {
|
||||
// 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.");
|
||||
+ "indices: {0} (unusual) - "
|
||||
+ "they will use material index 0.", this);
|
||||
}
|
||||
|
||||
return jmeMeshes;
|
||||
|
@ -53,6 +53,7 @@ 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.FbxSkeleton;
|
||||
import com.jme3.scene.plugins.fbx.anim.FbxSkinDeformer;
|
||||
import com.jme3.scene.plugins.fbx.file.FbxElement;
|
||||
import com.jme3.scene.plugins.fbx.material.FbxImage;
|
||||
@ -108,7 +109,7 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
/**
|
||||
* For FBX nodes that contain a skeleton (i.e. FBX limbs).
|
||||
*/
|
||||
protected Skeleton skeleton;
|
||||
protected FbxSkeleton skeleton;
|
||||
|
||||
protected final Transform jmeWorldNodeTransform = new Transform();
|
||||
protected final Transform jmeLocalNodeTransform = new Transform();
|
||||
@ -293,7 +294,8 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
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);
|
||||
logger.log(Level.WARNING, "Unsupported user data type: {0}. "
|
||||
+ "Ignoring.", userDataType);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -329,6 +331,9 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
// Material index does not exist. Create default material.
|
||||
jmeMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
jmeMat.setReceivesShadows(true);
|
||||
logger.log(Level.WARNING, "Material index {0} is undefined in: {1}. "
|
||||
+ "Will use default material.",
|
||||
new Object[]{materialIndex, this});
|
||||
} else {
|
||||
FbxMaterial fbxMat = materials.get(materialIndex);
|
||||
jmeMat = fbxMat.getJmeObject();
|
||||
@ -376,11 +381,11 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
FbxNode preferredParent = null;
|
||||
|
||||
if (deformer != null) {
|
||||
for (FbxCluster cluster : deformer.getJmeObject()) {
|
||||
for (FbxCluster cluster : deformer.getClusters()) {
|
||||
FbxLimbNode limb = cluster.getLimb();
|
||||
if (preferredParent == null) {
|
||||
preferredParent = limb.getSkeletonHolder();
|
||||
} else if (preferredParent != limb.getSkeletonHolder()) {
|
||||
preferredParent = limb.getSkeletonRoot();
|
||||
} else if (preferredParent != limb.getSkeletonRoot()) {
|
||||
logger.log(Level.WARNING, "A mesh is being deformed by multiple skeletons. "
|
||||
+ "Only one skeleton will work, ignoring other skeletons.");
|
||||
}
|
||||
@ -400,7 +405,8 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
|
||||
if (jmeMeshes == null || jmeMeshes.size() == 0) {
|
||||
// No meshes found on FBXMesh (??)
|
||||
logger.log(Level.WARNING, "No meshes could be loaded. Creating empty node.");
|
||||
logger.log(Level.WARNING, "No meshes could be loaded: {0}. "
|
||||
+ "Creating empty node.", this);
|
||||
spatial = new Node(getName() + "-node");
|
||||
} else {
|
||||
// Multiple jME3 geometries required for a single FBXMesh.
|
||||
@ -437,7 +443,7 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
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.");
|
||||
"The model {1} may appear distorted.", this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -479,8 +485,8 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
if (fbxNode.skeleton != null) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode);
|
||||
System.out.println("created skeleton: " + fbxNode.skeleton);
|
||||
// fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode);
|
||||
// System.out.println("created skeleton: " + fbxNode.skeleton);
|
||||
}
|
||||
}
|
||||
|
||||
@ -518,19 +524,19 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
@ -543,10 +549,18 @@ public class FbxNode extends FbxObject<Spatial> {
|
||||
// return limb;
|
||||
// }
|
||||
|
||||
public Skeleton getJmeSkeleton() {
|
||||
// public Skeleton getJmeSkeleton() {
|
||||
// return skeleton;
|
||||
// }
|
||||
|
||||
public FbxSkeleton getFbxSkeleton() {
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
public void setFbxSkeleton(FbxSkeleton skeleton) {
|
||||
this.skeleton = skeleton;
|
||||
}
|
||||
|
||||
public List<FbxNode> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.mesh.IndexBuffer;
|
||||
import com.jme3.scene.mesh.IndexIntBuffer;
|
||||
import com.jme3.scene.mesh.IndexShortBuffer;
|
||||
import com.jme3.scene.plugins.triangulator.EarClippingTriangulator;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.util.IntMap;
|
||||
import java.nio.ByteBuffer;
|
||||
@ -172,23 +173,40 @@ public final class IrUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpPoly(IrPolygon polygon) {
|
||||
System.out.println("Polygon with " + polygon.vertices.length + " vertices");
|
||||
for (IrVertex vertex : polygon.vertices) {
|
||||
System.out.println("\t" + vertex.pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert mesh from quads / triangles to triangles only.
|
||||
*/
|
||||
public static void triangulate(IrMesh mesh) {
|
||||
List<IrPolygon> newPolygons = new ArrayList<IrPolygon>(mesh.polygons.length);
|
||||
EarClippingTriangulator triangulator = new EarClippingTriangulator();
|
||||
for (IrPolygon inputPoly : mesh.polygons) {
|
||||
if (inputPoly.vertices.length == 4) {
|
||||
int numVertices = inputPoly.vertices.length;
|
||||
|
||||
if (numVertices < 3) {
|
||||
// point / edge
|
||||
logger.log(Level.WARNING, "Point or edge encountered. Ignoring.");
|
||||
} else if (numVertices == 3) {
|
||||
// triangle
|
||||
newPolygons.add(inputPoly);
|
||||
} else if (numVertices == 4) {
|
||||
// quad
|
||||
IrPolygon[] tris = quadToTri(inputPoly);
|
||||
newPolygons.add(tris[0]);
|
||||
newPolygons.add(tris[1]);
|
||||
} else if (inputPoly.vertices.length == 3) {
|
||||
newPolygons.add(inputPoly);
|
||||
} else {
|
||||
// N-gon. We have to ignore it..
|
||||
logger.log(Level.WARNING, "N-gon encountered, ignoring. "
|
||||
+ "The mesh may not appear correctly. "
|
||||
+ "Triangulate your model prior to export.");
|
||||
// N-gon
|
||||
dumpPoly(inputPoly);
|
||||
IrPolygon[] tris = triangulator.triangulate(inputPoly);
|
||||
for (IrPolygon tri : tris) {
|
||||
newPolygons.add(tri);
|
||||
}
|
||||
}
|
||||
}
|
||||
mesh.polygons = new IrPolygon[newPolygons.size()];
|
||||
@ -373,12 +391,11 @@ public final class IrUtils {
|
||||
boneIndices.put((byte)0);
|
||||
boneWeights.put(0f);
|
||||
}
|
||||
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
|
||||
} else {
|
||||
boneIndices.putInt(0);
|
||||
boneWeights.put(0f).put(0f).put(0f).put(0f);
|
||||
}
|
||||
|
||||
maxBonesPerVertex = Math.max(maxBonesPerVertex, vertex.boneWeightsIndices.length);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,241 @@
|
||||
/*
|
||||
* 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.triangulator;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Matrix3f;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.IrPolygon;
|
||||
import com.jme3.scene.plugins.IrVertex;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Implemented according to
|
||||
* <ul>
|
||||
* <li>http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf</li>
|
||||
* <li>http://cgm.cs.mcgill.ca/~godfried/teaching/cg-projects/97/Ian/algorithm2.html</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class EarClippingTriangulator {
|
||||
|
||||
private static enum VertexType {
|
||||
Convex,
|
||||
Reflex,
|
||||
Ear;
|
||||
}
|
||||
|
||||
private final ArrayList<Integer> indices = new ArrayList<Integer>();
|
||||
private final ArrayList<VertexType> types = new ArrayList<VertexType>();
|
||||
private final ArrayList<Vector2f> positions = new ArrayList<Vector2f>();
|
||||
|
||||
public EarClippingTriangulator() {
|
||||
}
|
||||
|
||||
private static int ccw(Vector2f p0, Vector2f p1, Vector2f p2) {
|
||||
float result = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
|
||||
if (result > 0) {
|
||||
return 1;
|
||||
} else if (result < 0) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean pointInTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) {
|
||||
float d = ((t1.y - t2.y) * (t0.x - t2.x) + (t2.x - t1.x) * (t0.y - t2.y));
|
||||
float a = ((t1.y - t2.y) * (p.x - t2.x) + (t2.x - t1.x) * (p.y - t2.y)) / d;
|
||||
float b = ((t2.y - t0.y) * (p.x - t2.x) + (t0.x - t2.x) * (p.y - t2.y)) / d;
|
||||
float c = 1 - a - b;
|
||||
return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1;
|
||||
}
|
||||
|
||||
private static Matrix3f normalToMatrix(Vector3f norm) {
|
||||
Vector3f tang1 = norm.cross(Vector3f.UNIT_X);
|
||||
if (tang1.lengthSquared() < FastMath.ZERO_TOLERANCE) {
|
||||
tang1 = norm.cross(Vector3f.UNIT_Y);
|
||||
}
|
||||
tang1.normalizeLocal();
|
||||
Vector3f tang2 = norm.cross(tang1).normalizeLocal();
|
||||
|
||||
return new Matrix3f(
|
||||
tang1.x, tang1.y, tang1.z,
|
||||
tang2.x, tang2.y, tang2.z,
|
||||
norm.x, norm.y, norm.z);
|
||||
}
|
||||
|
||||
private int prev(int index) {
|
||||
if (index == 0) {
|
||||
return indices.size() - 1;
|
||||
} else {
|
||||
return index - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private int next(int index) {
|
||||
if (index == indices.size() - 1) {
|
||||
return 0;
|
||||
} else {
|
||||
return index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private VertexType calcType(int index) {
|
||||
int prev = prev(index);
|
||||
int next = next(index);
|
||||
|
||||
Vector2f p0 = positions.get(prev);
|
||||
Vector2f p1 = positions.get(index);
|
||||
Vector2f p2 = positions.get(next);
|
||||
|
||||
if (ccw(p0, p1, p2) <= 0) {
|
||||
return VertexType.Reflex;
|
||||
} else {
|
||||
for (int i = 0; i < positions.size() - 3; i++) {
|
||||
int testIndex = (index + 2 + i) % positions.size();
|
||||
if (types.get(testIndex) != VertexType.Reflex) {
|
||||
continue;
|
||||
}
|
||||
Vector2f p = positions.get(testIndex);
|
||||
if (pointInTriangle(p0, p1, p2, p)) {
|
||||
return VertexType.Convex;
|
||||
}
|
||||
}
|
||||
return VertexType.Ear;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateType(int index) {
|
||||
if (types.get(index) == VertexType.Convex) {
|
||||
return;
|
||||
}
|
||||
types.set(index, calcType(index));
|
||||
}
|
||||
|
||||
private void loadVertices(IrVertex[] vertices) {
|
||||
indices.ensureCapacity(vertices.length);
|
||||
types.ensureCapacity(vertices.length);
|
||||
positions.ensureCapacity(vertices.length);
|
||||
|
||||
Vector3f normal = FastMath.computeNormal(
|
||||
vertices[0].pos,
|
||||
vertices[1].pos,
|
||||
vertices[2].pos);
|
||||
|
||||
Matrix3f transform = normalToMatrix(normal);
|
||||
|
||||
for (int i = 0; i < vertices.length; i++) {
|
||||
Vector3f projected = transform.mult(vertices[i].pos);
|
||||
indices.add(i);
|
||||
positions.add(new Vector2f(projected.x, projected.y));
|
||||
types.add(VertexType.Reflex);
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertices.length; i++) {
|
||||
types.set(i, calcType(i));
|
||||
}
|
||||
}
|
||||
|
||||
private IrPolygon createTriangle(IrPolygon polygon, int prev, int index, int next) {
|
||||
int p0 = indices.get(prev);
|
||||
int p1 = indices.get(index);
|
||||
int p2 = indices.get(next);
|
||||
IrPolygon triangle = new IrPolygon();
|
||||
triangle.vertices = new IrVertex[] {
|
||||
polygon.vertices[p0],
|
||||
polygon.vertices[p1],
|
||||
polygon.vertices[p2],
|
||||
};
|
||||
return triangle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triangulates the given polygon.
|
||||
*
|
||||
* Five or more vertices are required, if less are given, an exception
|
||||
* is thrown.
|
||||
*
|
||||
* @param polygon The polygon to triangulate.
|
||||
* @return N - 2 triangles, where N is the number of vertices in the polygon.
|
||||
*
|
||||
* @throws IllegalArgumentException If the polygon has less than 5 vertices.
|
||||
*/
|
||||
public IrPolygon[] triangulate(IrPolygon polygon) {
|
||||
if (polygon.vertices.length < 5) {
|
||||
throw new IllegalArgumentException("Only polygons with 5 or more vertices are supported");
|
||||
}
|
||||
|
||||
try {
|
||||
int numTris = 0;
|
||||
IrPolygon[] triangles = new IrPolygon[polygon.vertices.length - 2];
|
||||
|
||||
loadVertices(polygon.vertices);
|
||||
|
||||
int index = 0;
|
||||
while (types.size() > 3) {
|
||||
if (types.get(index) == VertexType.Ear) {
|
||||
int prev = prev(index);
|
||||
int next = next(index);
|
||||
|
||||
triangles[numTris++] = createTriangle(polygon, prev, index, next);
|
||||
|
||||
indices.remove(index);
|
||||
types.remove(index);
|
||||
positions.remove(index);
|
||||
|
||||
next = next(prev);
|
||||
updateType(prev);
|
||||
updateType(next);
|
||||
|
||||
index = next(next);
|
||||
} else {
|
||||
index = next(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (types.size() == 3) {
|
||||
triangles[numTris++] = createTriangle(polygon, 0, 1, 2);
|
||||
}
|
||||
|
||||
if (numTris != triangles.length) {
|
||||
throw new AssertionError("Triangulation failed to generate enough triangles");
|
||||
}
|
||||
|
||||
return triangles;
|
||||
} finally {
|
||||
indices.clear();
|
||||
positions.clear();
|
||||
types.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.triangulator;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.IrPolygon;
|
||||
import com.jme3.scene.plugins.IrVertex;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class TriangulatorTest extends TestCase {
|
||||
|
||||
public void testTriangulator() {
|
||||
Vector3f[] dataSet = new Vector3f[]{
|
||||
new Vector3f(0.75f, 0.3f, 1.2f),
|
||||
new Vector3f(0.75f, 0.3f, 0.0f),
|
||||
new Vector3f(0.75f, 0.17f, 0.0f),
|
||||
new Vector3f(0.75000095f, 0.17f, 1.02f),
|
||||
new Vector3f(0.75f, -0.17f, 1.02f),
|
||||
new Vector3f(0.75f, -0.17f, 0.0f),
|
||||
new Vector3f(0.75f, -0.3f, 0.0f),
|
||||
new Vector3f(0.75f, -0.3f, 1.2f)
|
||||
};
|
||||
|
||||
IrPolygon poly = new IrPolygon();
|
||||
poly.vertices = new IrVertex[dataSet.length];
|
||||
for (int i = 0; i < dataSet.length; i++) {
|
||||
poly.vertices[i] = new IrVertex();
|
||||
poly.vertices[i].pos = dataSet[i];
|
||||
}
|
||||
|
||||
EarClippingTriangulator triangulator = new EarClippingTriangulator();
|
||||
triangulator.triangulate(poly);
|
||||
}
|
||||
|
||||
}
|
@ -14,6 +14,7 @@ include 'jme3-terrain'
|
||||
include 'jme3-desktop'
|
||||
include 'jme3-blender'
|
||||
include 'jme3-jogl'
|
||||
include 'jme3-jinput'
|
||||
include 'jme3-lwjgl'
|
||||
include 'jme3-lwjgl3'
|
||||
|
||||
|
@ -1,35 +1,35 @@
|
||||
/*
|
||||
Version Info Examples
|
||||
=====================
|
||||
|
||||
Nightly Build Snapshot
|
||||
Version Info Examples
|
||||
=====================
|
||||
|
||||
Nightly Build Snapshot
|
||||
* git tag:
|
||||
* Full Version: 3.1-5124
|
||||
* POM Version: 3.1.0-SNAPSHOT
|
||||
* NBM Revision: 5124
|
||||
* NBM UC Suffix: nightly/3.1/plugins
|
||||
|
||||
Nightly Build Snapshot (PBRIsComing branch)
|
||||
Nightly Build Snapshot (PBRIsComing branch)
|
||||
* git tag:
|
||||
* Full Version: 3.1-PBRIsComing-5124
|
||||
* POM Version: 3.1.0-PBRIsComing-SNAPSHOT
|
||||
* NBM Revision: 5124
|
||||
* NBM UC Suffix: PBRIsComing-nightly/3.1/plugins
|
||||
|
||||
Alpha1 Release
|
||||
Alpha1 Release
|
||||
* git tag: v3.1.0-alpha1
|
||||
* Full Version: 3.1-alpha1
|
||||
* POM Version: 3.1.0-alpha1
|
||||
* NBM Revision: 0
|
||||
* NBM UC Suffix: stable/3.1/plugins
|
||||
|
||||
Final Release
|
||||
|
||||
Final Release
|
||||
* git tag: v3.1.0
|
||||
* Full Version: 3.1
|
||||
* POM Version: 3.1.0
|
||||
* NBM Revision: 0
|
||||
* NBM UC Suffix: stable/3.1/plugins
|
||||
*/
|
||||
*/
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import org.ajoberstar.grgit.*
|
||||
@ -54,6 +54,7 @@ ext {
|
||||
jmeFullVersion = "${jmeVersion}-UNKNOWN"
|
||||
jmePomVersion = "unknown"
|
||||
jmeNbmUcSuffix = "unknown"
|
||||
jmeVersionTag = "unknown"
|
||||
}
|
||||
|
||||
def getReleaseInfo(String tag) {
|
||||
@ -122,7 +123,7 @@ task configureVersionInfo {
|
||||
jmeShortGitHash = head.abbreviatedId
|
||||
jmeBranchName = grgit.branch.current.name
|
||||
jmeGitTag = grgit.tag.list().find { it.commit == head }
|
||||
|
||||
|
||||
if (jmeGitTag != null) {
|
||||
jmeGitTag = jmeGitTag.name
|
||||
} else {
|
||||
@ -135,6 +136,7 @@ task configureVersionInfo {
|
||||
jmePomVersion = "${releaseInfo.mainVersion}${releaseInfo.releaseSuffix}"
|
||||
jmeNbmRevision = "0"
|
||||
jmeNbmUcSuffix = "stable/${releaseInfo.baseVersion}/plugins"
|
||||
jmeVersionTag = releaseInfo.releaseName ?: "";
|
||||
} else {
|
||||
// SNAPSHOT
|
||||
jmeFullVersion = jmeMainVersion
|
||||
@ -142,7 +144,7 @@ task configureVersionInfo {
|
||||
if (System.env.TRAVIS_BRANCH != null) {
|
||||
jmeBranchName = System.env.TRAVIS_BRANCH
|
||||
}
|
||||
if (System.env.TRAVIS_PULL_REQUEST != null &&
|
||||
if (System.env.TRAVIS_PULL_REQUEST != null &&
|
||||
System.env.TRAVIS_PULL_REQUEST != "false") {
|
||||
jmeBranchName += "-pr-" + System.env.TRAVIS_PULL_REQUEST
|
||||
}
|
||||
@ -157,8 +159,9 @@ task configureVersionInfo {
|
||||
jmeFullVersion += "-${jmeRevision}"
|
||||
jmePomVersion += "-SNAPSHOT"
|
||||
jmeNbmRevision = jmeRevision
|
||||
jmeVersionTag = "SNAPSHOT"
|
||||
}
|
||||
|
||||
|
||||
logger.warn("Full Version: ${jmeFullVersion}")
|
||||
logger.warn("POM Version: ${jmePomVersion}")
|
||||
logger.warn("NBM Revision: ${jmeNbmRevision}")
|
||||
@ -168,4 +171,4 @@ task configureVersionInfo {
|
||||
logger.warn("Failed to get repository info: " + ex.message + ". " + \
|
||||
"Only partial build info will be generated.")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user