1/*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5package com.jme3.bullet;
6
7import com.jme3.app.Application;
8import com.jme3.app.state.AppState;
9import com.jme3.app.state.AppStateManager;
10import com.jme3.bullet.PhysicsSpace.BroadphaseType;
11import com.jme3.math.Vector3f;
12import com.jme3.renderer.RenderManager;
13import java.util.concurrent.*;
14import java.util.logging.Level;
15import java.util.logging.Logger;
16
17/**
18 * <code>BulletAppState</code> allows using bullet physics in an Application.
19 * @author normenhansen
20 */
21public class BulletAppState implements AppState, PhysicsTickListener {
22
23    protected boolean initialized = false;
24    protected Application app;
25    protected AppStateManager stateManager;
26    protected ScheduledThreadPoolExecutor executor;
27    protected PhysicsSpace pSpace;
28    protected ThreadingType threadingType = ThreadingType.SEQUENTIAL;
29    protected BroadphaseType broadphaseType = BroadphaseType.DBVT;
30    protected Vector3f worldMin = new Vector3f(-10000f, -10000f, -10000f);
31    protected Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f);
32    private float speed = 1;
33    protected boolean active = true;
34    protected float tpf;
35    protected Future physicsFuture;
36
37    /**
38     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
39     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
40     */
41    public BulletAppState() {
42    }
43
44    /**
45     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
46     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
47     * @param broadphaseType The type of broadphase collision detection, BroadphaseType.DVBT is the default
48     */
49    public BulletAppState(BroadphaseType broadphaseType) {
50        this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), broadphaseType);
51    }
52
53    /**
54     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
55     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
56     * An AxisSweep broadphase is used.
57     * @param worldMin The minimum world extent
58     * @param worldMax The maximum world extent
59     */
60    public BulletAppState(Vector3f worldMin, Vector3f worldMax) {
61        this(worldMin, worldMax, BroadphaseType.AXIS_SWEEP_3);
62    }
63
64    public BulletAppState(Vector3f worldMin, Vector3f worldMax, BroadphaseType broadphaseType) {
65        this.worldMin.set(worldMin);
66        this.worldMax.set(worldMax);
67        this.broadphaseType = broadphaseType;
68    }
69
70    private boolean startPhysicsOnExecutor() {
71        if (executor != null) {
72            executor.shutdown();
73        }
74        executor = new ScheduledThreadPoolExecutor(1);
75        final BulletAppState app = this;
76        Callable<Boolean> call = new Callable<Boolean>() {
77
78            public Boolean call() throws Exception {
79                detachedPhysicsLastUpdate = System.currentTimeMillis();
80                pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);
81                pSpace.addTickListener(app);
82                return true;
83            }
84        };
85        try {
86            return executor.submit(call).get();
87        } catch (InterruptedException ex) {
88            Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
89            return false;
90        } catch (ExecutionException ex) {
91            Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
92            return false;
93        }
94    }
95    private Callable<Boolean> parallelPhysicsUpdate = new Callable<Boolean>() {
96
97        public Boolean call() throws Exception {
98            pSpace.update(tpf * getSpeed());
99            return true;
100        }
101    };
102    long detachedPhysicsLastUpdate = 0;
103    private Callable<Boolean> detachedPhysicsUpdate = new Callable<Boolean>() {
104
105        public Boolean call() throws Exception {
106            pSpace.update(getPhysicsSpace().getAccuracy() * getSpeed());
107            pSpace.distributeEvents();
108            long update = System.currentTimeMillis() - detachedPhysicsLastUpdate;
109            detachedPhysicsLastUpdate = System.currentTimeMillis();
110            executor.schedule(detachedPhysicsUpdate, Math.round(getPhysicsSpace().getAccuracy() * 1000000.0f) - (update * 1000), TimeUnit.MICROSECONDS);
111            return true;
112        }
113    };
114
115    public PhysicsSpace getPhysicsSpace() {
116        return pSpace;
117    }
118
119    /**
120     * The physics system is started automatically on attaching, if you want to start it
121     * before for some reason, you can use this method.
122     */
123    public void startPhysics() {
124        //start physics thread(pool)
125        if (threadingType == ThreadingType.PARALLEL) {
126            startPhysicsOnExecutor();
127//        } else if (threadingType == ThreadingType.DETACHED) {
128//            startPhysicsOnExecutor();
129//            executor.submit(detachedPhysicsUpdate);
130        } else {
131            pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);
132        }
133        pSpace.addTickListener(this);
134        initialized = true;
135    }
136
137    public void initialize(AppStateManager stateManager, Application app) {
138        if (!initialized) {
139            startPhysics();
140        }
141        initialized = true;
142    }
143
144    public boolean isInitialized() {
145        return initialized;
146    }
147
148    public void setEnabled(boolean enabled) {
149        this.active = enabled;
150    }
151
152    public boolean isEnabled() {
153        return active;
154    }
155
156    public void stateAttached(AppStateManager stateManager) {
157        if (!initialized) {
158            startPhysics();
159        }
160        if (threadingType == ThreadingType.PARALLEL) {
161            PhysicsSpace.setLocalThreadPhysicsSpace(pSpace);
162        }
163    }
164
165    public void stateDetached(AppStateManager stateManager) {
166    }
167
168    public void update(float tpf) {
169        if (!active) {
170            return;
171        }
172//        if (threadingType != ThreadingType.DETACHED) {
173            pSpace.distributeEvents();
174//        }
175        this.tpf = tpf;
176    }
177
178    public void render(RenderManager rm) {
179        if (!active) {
180            return;
181        }
182        if (threadingType == ThreadingType.PARALLEL) {
183            physicsFuture = executor.submit(parallelPhysicsUpdate);
184        } else if (threadingType == ThreadingType.SEQUENTIAL) {
185            pSpace.update(active ? tpf * speed : 0);
186        } else {
187        }
188    }
189
190    public void postRender() {
191        if (physicsFuture != null) {
192            try {
193                physicsFuture.get();
194                physicsFuture = null;
195            } catch (InterruptedException ex) {
196                Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
197            } catch (ExecutionException ex) {
198                Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex);
199            }
200        }
201    }
202
203    public void cleanup() {
204        if (executor != null) {
205            executor.shutdown();
206            executor = null;
207        }
208        pSpace.removeTickListener(this);
209        pSpace.destroy();
210    }
211
212    /**
213     * @return the threadingType
214     */
215    public ThreadingType getThreadingType() {
216        return threadingType;
217    }
218
219    /**
220     * Use before attaching state
221     * @param threadingType the threadingType to set
222     */
223    public void setThreadingType(ThreadingType threadingType) {
224        this.threadingType = threadingType;
225    }
226
227    /**
228     * Use before attaching state
229     */
230    public void setBroadphaseType(BroadphaseType broadphaseType) {
231        this.broadphaseType = broadphaseType;
232    }
233
234    /**
235     * Use before attaching state
236     */
237    public void setWorldMin(Vector3f worldMin) {
238        this.worldMin = worldMin;
239    }
240
241    /**
242     * Use before attaching state
243     */
244    public void setWorldMax(Vector3f worldMax) {
245        this.worldMax = worldMax;
246    }
247
248    public float getSpeed() {
249        return speed;
250    }
251
252    public void setSpeed(float speed) {
253        this.speed = speed;
254    }
255
256    public void prePhysicsTick(PhysicsSpace space, float f) {
257    }
258
259    public void physicsTick(PhysicsSpace space, float f) {
260    }
261
262    public enum ThreadingType {
263
264        /**
265         * Default mode; user update, physics update and rendering happen sequentially (single threaded)
266         */
267        SEQUENTIAL,
268        /**
269         * Parallel threaded mode; physics update and rendering are executed in parallel, update order is kept.<br/>
270         * Multiple BulletAppStates will execute in parallel in this mode.
271         */
272        PARALLEL,
273    }
274}
275