1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.light;
34
35import com.jme3.export.*;
36import com.jme3.scene.Spatial;
37import com.jme3.util.SortUtil;
38import java.io.IOException;
39import java.util.*;
40
41/**
42 * <code>LightList</code> is used internally by {@link Spatial}s to manage
43 * lights that are attached to them.
44 *
45 * @author Kirill Vainer
46 */
47public final class LightList implements Iterable<Light>, Savable, Cloneable {
48
49    private Light[] list, tlist;
50    private float[] distToOwner;
51    private int listSize;
52    private Spatial owner;
53
54    private static final int DEFAULT_SIZE = 1;
55
56    private static final Comparator<Light> c = new Comparator<Light>() {
57        /**
58         * This assumes lastDistance have been computed in a previous step.
59         */
60        public int compare(Light l1, Light l2) {
61            if (l1.lastDistance < l2.lastDistance)
62                return -1;
63            else if (l1.lastDistance > l2.lastDistance)
64                return 1;
65            else
66                return 0;
67        }
68    };
69
70    /**
71     * Default constructor for serialization. Do not use
72     */
73    public LightList(){
74    }
75
76    /**
77     * Creates a <code>LightList</code> for the given {@link Spatial}.
78     *
79     * @param owner The spatial owner
80     */
81    public LightList(Spatial owner) {
82        listSize = 0;
83        list = new Light[DEFAULT_SIZE];
84        distToOwner = new float[DEFAULT_SIZE];
85        Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
86        this.owner = owner;
87    }
88
89    /**
90     * Set the owner of the LightList. Only used for cloning.
91     * @param owner
92     */
93    public void setOwner(Spatial owner){
94        this.owner = owner;
95    }
96
97    private void doubleSize(){
98        Light[] temp = new Light[list.length * 2];
99        float[] temp2 = new float[list.length * 2];
100        System.arraycopy(list, 0, temp, 0, list.length);
101        System.arraycopy(distToOwner, 0, temp2, 0, list.length);
102        list = temp;
103        distToOwner = temp2;
104    }
105
106    /**
107     * Adds a light to the list. List size is doubled if there is no room.
108     *
109     * @param l
110     *            The light to add.
111     */
112    public void add(Light l) {
113        if (listSize == list.length) {
114            doubleSize();
115        }
116        list[listSize] = l;
117        distToOwner[listSize++] = Float.NEGATIVE_INFINITY;
118    }
119
120    /**
121     * Remove the light at the given index.
122     *
123     * @param index
124     */
125    public void remove(int index){
126        if (index >= listSize || index < 0)
127            throw new IndexOutOfBoundsException();
128
129        listSize --;
130        if (index == listSize){
131            list[listSize] = null;
132            return;
133        }
134
135        for (int i = index; i < listSize; i++){
136            list[i] = list[i+1];
137        }
138        list[listSize] = null;
139    }
140
141    /**
142     * Removes the given light from the LightList.
143     *
144     * @param l the light to remove
145     */
146    public void remove(Light l){
147        for (int i = 0; i < listSize; i++){
148            if (list[i] == l){
149                remove(i);
150                return;
151            }
152        }
153    }
154
155    /**
156     * @return The size of the list.
157     */
158    public int size(){
159        return listSize;
160    }
161
162    /**
163     * @return the light at the given index.
164     * @throws IndexOutOfBoundsException If the given index is outside bounds.
165     */
166    public Light get(int num){
167        if (num >= listSize || num < 0)
168            throw new IndexOutOfBoundsException();
169
170        return list[num];
171    }
172
173    /**
174     * Resets list size to 0.
175     */
176    public void clear() {
177        if (listSize == 0)
178            return;
179
180        for (int i = 0; i < listSize; i++)
181            list[i] = null;
182
183        if (tlist != null)
184            Arrays.fill(tlist, null);
185
186        listSize = 0;
187    }
188
189    /**
190     * Sorts the elements in the list acording to their Comparator.
191     * There are two reasons why lights should be resorted.
192     * First, if the lights have moved, that means their distance to
193     * the spatial changed.
194     * Second, if the spatial itself moved, it means the distance from it to
195     * the individual lights might have changed.
196     *
197     *
198     * @param transformChanged Whether the spatial's transform has changed
199     */
200    public void sort(boolean transformChanged) {
201        if (listSize > 1) {
202            // resize or populate our temporary array as necessary
203            if (tlist == null || tlist.length != list.length) {
204                tlist = list.clone();
205            } else {
206                System.arraycopy(list, 0, tlist, 0, list.length);
207            }
208
209            if (transformChanged){
210                // check distance of each light
211                for (int i = 0; i < listSize; i++){
212                    list[i].computeLastDistance(owner);
213                }
214            }
215
216            // now merge sort tlist into list
217            SortUtil.msort(tlist, list, 0, listSize - 1, c);
218        }
219    }
220
221    /**
222     * Updates a "world-space" light list, using the spatial's local-space
223     * light list and its parent's world-space light list.
224     *
225     * @param local
226     * @param parent
227     */
228    public void update(LightList local, LightList parent){
229        // clear the list as it will be reconstructed
230        // using the arguments
231        clear();
232
233        while (list.length <= local.listSize){
234            doubleSize();
235        }
236
237        // add the lights from the local list
238        System.arraycopy(local.list, 0, list, 0, local.listSize);
239        for (int i = 0; i < local.listSize; i++){
240//            list[i] = local.list[i];
241            distToOwner[i] = Float.NEGATIVE_INFINITY;
242        }
243
244        // if the spatial has a parent node, add the lights
245        // from the parent list as well
246        if (parent != null){
247            int sz = local.listSize + parent.listSize;
248            while (list.length <= sz)
249                doubleSize();
250
251            for (int i = 0; i < parent.listSize; i++){
252                int p = i + local.listSize;
253                list[p] = parent.list[i];
254                distToOwner[p] = Float.NEGATIVE_INFINITY;
255            }
256
257            listSize = local.listSize + parent.listSize;
258        }else{
259            listSize = local.listSize;
260        }
261    }
262
263    /**
264     * Returns an iterator that can be used to iterate over this LightList.
265     *
266     * @return an iterator that can be used to iterate over this LightList.
267     */
268    public Iterator<Light> iterator() {
269        return new Iterator<Light>(){
270
271            int index = 0;
272
273            public boolean hasNext() {
274                return index < size();
275            }
276
277            public Light next() {
278                if (!hasNext())
279                    throw new NoSuchElementException();
280
281                return list[index++];
282            }
283
284            public void remove() {
285                LightList.this.remove(--index);
286            }
287        };
288    }
289
290    @Override
291    public LightList clone(){
292        try{
293            LightList clone = (LightList) super.clone();
294
295            clone.owner = null;
296            clone.list = list.clone();
297            clone.distToOwner = distToOwner.clone();
298            clone.tlist = null; // list used for sorting only
299
300            return clone;
301        }catch (CloneNotSupportedException ex){
302            throw new AssertionError();
303        }
304    }
305
306    public void write(JmeExporter ex) throws IOException {
307        OutputCapsule oc = ex.getCapsule(this);
308//        oc.write(owner, "owner", null);
309
310        ArrayList<Light> lights = new ArrayList<Light>();
311        for (int i = 0; i < listSize; i++){
312            lights.add(list[i]);
313        }
314        oc.writeSavableArrayList(lights, "lights", null);
315    }
316
317    public void read(JmeImporter im) throws IOException {
318        InputCapsule ic = im.getCapsule(this);
319//        owner = (Spatial) ic.readSavable("owner", null);
320
321        List<Light> lights = ic.readSavableArrayList("lights", null);
322        listSize = lights.size();
323
324        // NOTE: make sure the array has a length of at least 1
325        int arraySize = Math.max(DEFAULT_SIZE, listSize);
326        list = new Light[arraySize];
327        distToOwner = new float[arraySize];
328
329        for (int i = 0; i < listSize; i++){
330            list[i] = lights.get(i);
331        }
332
333        Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
334    }
335
336}
337