/*
* 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.environment;
import com.jme3.light.LightProbe;
import com.jme3.environment.generation.JobProgressListener;
import com.jme3.environment.generation.PrefilteredEnvMapFaceGenerator;
import com.jme3.environment.generation.IrradianceMapGenerator;
import com.jme3.environment.util.EnvMapUtils;
import com.jme3.environment.generation.JobProgressAdapter;
import com.jme3.app.Application;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.texture.TextureCubeMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
/**
* This Factory allows to create LightProbes within a scene given an EnvironmentCamera.
*
* Since the process can be long, you can provide a JobProgressListener that
* will be notified of the ongoing generation process when calling the makeProbe method.
*
* The process is the folowing :
* 1. Create an EnvironmentCamera
* 2. give it a position in the scene
* 3. call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
* 4. add the created LightProbe to a node with the {@link Node#addLight(com.jme3.light.Light) } method.
*
* Optionally for step 3 call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
* with a {@link JobProgressListener} to be notified of the progress of the generation process.
*
* The generation will be split in several threads for faster generation.
*
* This class is entirely thread safe and can be called from any thread.
*
* Note that in case you are using a {@link JobProgressListener} all the its
* method will be called inside and app.enqueu callable.
* This means that it's completely safe to modify the scenegraph within the
* Listener method, but also means that the even will be delayed until next update loop.
*
* @see EnvironmentCamera
* @author bouquet
*/
public class LightProbeFactory {
/**
* Creates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* If you want to monitor the process use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
*
*
*
* @see LightProbe
* @see EnvironmentCamera
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @return the created LightProbe
*/
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene) {
return makeProbe(envCam, scene, null);
}
/**
* Creates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param listener the listener of the genration progress.
* @return the created LightProbe
*/
public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
final LightProbe probe = new LightProbe();
probe.setPosition(envCam.getPosition());
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
}
});
return probe;
}
/**
* Updates a LightProbe with the giver EnvironmentCamera in the given scene.
*
* Note that this is an assynchronous process that will run on multiple threads.
* The process is thread safe.
* The created lightProbe will only be marked as ready when the rendering process is done.
*
* The JobProgressListener will be notified of the progress of the generation.
* Note that you can also use a {@link JobProgressAdapter}.
*
* @see LightProbe
* @see EnvironmentCamera
* @see JobProgressListener
*
* @param probe the Light probe to update
* @param envCam the EnvironmentCamera
* @param scene the Scene
* @param listener the listener of the genration progress.
* @return the created LightProbe
*/
public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
envCam.setPosition(probe.getPosition());
probe.setReady(false);
if(probe.getIrradianceMap() != null) {
probe.getIrradianceMap().getImage().dispose();
probe.getPrefilteredEnvMap().getImage().dispose();
}
probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
@Override
public void done(TextureCubeMap map) {
generatePbrMaps(map, probe, envCam.getApplication(), listener);
}
});
return probe;
}
/**
* Internally called to generate the maps.
* This method will spawn 7 thread (one for the IrradianceMap, and one for each face of the prefiltered env map).
* Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done.
*
* @param envMap the raw env map rendered by the env camera
* @param probe the LigthProbe to generate maps for
* @param app the Application
* @param listener a progress listener. (can be null if no progress reporting is needed)
*/
private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, final JobProgressListener<LightProbe> listener) {
IrradianceMapGenerator irrMapGenerator;
PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6));
int size = envMap.getImage().getWidth();
irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap());
jobState.executor.execute(irrMapGenerator);
for (int i = 0; i < pemGenerators.length; i++) {
pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i));
pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getPrefilteredEnvMap());
jobState.executor.execute(pemGenerators[i]);
}
}
/**
* An inner class to keep the state of a generation process
*/
private static class JobState {
double progress[] = new double[7];
boolean done[] = new boolean[7];
ScheduledThreadPoolExecutor executor;
boolean started = false;
public JobState(ScheduledThreadPoolExecutor executor) {
this.executor = executor;
}
boolean isDone() {
for (boolean d : done) {
if (d == false) {
return false;
}
}
return true;
}
float getProgress() {
float mean = 0;
for (double progres : progress) {
mean += progres;
}
return mean / 7f;
}
}
/**
* An inner JobProgressListener to controll the genration process and properly clean up when it's done
*/
private static class JobListener extends JobProgressAdapter<Integer> {
JobProgressListener<LightProbe> globalListener;
JobState jobState;
LightProbe probe;
int index;
public JobListener(JobProgressListener<LightProbe> globalListener, JobState jobState, LightProbe probe, int index) {
this.globalListener = globalListener;
this.jobState = jobState;
this.probe = probe;
this.index = index;
}
@Override
public void start() {
if (globalListener != null && !jobState.started) {
jobState.started = true;
globalListener.start();
}
}
@Override
public void progress(double value) {
jobState.progress[index] = value;
if (globalListener != null) {
globalListener.progress(jobState.getProgress());
}
}
@Override
public void done(Integer result) {
if (globalListener != null) {
if (result < 6) {
globalListener.step("Prefiltered env map face " + result + " generated");
} else {
globalListener.step("Irradiance map generated");
}
}
jobState.done[index] = true;
if (jobState.isDone()) {
probe.setReady(true);
if (globalListener != null) {
globalListener.done(probe);
}
jobState.executor.shutdownNow();
}
}
}
}