* Ogre3D mesh.xml loader is now more resilient to certain models exported using blender2ogre
* Ogre3D dotScene loader can now load spot lights * Added some better debugging to FBO errors * Fix weird explosion in TestWalkingChar * Added additional "canvas torture methods" in TestCanvas * Several fixes to canvas: - Issue when size becomes 0, 0 - Freeze if no framerate limit is imposed and canvas is closed git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8210 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
120a3d1e4f
commit
6d728615a3
@ -102,12 +102,11 @@ public final class SAXUtil {
|
|||||||
public static boolean parseBool(String bool, boolean def) throws SAXException{
|
public static boolean parseBool(String bool, boolean def) throws SAXException{
|
||||||
if (bool == null || bool.equals(""))
|
if (bool == null || bool.equals(""))
|
||||||
return def;
|
return def;
|
||||||
else if (bool.equals("false"))
|
|
||||||
return false;
|
|
||||||
else if (bool.equals("true"))
|
|
||||||
return true;
|
|
||||||
else
|
else
|
||||||
throw new SAXException("Expected a boolean, got'"+bool+"'");
|
return Boolean.valueOf(bool);
|
||||||
|
//else
|
||||||
|
//else
|
||||||
|
// throw new SAXException("Expected a boolean, got'"+bool+"'");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String parseString(String str, String def){
|
public static String parseString(String str, String def){
|
||||||
|
@ -90,6 +90,7 @@ import jme3tools.converters.MipMapGenerator;
|
|||||||
import org.lwjgl.opengl.ARBDrawBuffers;
|
import org.lwjgl.opengl.ARBDrawBuffers;
|
||||||
//import org.lwjgl.opengl.ARBDrawInstanced;
|
//import org.lwjgl.opengl.ARBDrawInstanced;
|
||||||
import org.lwjgl.opengl.ARBDrawInstanced;
|
import org.lwjgl.opengl.ARBDrawInstanced;
|
||||||
|
import org.lwjgl.opengl.ARBFramebufferObject;
|
||||||
import org.lwjgl.opengl.ARBMultisample;
|
import org.lwjgl.opengl.ARBMultisample;
|
||||||
import org.lwjgl.opengl.ContextCapabilities;
|
import org.lwjgl.opengl.ContextCapabilities;
|
||||||
import org.lwjgl.opengl.EXTTextureArray;
|
import org.lwjgl.opengl.EXTTextureArray;
|
||||||
@ -427,14 +428,17 @@ public class LwjglRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void resetGLObjects() {
|
public void resetGLObjects() {
|
||||||
|
logger.log(Level.INFO, "Reseting objects and invalidating state");
|
||||||
objManager.resetObjects();
|
objManager.resetObjects();
|
||||||
statistics.clearMemory();
|
statistics.clearMemory();
|
||||||
invalidateState();
|
invalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
|
logger.log(Level.INFO, "Deleting objects and invalidating state");
|
||||||
objManager.deleteAllObjects(this);
|
objManager.deleteAllObjects(this);
|
||||||
statistics.clearMemory();
|
statistics.clearMemory();
|
||||||
|
invalidateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCap(Caps cap) {
|
private void checkCap(Caps cap) {
|
||||||
@ -537,8 +541,8 @@ public class LwjglRenderer implements Renderer {
|
|||||||
|
|
||||||
if (state.isPointSprite() && !context.pointSprite) {
|
if (state.isPointSprite() && !context.pointSprite) {
|
||||||
// Only enable/disable sprite
|
// Only enable/disable sprite
|
||||||
if (context.boundTextures[0] != null) {
|
if (context.boundTextures[0] != null){
|
||||||
if (context.boundTextureUnit != 0) {
|
if (context.boundTextureUnit != 0){
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
context.boundTextureUnit = 0;
|
context.boundTextureUnit = 0;
|
||||||
}
|
}
|
||||||
@ -547,8 +551,8 @@ public class LwjglRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
context.pointSprite = true;
|
context.pointSprite = true;
|
||||||
} else if (!state.isPointSprite() && context.pointSprite) {
|
} else if (!state.isPointSprite() && context.pointSprite) {
|
||||||
if (context.boundTextures[0] != null) {
|
if (context.boundTextures[0] != null){
|
||||||
if (context.boundTextureUnit != 0) {
|
if (context.boundTextureUnit != 0){
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
context.boundTextureUnit = 0;
|
context.boundTextureUnit = 0;
|
||||||
}
|
}
|
||||||
@ -960,7 +964,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source.setId(id);
|
source.setId(id);
|
||||||
} else {
|
}else{
|
||||||
throw new RendererException("Cannot recompile shader source");
|
throw new RendererException("Cannot recompile shader source");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1278,6 +1282,85 @@ public class LwjglRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getTargetBufferName(int buffer){
|
||||||
|
switch (buffer){
|
||||||
|
case GL_NONE: return "NONE";
|
||||||
|
case GL_FRONT: return "GL_FRONT";
|
||||||
|
case GL_BACK: return "GL_BACK";
|
||||||
|
default:
|
||||||
|
if ( buffer >= GL_COLOR_ATTACHMENT0_EXT
|
||||||
|
&& buffer <= GL_COLOR_ATTACHMENT15_EXT){
|
||||||
|
return "GL_COLOR_ATTACHMENT" +
|
||||||
|
(buffer - GL_COLOR_ATTACHMENT0_EXT);
|
||||||
|
}else{
|
||||||
|
return "UNKNOWN? " + buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printRealRenderBufferInfo(FrameBuffer fb, RenderBuffer rb, String name){
|
||||||
|
System.out.println("== Renderbuffer " + name + " ==");
|
||||||
|
System.out.println("RB ID: " + rb.getId());
|
||||||
|
System.out.println("Is proper? " + glIsRenderbufferEXT(rb.getId()));
|
||||||
|
|
||||||
|
int attachment = convertAttachmentSlot(rb.getSlot());
|
||||||
|
|
||||||
|
int type = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT,
|
||||||
|
attachment,
|
||||||
|
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT);
|
||||||
|
|
||||||
|
int rbName = glGetFramebufferAttachmentParameterEXT(GL_DRAW_FRAMEBUFFER_EXT,
|
||||||
|
attachment,
|
||||||
|
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT);
|
||||||
|
|
||||||
|
switch (type){
|
||||||
|
case GL_NONE:
|
||||||
|
System.out.println("Type: None");
|
||||||
|
return; // note: return from method as other queries will be invalid
|
||||||
|
case GL_TEXTURE:
|
||||||
|
System.out.println("Type: Texture");
|
||||||
|
break;
|
||||||
|
case GL_RENDERBUFFER_EXT:
|
||||||
|
System.out.println("Type: Buffer");
|
||||||
|
System.out.println("RB ID: " + rbName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printRealFrameBufferInfo(FrameBuffer fb) {
|
||||||
|
boolean doubleBuffer = glGetBoolean(GL_DOUBLEBUFFER);
|
||||||
|
String drawBuf = getTargetBufferName(glGetInteger(GL_DRAW_BUFFER));
|
||||||
|
String readBuf = getTargetBufferName(glGetInteger(GL_READ_BUFFER));
|
||||||
|
|
||||||
|
int fbId = fb.getId();
|
||||||
|
int curDrawBinding = glGetInteger(ARBFramebufferObject.GL_DRAW_FRAMEBUFFER_BINDING);
|
||||||
|
int curReadBinding = glGetInteger(ARBFramebufferObject.GL_READ_FRAMEBUFFER_BINDING);
|
||||||
|
|
||||||
|
System.out.println("=== OpenGL FBO State ===");
|
||||||
|
System.out.println("Context doublebuffered? " + doubleBuffer);
|
||||||
|
System.out.println("FBO ID: " + fbId);
|
||||||
|
System.out.println("Is proper? " + glIsFramebufferEXT(fbId));
|
||||||
|
System.out.println("Is bound to draw? " + (fbId == curDrawBinding));
|
||||||
|
System.out.println("Is bound to read? " + (fbId == curReadBinding));
|
||||||
|
System.out.println("Draw buffer: " + drawBuf);
|
||||||
|
System.out.println("Read buffer: " + readBuf);
|
||||||
|
|
||||||
|
if (context.boundFBO != fbId){
|
||||||
|
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbId);
|
||||||
|
context.boundFBO = fbId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fb.getDepthBuffer() != null){
|
||||||
|
printRealRenderBufferInfo(fb, fb.getDepthBuffer(), "Depth");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < fb.getNumColorBuffers(); i++){
|
||||||
|
printRealRenderBufferInfo(fb, fb.getColorBuffer(i), "Color" + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkFrameBufferError() {
|
private void checkFrameBufferError() {
|
||||||
int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
|
int status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@ -1290,7 +1373,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
|
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
|
||||||
throw new IllegalStateException("Framebuffer has erronous attachment.");
|
throw new IllegalStateException("Framebuffer has erronous attachment.");
|
||||||
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
|
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
|
||||||
throw new IllegalStateException("Framebuffer is missing required attachment.");
|
throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached.");
|
||||||
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
|
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
|
||||||
throw new IllegalStateException("Framebuffer attachments must have same dimensions.");
|
throw new IllegalStateException("Framebuffer attachments must have same dimensions.");
|
||||||
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
|
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
|
||||||
@ -1487,6 +1570,11 @@ public class LwjglRenderer implements Renderer {
|
|||||||
|
|
||||||
lastFb = null;
|
lastFb = null;
|
||||||
} else {
|
} else {
|
||||||
|
if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null){
|
||||||
|
throw new IllegalArgumentException("The framebuffer: " + fb +
|
||||||
|
"\nDoesn't have any color/depth buffers");
|
||||||
|
}
|
||||||
|
|
||||||
if (fb.isUpdateNeeded()) {
|
if (fb.isUpdateNeeded()) {
|
||||||
updateFrameBuffer(fb);
|
updateFrameBuffer(fb);
|
||||||
}
|
}
|
||||||
@ -1544,13 +1632,14 @@ public class LwjglRenderer implements Renderer {
|
|||||||
assert fb.getId() >= 0;
|
assert fb.getId() >= 0;
|
||||||
assert context.boundFBO == fb.getId();
|
assert context.boundFBO == fb.getId();
|
||||||
lastFb = fb;
|
lastFb = fb;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
checkFrameBufferError();
|
checkFrameBufferError();
|
||||||
} catch (IllegalStateException ex) {
|
} catch (IllegalStateException ex) {
|
||||||
logger.log(Level.SEVERE, "Problem FBO:\n{0}", fb);
|
logger.log(Level.SEVERE, "=== jMonkeyEngine FBO State ===\n{0}", fb);
|
||||||
throw ex;
|
printRealFrameBufferInfo(fb);
|
||||||
}
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1964,7 +2053,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
glBindBuffer(target, bufId);
|
glBindBuffer(target, bufId);
|
||||||
context.boundElementArrayVBO = bufId;
|
context.boundElementArrayVBO = bufId;
|
||||||
//statistics.onVertexBufferUse(vb, true);
|
//statistics.onVertexBufferUse(vb, true);
|
||||||
} else {
|
}else{
|
||||||
//statistics.onVertexBufferUse(vb, false);
|
//statistics.onVertexBufferUse(vb, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1973,7 +2062,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
glBindBuffer(target, bufId);
|
glBindBuffer(target, bufId);
|
||||||
context.boundArrayVBO = bufId;
|
context.boundArrayVBO = bufId;
|
||||||
//statistics.onVertexBufferUse(vb, true);
|
//statistics.onVertexBufferUse(vb, true);
|
||||||
} else {
|
}else{
|
||||||
//statistics.onVertexBufferUse(vb, false);
|
//statistics.onVertexBufferUse(vb, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2142,7 +2231,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
glBindBuffer(GL_ARRAY_BUFFER, bufId);
|
glBindBuffer(GL_ARRAY_BUFFER, bufId);
|
||||||
context.boundArrayVBO = bufId;
|
context.boundArrayVBO = bufId;
|
||||||
//statistics.onVertexBufferUse(vb, true);
|
//statistics.onVertexBufferUse(vb, true);
|
||||||
} else {
|
}else{
|
||||||
//statistics.onVertexBufferUse(vb, false);
|
//statistics.onVertexBufferUse(vb, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2189,7 +2278,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufId);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufId);
|
||||||
context.boundElementArrayVBO = bufId;
|
context.boundElementArrayVBO = bufId;
|
||||||
//statistics.onVertexBufferUse(indexBuf, true);
|
//statistics.onVertexBufferUse(indexBuf, true);
|
||||||
} else {
|
}else{
|
||||||
//statistics.onVertexBufferUse(indexBuf, true);
|
//statistics.onVertexBufferUse(indexBuf, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2314,9 +2403,9 @@ public class LwjglRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
|
private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
|
||||||
if (mesh.getId() == -1) {
|
if (mesh.getId() == -1){
|
||||||
updateVertexArray(mesh);
|
updateVertexArray(mesh);
|
||||||
} else {
|
}else{
|
||||||
// TODO: Check if it was updated
|
// TODO: Check if it was updated
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2359,7 +2448,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
//for (Entry<VertexBuffer> entry : buffers) {
|
//for (Entry<VertexBuffer> entry : buffers) {
|
||||||
// VertexBuffer vb = entry.getValue();
|
// VertexBuffer vb = entry.getValue();
|
||||||
for (int i = 0; i < buffersList.size(); i++) {
|
for (int i = 0; i < buffersList.size(); i++){
|
||||||
VertexBuffer vb = buffersList.get(i);
|
VertexBuffer vb = buffersList.get(i);
|
||||||
|
|
||||||
if (vb.getBufferType() == Type.InterleavedData
|
if (vb.getBufferType() == Type.InterleavedData
|
||||||
@ -2391,10 +2480,10 @@ public class LwjglRenderer implements Renderer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.pointSprite && mesh.getMode() != Mode.Points) {
|
if (context.pointSprite && mesh.getMode() != Mode.Points){
|
||||||
// XXX: Hack, disable point sprite mode if mesh not in point mode
|
// XXX: Hack, disable point sprite mode if mesh not in point mode
|
||||||
if (context.boundTextures[0] != null) {
|
if (context.boundTextures[0] != null){
|
||||||
if (context.boundTextureUnit != 0) {
|
if (context.boundTextureUnit != 0){
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
context.boundTextureUnit = 0;
|
context.boundTextureUnit = 0;
|
||||||
}
|
}
|
||||||
@ -2417,7 +2506,7 @@ public class LwjglRenderer implements Renderer {
|
|||||||
// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
|
// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
|
||||||
// renderMeshVertexArray(mesh, lod, count);
|
// renderMeshVertexArray(mesh, lod, count);
|
||||||
// }else{
|
// }else{
|
||||||
renderMeshDefault(mesh, lod, count);
|
renderMeshDefault(mesh, lod, count);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,11 @@ import com.jme3.system.JmeContext.Type;
|
|||||||
import com.jme3.system.JmeSystem;
|
import com.jme3.system.JmeSystem;
|
||||||
import com.jme3.system.JmeSystem.Platform;
|
import com.jme3.system.JmeSystem.Platform;
|
||||||
import java.awt.Canvas;
|
import java.awt.Canvas;
|
||||||
import java.util.concurrent.BrokenBarrierException;
|
|
||||||
import java.util.concurrent.CyclicBarrier;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import org.lwjgl.LWJGLException;
|
import org.lwjgl.LWJGLException;
|
||||||
|
import org.lwjgl.LWJGLUtil;
|
||||||
import org.lwjgl.input.Keyboard;
|
import org.lwjgl.input.Keyboard;
|
||||||
import org.lwjgl.input.Mouse;
|
import org.lwjgl.input.Mouse;
|
||||||
import org.lwjgl.opengl.Display;
|
import org.lwjgl.opengl.Display;
|
||||||
@ -53,14 +51,23 @@ import org.lwjgl.opengl.PixelFormat;
|
|||||||
|
|
||||||
public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext {
|
public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext {
|
||||||
|
|
||||||
|
protected static final int TASK_NOTHING = 0,
|
||||||
|
TASK_DESTROY_DISPLAY = 1,
|
||||||
|
TASK_CREATE_DISPLAY = 2,
|
||||||
|
TASK_COMPLETE = 3;
|
||||||
|
|
||||||
|
// protected static final boolean USE_SHARED_CONTEXT =
|
||||||
|
// Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true"));
|
||||||
|
|
||||||
|
protected static final boolean USE_SHARED_CONTEXT = false;
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName());
|
private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName());
|
||||||
private Canvas canvas;
|
private Canvas canvas;
|
||||||
private int width;
|
private int width;
|
||||||
private int height;
|
private int height;
|
||||||
|
|
||||||
private final AtomicBoolean needRestoreCanvas = new AtomicBoolean(false);
|
private final Object taskLock = new Object();
|
||||||
private final AtomicBoolean needDestroyCanvas = new AtomicBoolean(false);
|
private int desiredTask = TASK_NOTHING;
|
||||||
private final CyclicBarrier actionRequiredBarrier = new CyclicBarrier(2);
|
|
||||||
|
|
||||||
private Thread renderThread;
|
private Thread renderThread;
|
||||||
private boolean runningFirstTime = true;
|
private boolean runningFirstTime = true;
|
||||||
@ -95,11 +102,19 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(Level.INFO, "EDT: Notifying OGL that canvas is visible..");
|
logger.log(Level.INFO, "EDT: Telling OGL to create display ..");
|
||||||
needRestoreCanvas.set(true);
|
synchronized (taskLock){
|
||||||
|
desiredTask = TASK_CREATE_DISPLAY;
|
||||||
// NOTE: no need to wait for OGL to initialize the canvas,
|
// while (desiredTask != TASK_COMPLETE){
|
||||||
// it can happen at any time.
|
// try {
|
||||||
|
// taskLock.wait();
|
||||||
|
// } catch (InterruptedException ex) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// desiredTask = TASK_NOTHING;
|
||||||
|
}
|
||||||
|
// logger.log(Level.INFO, "EDT: OGL has created the display");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -112,19 +127,20 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
|
|
||||||
// We must tell GL context to shutdown and wait for it to
|
// We must tell GL context to shutdown and wait for it to
|
||||||
// shutdown, otherwise, issues will occur.
|
// shutdown, otherwise, issues will occur.
|
||||||
logger.log(Level.INFO, "EDT: Notifying OGL that canvas is about to become invisible..");
|
logger.log(Level.INFO, "EDT: Telling OGL to destroy display ..");
|
||||||
needDestroyCanvas.set(true);
|
synchronized (taskLock){
|
||||||
try {
|
desiredTask = TASK_DESTROY_DISPLAY;
|
||||||
actionRequiredBarrier.await();
|
while (desiredTask != TASK_COMPLETE){
|
||||||
} catch (InterruptedException ex) {
|
try {
|
||||||
logger.log(Level.SEVERE, "EDT: Interrupted! ", ex);
|
taskLock.wait();
|
||||||
} catch (BrokenBarrierException ex){
|
} catch (InterruptedException ex){
|
||||||
logger.log(Level.SEVERE, "EDT: Broken barrier! ", ex);
|
super.removeNotify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
desiredTask = TASK_NOTHING;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset barrier for future use
|
|
||||||
actionRequiredBarrier.reset();
|
|
||||||
|
|
||||||
logger.log(Level.INFO, "EDT: Acknowledged receipt of canvas death");
|
logger.log(Level.INFO, "EDT: Acknowledged receipt of canvas death");
|
||||||
// GL context is dead at this point
|
// GL context is dead at this point
|
||||||
|
|
||||||
@ -171,34 +187,42 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void runLoop(){
|
protected void runLoop(){
|
||||||
if (needDestroyCanvas.getAndSet(false)){
|
if (desiredTask != TASK_NOTHING){
|
||||||
// Destroy canvas
|
synchronized (taskLock){
|
||||||
logger.log(Level.INFO, "OGL: Received destroy request! Complying..");
|
switch (desiredTask){
|
||||||
try {
|
case TASK_CREATE_DISPLAY:
|
||||||
listener.loseFocus();
|
logger.log(Level.INFO, "OGL: Creating display ..");
|
||||||
pauseCanvas();
|
restoreCanvas();
|
||||||
} finally {
|
listener.gainFocus();
|
||||||
try {
|
desiredTask = TASK_NOTHING;
|
||||||
// Required to avoid deadlock if an exception occurs
|
break;
|
||||||
actionRequiredBarrier.await();
|
case TASK_DESTROY_DISPLAY:
|
||||||
} catch (InterruptedException ex) {
|
logger.log(Level.INFO, "OGL: Destroying display ..");
|
||||||
logger.log(Level.SEVERE, "OGL: Interrupted! ", ex);
|
listener.loseFocus();
|
||||||
} catch (BrokenBarrierException ex) {
|
pauseCanvas();
|
||||||
logger.log(Level.SEVERE, "OGL: Broken barrier! ", ex);
|
break;
|
||||||
}
|
}
|
||||||
|
desiredTask = TASK_COMPLETE;
|
||||||
|
taskLock.notifyAll();
|
||||||
}
|
}
|
||||||
}else if (needRestoreCanvas.getAndSet(false)){
|
|
||||||
// Put canvas back online
|
|
||||||
logger.log(Level.INFO, "OGL: Canvas is now visible! Re-initializing..");
|
|
||||||
restoreCanvas();
|
|
||||||
listener.gainFocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width != canvas.getWidth() || height != canvas.getHeight()){
|
if (renderable.get()){
|
||||||
width = canvas.getWidth();
|
int newWidth = Math.max(canvas.getWidth(), 1);
|
||||||
height = canvas.getHeight();
|
int newHeight = Math.max(canvas.getHeight(), 1);
|
||||||
if (listener != null)
|
if (width != newWidth || height != newHeight){
|
||||||
listener.reshape(width, height);
|
width = newWidth;
|
||||||
|
height = newHeight;
|
||||||
|
if (listener != null){
|
||||||
|
listener.reshape(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (frameRate <= 0){
|
||||||
|
// NOTE: MUST be done otherwise
|
||||||
|
// Windows OS will freeze
|
||||||
|
Display.sync(30);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.runLoop();
|
super.runLoop();
|
||||||
@ -218,8 +242,6 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
Keyboard.destroy();
|
Keyboard.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(Level.INFO, "OGL: Canvas will become invisible! Destroying ..");
|
|
||||||
|
|
||||||
renderable.set(false);
|
renderable.set(false);
|
||||||
destroyContext();
|
destroyContext();
|
||||||
}
|
}
|
||||||
@ -237,7 +259,7 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(Level.INFO, "OGL: Creating display..");
|
logger.log(Level.INFO, "OGL: Creating display context ..");
|
||||||
|
|
||||||
// Set renderable to true, since canvas is now displayable.
|
// Set renderable to true, since canvas is now displayable.
|
||||||
renderable.set(true);
|
renderable.set(true);
|
||||||
@ -306,7 +328,27 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
|
|
||||||
if (pbuffer == null) {
|
if (pbuffer == null) {
|
||||||
pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null);
|
pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null);
|
||||||
|
pbuffer.makeCurrent();
|
||||||
logger.log(Level.INFO, "OGL: Pbuffer has been created");
|
logger.log(Level.INFO, "OGL: Pbuffer has been created");
|
||||||
|
|
||||||
|
// Any created objects are no longer valid
|
||||||
|
if (!runningFirstTime){
|
||||||
|
renderer.resetGLObjects();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pbuffer.makeCurrent();
|
||||||
|
if (!pbuffer.isCurrent()){
|
||||||
|
throw new LWJGLException("Pbuffer cannot be made current");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void destroyPbuffer(){
|
||||||
|
if (pbuffer != null){
|
||||||
|
if (!pbuffer.isBufferLost()){
|
||||||
|
pbuffer.destroy();
|
||||||
|
}
|
||||||
|
pbuffer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,25 +359,9 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
*/
|
*/
|
||||||
protected void destroyContext(){
|
protected void destroyContext(){
|
||||||
try {
|
try {
|
||||||
// The canvas is no longer visible,
|
// invalidate the state so renderer can resume operation
|
||||||
// but the context thread is still running.
|
if (!USE_SHARED_CONTEXT){
|
||||||
if (!needClose.get()){
|
renderer.cleanup();
|
||||||
// MUST make sure there's still a context current here ..
|
|
||||||
// Display is dead, make pbuffer available to the system
|
|
||||||
makePbufferAvailable();
|
|
||||||
|
|
||||||
// pbuffer is now available, make it current
|
|
||||||
pbuffer.makeCurrent();
|
|
||||||
|
|
||||||
// invalidate the state so renderer can resume operation
|
|
||||||
renderer.invalidateState();
|
|
||||||
}else{
|
|
||||||
// The context thread is no longer running.
|
|
||||||
// Destroy pbuffer.
|
|
||||||
if (pbuffer != null && !pbuffer.isBufferLost()){
|
|
||||||
pbuffer.destroy();
|
|
||||||
pbuffer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Display.isCreated()){
|
if (Display.isCreated()){
|
||||||
@ -353,21 +379,35 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
Keyboard.destroy();
|
Keyboard.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
//try {
|
||||||
// NOTE: On Windows XP, not calling setParent(null)
|
// NOTE: On Windows XP, not calling setParent(null)
|
||||||
// freezes the application.
|
// freezes the application.
|
||||||
// On Mac it freezes the application.
|
// On Mac it freezes the application.
|
||||||
// On Linux it fixes a crash with X Window System.
|
// On Linux it fixes a crash with X Window System.
|
||||||
if (JmeSystem.getPlatform() == Platform.Windows32
|
if (JmeSystem.getPlatform() == Platform.Windows32
|
||||||
|| JmeSystem.getPlatform() == Platform.Windows64){
|
|| JmeSystem.getPlatform() == Platform.Windows64){
|
||||||
Display.setParent(null);
|
//Display.setParent(null);
|
||||||
}
|
}
|
||||||
} catch (LWJGLException ex) {
|
//} catch (LWJGLException ex) {
|
||||||
logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex);
|
// logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex);
|
||||||
}
|
//}
|
||||||
|
|
||||||
Display.destroy();
|
Display.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The canvas is no longer visible,
|
||||||
|
// but the context thread is still running.
|
||||||
|
if (!needClose.get()){
|
||||||
|
// MUST make sure there's still a context current here ..
|
||||||
|
// Display is dead, make pbuffer available to the system
|
||||||
|
makePbufferAvailable();
|
||||||
|
|
||||||
|
renderer.invalidateState();
|
||||||
|
}else{
|
||||||
|
// The context thread is no longer running.
|
||||||
|
// Destroy pbuffer.
|
||||||
|
destroyPbuffer();
|
||||||
|
}
|
||||||
} catch (LWJGLException ex) {
|
} catch (LWJGLException ex) {
|
||||||
listener.handleError("Failed make pbuffer available", ex);
|
listener.handleError("Failed make pbuffer available", ex);
|
||||||
}
|
}
|
||||||
@ -385,28 +425,44 @@ public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContex
|
|||||||
frameRate = settings.getFrameRate();
|
frameRate = settings.getFrameRate();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First create the pbuffer, if it is needed.
|
|
||||||
makePbufferAvailable();
|
|
||||||
|
|
||||||
if (renderable.get()){
|
if (renderable.get()){
|
||||||
|
if (!runningFirstTime){
|
||||||
|
// because the display is a different opengl context
|
||||||
|
// must reset the context state.
|
||||||
|
if (!USE_SHARED_CONTEXT){
|
||||||
|
renderer.cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if the pbuffer is currently active,
|
// if the pbuffer is currently active,
|
||||||
// make sure to deactivate it
|
// make sure to deactivate it
|
||||||
if (pbuffer.isCurrent()){
|
destroyPbuffer();
|
||||||
pbuffer.releaseContext();
|
|
||||||
|
if (Keyboard.isCreated()){
|
||||||
|
Keyboard.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Display.setVSyncEnabled(settings.isVSync());
|
Display.setVSyncEnabled(settings.isVSync());
|
||||||
Display.setParent(canvas);
|
Display.setParent(canvas);
|
||||||
Display.create(acquirePixelFormat(false), pbuffer);
|
|
||||||
|
|
||||||
// because the display is a different opengl context
|
if (USE_SHARED_CONTEXT){
|
||||||
// must reset the context state.
|
Display.create(acquirePixelFormat(false), pbuffer);
|
||||||
|
}else{
|
||||||
|
Display.create(acquirePixelFormat(false));
|
||||||
|
}
|
||||||
|
|
||||||
renderer.invalidateState();
|
renderer.invalidateState();
|
||||||
}else{
|
}else{
|
||||||
pbuffer.makeCurrent();
|
// First create the pbuffer, if it is needed.
|
||||||
|
makePbufferAvailable();
|
||||||
}
|
}
|
||||||
// At this point, the OpenGL context is active.
|
|
||||||
|
|
||||||
|
// At this point, the OpenGL context is active.
|
||||||
if (runningFirstTime){
|
if (runningFirstTime){
|
||||||
// THIS is the part that creates the renderer.
|
// THIS is the part that creates the renderer.
|
||||||
// It must always be called, now that we have the pbuffer workaround.
|
// It must always be called, now that we have the pbuffer workaround.
|
||||||
|
@ -41,6 +41,8 @@ import com.jme3.asset.AssetNotFoundException;
|
|||||||
import com.jme3.light.DirectionalLight;
|
import com.jme3.light.DirectionalLight;
|
||||||
import com.jme3.light.Light;
|
import com.jme3.light.Light;
|
||||||
import com.jme3.light.PointLight;
|
import com.jme3.light.PointLight;
|
||||||
|
import com.jme3.light.SpotLight;
|
||||||
|
import com.jme3.math.FastMath;
|
||||||
import com.jme3.math.Quaternion;
|
import com.jme3.math.Quaternion;
|
||||||
import com.jme3.math.Vector3f;
|
import com.jme3.math.Vector3f;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
@ -104,16 +106,22 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
light = null;
|
light = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkTopNode(String topNode) throws SAXException{
|
||||||
|
if (!elementStack.peek().equals(topNode)){
|
||||||
|
throw new SAXException("dotScene parse error: Expected parent node to be " + topNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Quaternion parseQuat(Attributes attribs) throws SAXException{
|
private Quaternion parseQuat(Attributes attribs) throws SAXException{
|
||||||
if (attribs.getValue("x") != null){
|
if (attribs.getValue("x") != null){
|
||||||
// defined as quaternion
|
// defined as quaternion
|
||||||
// qx, qy, qz, qw defined
|
|
||||||
float x = parseFloat(attribs.getValue("x"));
|
float x = parseFloat(attribs.getValue("x"));
|
||||||
float y = parseFloat(attribs.getValue("y"));
|
float y = parseFloat(attribs.getValue("y"));
|
||||||
float z = parseFloat(attribs.getValue("z"));
|
float z = parseFloat(attribs.getValue("z"));
|
||||||
float w = parseFloat(attribs.getValue("w"));
|
float w = parseFloat(attribs.getValue("w"));
|
||||||
return new Quaternion(x,y,z,w);
|
return new Quaternion(x,y,z,w);
|
||||||
}else if (attribs.getValue("qx") != null){
|
}else if (attribs.getValue("qx") != null){
|
||||||
|
// defined as quaternion with prefix "q"
|
||||||
float x = parseFloat(attribs.getValue("qx"));
|
float x = parseFloat(attribs.getValue("qx"));
|
||||||
float y = parseFloat(attribs.getValue("qy"));
|
float y = parseFloat(attribs.getValue("qy"));
|
||||||
float z = parseFloat(attribs.getValue("qz"));
|
float z = parseFloat(attribs.getValue("qz"));
|
||||||
@ -129,6 +137,7 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
q.fromAngleAxis(angle, new Vector3f(axisX, axisY, axisZ));
|
q.fromAngleAxis(angle, new Vector3f(axisX, axisY, axisZ));
|
||||||
return q;
|
return q;
|
||||||
}else{
|
}else{
|
||||||
|
// defines as 3 angles along XYZ axes
|
||||||
float angleX = parseFloat(attribs.getValue("angleX"));
|
float angleX = parseFloat(attribs.getValue("angleX"));
|
||||||
float angleY = parseFloat(attribs.getValue("angleY"));
|
float angleY = parseFloat(attribs.getValue("angleY"));
|
||||||
float angleZ = parseFloat(attribs.getValue("angleZ"));
|
float angleZ = parseFloat(attribs.getValue("angleZ"));
|
||||||
@ -139,19 +148,22 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void parseLightNormal(Attributes attribs) throws SAXException {
|
private void parseLightNormal(Attributes attribs) throws SAXException {
|
||||||
assert elementStack.peek().equals("light");
|
checkTopNode("light");
|
||||||
|
|
||||||
// SpotLight will be supporting a direction-normal, too.
|
// SpotLight will be supporting a direction-normal, too.
|
||||||
if (light instanceof DirectionalLight)
|
if (light instanceof DirectionalLight)
|
||||||
((DirectionalLight) light).setDirection(parseVector3(attribs));
|
((DirectionalLight) light).setDirection(parseVector3(attribs));
|
||||||
|
else if (light instanceof SpotLight){
|
||||||
|
((SpotLight) light).setDirection(parseVector3(attribs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseLightAttenuation(Attributes attribs) throws SAXException {
|
private void parseLightAttenuation(Attributes attribs) throws SAXException {
|
||||||
// NOTE: Only radius is supported atm ( for pointlights only, since there are no spotlights, yet).
|
// NOTE: Derives range based on "linear" if it is used solely
|
||||||
assert elementStack.peek().equals("light");
|
// for the attenuation. Otherwise derives it from "range"
|
||||||
|
checkTopNode("light");
|
||||||
|
|
||||||
// SpotLight will be supporting a direction-normal, too.
|
if (light instanceof PointLight || light instanceof SpotLight){
|
||||||
if (light instanceof PointLight){
|
|
||||||
float range = parseFloat(attribs.getValue("range"));
|
float range = parseFloat(attribs.getValue("range"));
|
||||||
float constant = parseFloat(attribs.getValue("constant"));
|
float constant = parseFloat(attribs.getValue("constant"));
|
||||||
float linear = parseFloat(attribs.getValue("linear"));
|
float linear = parseFloat(attribs.getValue("linear"));
|
||||||
@ -165,15 +177,36 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
if (constant == 1 && quadratic == 0 && linear > 0){
|
if (constant == 1 && quadratic == 0 && linear > 0){
|
||||||
range = 1f / linear;
|
range = 1f / linear;
|
||||||
}
|
}
|
||||||
((PointLight) light).setRadius(range);
|
|
||||||
|
if (light instanceof PointLight){
|
||||||
|
((PointLight) light).setRadius(range);
|
||||||
|
}else{
|
||||||
|
((SpotLight)light).setSpotRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseLightSpotLightRange(Attributes attribs) throws SAXException{
|
||||||
|
checkTopNode("light");
|
||||||
|
|
||||||
|
float outer = SAXUtil.parseFloat(attribs.getValue("outer"));
|
||||||
|
float inner = SAXUtil.parseFloat(attribs.getValue("inner"));
|
||||||
|
|
||||||
|
if (!(light instanceof SpotLight)){
|
||||||
|
throw new SAXException("dotScene parse error: spotLightRange "
|
||||||
|
+ "can only appear under 'spot' light elements");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpotLight sl = (SpotLight) light;
|
||||||
|
sl.setSpotInnerAngle(inner * 0.5f);
|
||||||
|
sl.setSpotOuterAngle(outer * 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseLight(Attributes attribs) throws SAXException {
|
private void parseLight(Attributes attribs) throws SAXException {
|
||||||
assert node != null;
|
if (node == null || node.getParent() == null)
|
||||||
assert node.getParent() != null;
|
throw new SAXException("dotScene parse error: light can only appear under a node");
|
||||||
assert elementStack.peek().equals("node");
|
|
||||||
|
checkTopNode("node");
|
||||||
|
|
||||||
String lightType = parseString(attribs.getValue("type"), "point");
|
String lightType = parseString(attribs.getValue("type"), "point");
|
||||||
if(lightType.equals("point")) {
|
if(lightType.equals("point")) {
|
||||||
@ -182,10 +215,8 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
light = new DirectionalLight();
|
light = new DirectionalLight();
|
||||||
// Assuming "normal" property is not provided
|
// Assuming "normal" property is not provided
|
||||||
((DirectionalLight)light).setDirection(Vector3f.UNIT_Z);
|
((DirectionalLight)light).setDirection(Vector3f.UNIT_Z);
|
||||||
} else if(lightType.equals("spotLight")) {
|
} else if(lightType.equals("spotLight") || lightType.equals("spot")) {
|
||||||
// TODO: SpotLight class.
|
light = new SpotLight();
|
||||||
logger.warning("No SpotLight class atm, using Pointlight instead.");
|
|
||||||
light = new PointLight();
|
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.WARNING, "No matching jME3 LightType found for OGRE LightType: {0}", lightType);
|
logger.log(Level.WARNING, "No matching jME3 LightType found for OGRE LightType: {0}", lightType);
|
||||||
}
|
}
|
||||||
@ -203,14 +234,19 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
@Override
|
@Override
|
||||||
public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{
|
public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{
|
||||||
if (qName.equals("scene")){
|
if (qName.equals("scene")){
|
||||||
assert elementStack.size() == 0;
|
if (elementStack.size() != 0){
|
||||||
|
throw new SAXException("dotScene parse error: 'scene' element must be the root XML element");
|
||||||
|
}
|
||||||
|
|
||||||
String version = attribs.getValue("formatVersion");
|
String version = attribs.getValue("formatVersion");
|
||||||
if (version == null || !version.equals("1.0.0"))
|
if (version == null && !version.equals("1.0.0") && !version.equals("1.0.1"))
|
||||||
logger.log(Level.WARNING, "Unrecognized version number"
|
logger.log(Level.WARNING, "Unrecognized version number"
|
||||||
+ " in dotScene file: {0}", version);
|
+ " in dotScene file: {0}", version);
|
||||||
|
|
||||||
}else if (qName.equals("nodes")){
|
}else if (qName.equals("nodes")){
|
||||||
assert root == null;
|
if (root != null){
|
||||||
|
throw new SAXException("dotScene parse error: nodes element was specified twice");
|
||||||
|
}
|
||||||
if (sceneName == null)
|
if (sceneName == null)
|
||||||
root = new Node("OgreDotScene"+(++sceneIdx));
|
root = new Node("OgreDotScene"+(++sceneIdx));
|
||||||
else
|
else
|
||||||
@ -218,22 +254,31 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
|
|
||||||
node = root;
|
node = root;
|
||||||
}else if (qName.equals("externals")){
|
}else if (qName.equals("externals")){
|
||||||
assert elementStack.peek().equals("scene");
|
checkTopNode("scene");
|
||||||
|
// Not loaded currently
|
||||||
}else if (qName.equals("item")){
|
}else if (qName.equals("item")){
|
||||||
assert elementStack.peek().equals("externals");
|
checkTopNode("externals");
|
||||||
}else if (qName.equals("file")){
|
}else if (qName.equals("file")){
|
||||||
assert elementStack.peek().equals("item");
|
checkTopNode("item");
|
||||||
String matFile = folderName+attribs.getValue("name");
|
|
||||||
try {
|
// XXX: Currently material file name is based
|
||||||
materialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile));
|
// on the scene's filename. THIS IS NOT CORRECT.
|
||||||
} catch (AssetNotFoundException ex){
|
// To solve, port SceneLoader to use DOM instead of SAX
|
||||||
materialList = null;
|
|
||||||
logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile);
|
//String matFile = folderName+attribs.getValue("name");
|
||||||
}
|
//try {
|
||||||
|
// materialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile));
|
||||||
|
//} catch (AssetNotFoundException ex){
|
||||||
|
// materialList = null;
|
||||||
|
// logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile);
|
||||||
|
//}
|
||||||
}else if (qName.equals("node")){
|
}else if (qName.equals("node")){
|
||||||
String curElement = elementStack.peek();
|
String curElement = elementStack.peek();
|
||||||
assert curElement.equals("nodes") || curElement.equals("node");
|
if (!curElement.equals("node") && !curElement.equals("nodes")){
|
||||||
|
throw new SAXException("dotScene parse error: "
|
||||||
|
+ "node element can only appear under 'node' or 'nodes'");
|
||||||
|
}
|
||||||
|
|
||||||
String name = attribs.getValue("name");
|
String name = attribs.getValue("name");
|
||||||
if (name == null)
|
if (name == null)
|
||||||
name = "OgreNode-" + (++nodeIdx);
|
name = "OgreNode-" + (++nodeIdx);
|
||||||
@ -259,7 +304,8 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else if (qName.equals("entity")){
|
}else if (qName.equals("entity")){
|
||||||
assert elementStack.peek().equals("node");
|
checkTopNode("node");
|
||||||
|
|
||||||
String name = attribs.getValue("name");
|
String name = attribs.getValue("name");
|
||||||
if (name == null)
|
if (name == null)
|
||||||
name = "OgreEntity-" + (++nodeIdx);
|
name = "OgreEntity-" + (++nodeIdx);
|
||||||
@ -267,32 +313,31 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
name += "-entity";
|
name += "-entity";
|
||||||
|
|
||||||
String meshFile = attribs.getValue("meshFile");
|
String meshFile = attribs.getValue("meshFile");
|
||||||
if (meshFile == null)
|
if (meshFile == null) {
|
||||||
throw new SAXException("Required attribute 'meshFile' missing for 'entity' node");
|
throw new SAXException("Required attribute 'meshFile' missing for 'entity' node");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Not currently used
|
||||||
String materialName = attribs.getValue("materialName");
|
String materialName = attribs.getValue("materialName");
|
||||||
|
|
||||||
// NOTE: append "xml" since its assumed mesh filse are binary in dotScene
|
if (folderName != null) {
|
||||||
if (folderName != null)
|
|
||||||
meshFile = folderName + meshFile;
|
meshFile = folderName + meshFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: append "xml" since its assumed mesh files are binary in dotScene
|
||||||
meshFile += ".xml";
|
meshFile += ".xml";
|
||||||
|
|
||||||
entityNode = new Node(name);
|
entityNode = new Node(name);
|
||||||
OgreMeshKey key = new OgreMeshKey(meshFile, materialList);
|
OgreMeshKey key = new OgreMeshKey(meshFile, materialList);
|
||||||
Spatial ogreMesh =
|
Spatial ogreMesh = assetManager.loadModel(key);
|
||||||
(Spatial) assetManager.loadAsset(key);
|
|
||||||
//TODO:workaround for meshxml / mesh.xml
|
|
||||||
if(ogreMesh==null){
|
|
||||||
meshFile = folderName + attribs.getValue("meshFile") + "xml";
|
|
||||||
key = new OgreMeshKey(meshFile, materialList);
|
|
||||||
ogreMesh = (Spatial) assetManager.loadAsset(key);
|
|
||||||
}
|
|
||||||
entityNode.attachChild(ogreMesh);
|
entityNode.attachChild(ogreMesh);
|
||||||
node.attachChild(entityNode);
|
node.attachChild(entityNode);
|
||||||
node = null;
|
node = null;
|
||||||
}else if (qName.equals("position")){
|
}else if (qName.equals("position")){
|
||||||
node.setLocalTranslation(SAXUtil.parseVector3(attribs));
|
if (elementStack.peek().equals("node")){
|
||||||
|
node.setLocalTranslation(SAXUtil.parseVector3(attribs));
|
||||||
|
}
|
||||||
}else if (qName.equals("quaternion") || qName.equals("rotation")){
|
}else if (qName.equals("quaternion") || qName.equals("rotation")){
|
||||||
node.setLocalRotation(parseQuat(attribs));
|
node.setLocalRotation(parseQuat(attribs));
|
||||||
}else if (qName.equals("scale")){
|
}else if (qName.equals("scale")){
|
||||||
@ -305,19 +350,22 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
light.setColor(parseColor(attribs));
|
light.setColor(parseColor(attribs));
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
assert elementStack.peek().equals("environment");
|
checkTopNode("environment");
|
||||||
}
|
}
|
||||||
} else if (qName.equals("normal")) {
|
} else if (qName.equals("normal") || qName.equals("direction")) {
|
||||||
|
checkTopNode("light");
|
||||||
parseLightNormal(attribs);
|
parseLightNormal(attribs);
|
||||||
} else if (qName.equals("lightAttenuation")) {
|
} else if (qName.equals("lightAttenuation")) {
|
||||||
parseLightAttenuation(attribs);
|
parseLightAttenuation(attribs);
|
||||||
|
} else if (qName.equals("spotLightRange") || qName.equals("lightRange")) {
|
||||||
|
parseLightSpotLightRange(attribs);
|
||||||
}
|
}
|
||||||
|
|
||||||
elementStack.push(qName);
|
elementStack.push(qName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endElement(String uri, String name, String qName) {
|
public void endElement(String uri, String name, String qName) throws SAXException {
|
||||||
if (qName.equals("node")){
|
if (qName.equals("node")){
|
||||||
node = node.getParent();
|
node = node.getParent();
|
||||||
}else if (qName.equals("nodes")){
|
}else if (qName.equals("nodes")){
|
||||||
@ -339,11 +387,21 @@ public class SceneLoader extends DefaultHandler implements AssetLoader {
|
|||||||
PointLight pl = (PointLight) light;
|
PointLight pl = (PointLight) light;
|
||||||
Vector3f pos = node.getWorldTranslation();
|
Vector3f pos = node.getWorldTranslation();
|
||||||
pl.setPosition(pos);
|
pl.setPosition(pos);
|
||||||
|
}else if (light instanceof SpotLight){
|
||||||
|
SpotLight sl = (SpotLight) light;
|
||||||
|
|
||||||
|
Vector3f pos = node.getWorldTranslation();
|
||||||
|
sl.setPosition(pos);
|
||||||
|
|
||||||
|
Quaternion q = node.getWorldRotation();
|
||||||
|
Vector3f dir = sl.getDirection();
|
||||||
|
q.multLocal(dir);
|
||||||
|
sl.setDirection(dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
light = null;
|
light = null;
|
||||||
}
|
}
|
||||||
assert elementStack.peek().equals(qName);
|
checkTopNode(qName);
|
||||||
elementStack.pop();
|
elementStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ public class TestBareBonesApp extends Application {
|
|||||||
boxGeom.updateGeometricState();
|
boxGeom.updateGeometricState();
|
||||||
|
|
||||||
// render the viewports
|
// render the viewports
|
||||||
renderManager.render(tpf, true);
|
renderManager.render(tpf, context.isRenderable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -88,7 +88,7 @@ public class TestAppStates extends Application {
|
|||||||
stateManager.render(renderManager);
|
stateManager.render(renderManager);
|
||||||
|
|
||||||
// render the viewports
|
// render the viewports
|
||||||
renderManager.render(tpf, true);
|
renderManager.render(tpf, context.isRenderable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -37,7 +37,10 @@ import com.jme3.app.SimpleApplication;
|
|||||||
import com.jme3.system.AppSettings;
|
import com.jme3.system.AppSettings;
|
||||||
import com.jme3.system.JmeCanvasContext;
|
import com.jme3.system.JmeCanvasContext;
|
||||||
import com.jme3.util.JmeFormatter;
|
import com.jme3.util.JmeFormatter;
|
||||||
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Canvas;
|
import java.awt.Canvas;
|
||||||
|
import java.awt.Container;
|
||||||
|
import java.awt.Dimension;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.WindowAdapter;
|
import java.awt.event.WindowAdapter;
|
||||||
@ -50,8 +53,11 @@ import javax.swing.JFrame;
|
|||||||
import javax.swing.JMenu;
|
import javax.swing.JMenu;
|
||||||
import javax.swing.JMenuBar;
|
import javax.swing.JMenuBar;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.JTabbedPane;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
public class TestCanvas {
|
public class TestCanvas {
|
||||||
|
|
||||||
@ -59,7 +65,130 @@ public class TestCanvas {
|
|||||||
private static Canvas canvas;
|
private static Canvas canvas;
|
||||||
private static Application app;
|
private static Application app;
|
||||||
private static JFrame frame;
|
private static JFrame frame;
|
||||||
private static final String appClass = "jme3test.post.TestMultiplesFilters";
|
private static Container canvasPanel1, canvasPanel2;
|
||||||
|
private static Container currentPanel;
|
||||||
|
private static JTabbedPane tabbedPane;
|
||||||
|
private static final String appClass = "jme3test.post.TestRenderToTexture";
|
||||||
|
|
||||||
|
private static void createTabs(){
|
||||||
|
tabbedPane = new JTabbedPane();
|
||||||
|
|
||||||
|
canvasPanel1 = new JPanel();
|
||||||
|
canvasPanel1.setLayout(new BorderLayout());
|
||||||
|
tabbedPane.addTab("jME3 Canvas 1", canvasPanel1);
|
||||||
|
|
||||||
|
canvasPanel2 = new JPanel();
|
||||||
|
canvasPanel2.setLayout(new BorderLayout());
|
||||||
|
tabbedPane.addTab("jME3 Canvas 2", canvasPanel2);
|
||||||
|
|
||||||
|
frame.getContentPane().add(tabbedPane);
|
||||||
|
|
||||||
|
currentPanel = canvasPanel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createMenu(){
|
||||||
|
JMenuBar menuBar = new JMenuBar();
|
||||||
|
frame.setJMenuBar(menuBar);
|
||||||
|
|
||||||
|
JMenu menuTortureMethods = new JMenu("Canvas Torture Methods");
|
||||||
|
menuBar.add(menuTortureMethods);
|
||||||
|
|
||||||
|
final JMenuItem itemRemoveCanvas = new JMenuItem("Remove Canvas");
|
||||||
|
menuTortureMethods.add(itemRemoveCanvas);
|
||||||
|
itemRemoveCanvas.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (itemRemoveCanvas.getText().equals("Remove Canvas")){
|
||||||
|
currentPanel.remove(canvas);
|
||||||
|
|
||||||
|
itemRemoveCanvas.setText("Add Canvas");
|
||||||
|
}else if (itemRemoveCanvas.getText().equals("Add Canvas")){
|
||||||
|
currentPanel.add(canvas, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
itemRemoveCanvas.setText("Remove Canvas");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final JMenuItem itemHideCanvas = new JMenuItem("Hide Canvas");
|
||||||
|
menuTortureMethods.add(itemHideCanvas);
|
||||||
|
itemHideCanvas.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (itemHideCanvas.getText().equals("Hide Canvas")){
|
||||||
|
canvas.setVisible(false);
|
||||||
|
itemHideCanvas.setText("Show Canvas");
|
||||||
|
}else if (itemHideCanvas.getText().equals("Show Canvas")){
|
||||||
|
canvas.setVisible(true);
|
||||||
|
itemHideCanvas.setText("Hide Canvas");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final JMenuItem itemSwitchTab = new JMenuItem("Switch to tab #2");
|
||||||
|
menuTortureMethods.add(itemSwitchTab);
|
||||||
|
itemSwitchTab.addActionListener(new ActionListener(){
|
||||||
|
public void actionPerformed(ActionEvent e){
|
||||||
|
if (itemSwitchTab.getText().equals("Switch to tab #2")){
|
||||||
|
canvasPanel1.remove(canvas);
|
||||||
|
canvasPanel2.add(canvas, BorderLayout.CENTER);
|
||||||
|
currentPanel = canvasPanel2;
|
||||||
|
itemSwitchTab.setText("Switch to tab #1");
|
||||||
|
}else if (itemSwitchTab.getText().equals("Switch to tab #1")){
|
||||||
|
canvasPanel2.remove(canvas);
|
||||||
|
canvasPanel1.add(canvas, BorderLayout.CENTER);
|
||||||
|
currentPanel = canvasPanel1;
|
||||||
|
itemSwitchTab.setText("Switch to tab #2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem itemSwitchLaf = new JMenuItem("Switch Look and Feel");
|
||||||
|
menuTortureMethods.add(itemSwitchLaf);
|
||||||
|
itemSwitchLaf.addActionListener(new ActionListener(){
|
||||||
|
public void actionPerformed(ActionEvent e){
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
|
} catch (Throwable t){
|
||||||
|
t.printStackTrace();
|
||||||
|
}
|
||||||
|
SwingUtilities.updateComponentTreeUI(frame);
|
||||||
|
frame.pack();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem itemSmallSize = new JMenuItem("Set size to (0, 0)");
|
||||||
|
menuTortureMethods.add(itemSmallSize);
|
||||||
|
itemSmallSize.addActionListener(new ActionListener(){
|
||||||
|
public void actionPerformed(ActionEvent e){
|
||||||
|
Dimension preferred = frame.getPreferredSize();
|
||||||
|
frame.setPreferredSize(new Dimension(0, 0));
|
||||||
|
frame.pack();
|
||||||
|
frame.setPreferredSize(preferred);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas");
|
||||||
|
menuTortureMethods.add(itemKillCanvas);
|
||||||
|
itemKillCanvas.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
currentPanel.remove(canvas);
|
||||||
|
app.stop(true);
|
||||||
|
|
||||||
|
createCanvas(appClass);
|
||||||
|
currentPanel.add(canvas, BorderLayout.CENTER);
|
||||||
|
frame.pack();
|
||||||
|
startApp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JMenuItem itemExit = new JMenuItem("Exit");
|
||||||
|
menuTortureMethods.add(itemExit);
|
||||||
|
itemExit.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent ae) {
|
||||||
|
frame.dispose();
|
||||||
|
app.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void createFrame(){
|
private static void createFrame(){
|
||||||
frame = new JFrame("Test");
|
frame = new JFrame("Test");
|
||||||
@ -71,75 +200,14 @@ public class TestCanvas {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
JMenuBar menuBar = new JMenuBar();
|
createTabs();
|
||||||
frame.setJMenuBar(menuBar);
|
createMenu();
|
||||||
|
|
||||||
JMenu menuFile = new JMenu("File");
|
|
||||||
menuBar.add(menuFile);
|
|
||||||
|
|
||||||
final JMenuItem itemRemoveCanvas = new JMenuItem("Remove Canvas");
|
|
||||||
menuFile.add(itemRemoveCanvas);
|
|
||||||
itemRemoveCanvas.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
if (itemRemoveCanvas.getText().equals("Remove Canvas")){
|
|
||||||
frame.getContentPane().remove(canvas);
|
|
||||||
|
|
||||||
// force OS to repaint over canvas ..
|
|
||||||
// this is needed since AWT does not handle
|
|
||||||
// that when a heavy-weight component is removed.
|
|
||||||
frame.setVisible(false);
|
|
||||||
frame.setVisible(true);
|
|
||||||
frame.requestFocus();
|
|
||||||
|
|
||||||
itemRemoveCanvas.setText("Add Canvas");
|
|
||||||
}else if (itemRemoveCanvas.getText().equals("Add Canvas")){
|
|
||||||
frame.getContentPane().add(canvas);
|
|
||||||
itemRemoveCanvas.setText("Remove Canvas");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas");
|
|
||||||
menuFile.add(itemKillCanvas);
|
|
||||||
itemKillCanvas.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
frame.getContentPane().remove(canvas);
|
|
||||||
app.stop(true);
|
|
||||||
|
|
||||||
createCanvas(appClass);
|
|
||||||
frame.getContentPane().add(canvas);
|
|
||||||
frame.pack();
|
|
||||||
startApp();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
JMenuItem itemExit = new JMenuItem("Exit");
|
|
||||||
menuFile.add(itemExit);
|
|
||||||
itemExit.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent ae) {
|
|
||||||
frame.dispose();
|
|
||||||
app.stop();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
JMenu menuEdit = new JMenu("Edit");
|
|
||||||
menuBar.add(menuEdit);
|
|
||||||
JMenuItem itemDelete = new JMenuItem("Delete");
|
|
||||||
menuEdit.add(itemDelete);
|
|
||||||
|
|
||||||
JMenu menuView = new JMenu("View");
|
|
||||||
menuBar.add(menuView);
|
|
||||||
JMenuItem itemSetting = new JMenuItem("Settings");
|
|
||||||
menuView.add(itemSetting);
|
|
||||||
|
|
||||||
JMenu menuHelp = new JMenu("Help");
|
|
||||||
menuBar.add(menuHelp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createCanvas(String appClass){
|
public static void createCanvas(String appClass){
|
||||||
AppSettings settings = new AppSettings(true);
|
AppSettings settings = new AppSettings(true);
|
||||||
settings.setWidth( Math.max(640, frame.getContentPane().getWidth()) );
|
settings.setWidth(640);
|
||||||
settings.setHeight( Math.max(480, frame.getContentPane().getHeight()) );
|
settings.setHeight(480);
|
||||||
|
|
||||||
try{
|
try{
|
||||||
Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
|
Class<? extends Application> clazz = (Class<? extends Application>) Class.forName(appClass);
|
||||||
@ -155,6 +223,7 @@ public class TestCanvas {
|
|||||||
app.setPauseOnLostFocus(false);
|
app.setPauseOnLostFocus(false);
|
||||||
app.setSettings(settings);
|
app.setSettings(settings);
|
||||||
app.createCanvas();
|
app.createCanvas();
|
||||||
|
app.startCanvas();
|
||||||
|
|
||||||
context = (JmeCanvasContext) app.getContext();
|
context = (JmeCanvasContext) app.getContext();
|
||||||
canvas = context.getCanvas();
|
canvas = context.getCanvas();
|
||||||
@ -184,14 +253,20 @@ public class TestCanvas {
|
|||||||
Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
|
Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
|
||||||
Logger.getLogger("").addHandler(consoleHandler);
|
Logger.getLogger("").addHandler(consoleHandler);
|
||||||
|
|
||||||
|
createCanvas(appClass);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
}
|
||||||
|
|
||||||
SwingUtilities.invokeLater(new Runnable(){
|
SwingUtilities.invokeLater(new Runnable(){
|
||||||
public void run(){
|
public void run(){
|
||||||
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
|
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
|
||||||
|
|
||||||
createFrame();
|
createFrame();
|
||||||
createCanvas(appClass);
|
|
||||||
|
|
||||||
frame.getContentPane().add(canvas);
|
currentPanel.add(canvas, BorderLayout.CENTER);
|
||||||
frame.pack();
|
frame.pack();
|
||||||
startApp();
|
startApp();
|
||||||
frame.setLocationRelativeTo(null);
|
frame.setLocationRelativeTo(null);
|
||||||
|
@ -225,7 +225,7 @@ public class TestWalkingChar extends SimpleApplication implements ActionListener
|
|||||||
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
|
mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
|
||||||
effect.setMaterial(mat);
|
effect.setMaterial(mat);
|
||||||
effect.setLocalScale(100);
|
// effect.setLocalScale(100);
|
||||||
rootNode.attachChild(effect);
|
rootNode.attachChild(effect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user