/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.graphics.g3d.particles; import java.util.Arrays; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.reflect.ArrayReflection; /** This class represents an group of elements like an array, but the properties of the elements are stored as separate arrays. * These arrays are called {@link Channel} and are represented by {@link ChannelDescriptor}. It's not necessary to store primitive * types in the channels but doing so will "exploit" data locality in the JVM, which is ensured for primitive types. Use * {@link FloatChannel}, {@link IntChannel}, {@link ObjectChannel} to store the data. * @author inferno */ public class ParallelArray { /** This class describes the content of a {@link Channel} */ public static class ChannelDescriptor { public int id; public Class<?> type; public int count; public ChannelDescriptor (int id, Class<?> type, int count) { this.id = id; this.type = type; this.count = count; } } /** This class represents a container of values for all the elements for a given property */ public abstract class Channel { public int id; public Object data; public int strideSize; public Channel (int id, Object data, int strideSize) { this.id = id; this.strideSize = strideSize; this.data = data; } public abstract void add (int index, Object... objects); public abstract void swap (int i, int k); protected abstract void setCapacity (int requiredCapacity); } /** This interface is used to provide custom initialization of the {@link Channel} data */ public static interface ChannelInitializer<T extends Channel> { public void init (T channel); } public class FloatChannel extends Channel { public float[] data; public FloatChannel (int id, int strideSize, int size) { super(id, new float[size * strideSize], strideSize); this.data = (float[])super.data; } @Override public void add (int index, Object... objects) { for (int i = strideSize * size, c = i + strideSize, k = 0; i < c; ++i, ++k) { data[i] = (Float)objects[k]; } } @Override public void swap (int i, int k) { float t; i = strideSize * i; k = strideSize * k; for (int c = i + strideSize; i < c; ++i, ++k) { t = data[i]; data[i] = data[k]; data[k] = t; } } @Override public void setCapacity (int requiredCapacity) { float[] newData = new float[strideSize * requiredCapacity]; System.arraycopy(data, 0, newData, 0, Math.min(data.length, newData.length)); super.data = data = newData; } } public class IntChannel extends Channel { public int[] data; public IntChannel (int id, int strideSize, int size) { super(id, new int[size * strideSize], strideSize); this.data = (int[])super.data; } @Override public void add (int index, Object... objects) { for (int i = strideSize * size, c = i + strideSize, k = 0; i < c; ++i, ++k) { data[i] = (Integer)objects[k]; } } @Override public void swap (int i, int k) { int t; i = strideSize * i; k = strideSize * k; for (int c = i + strideSize; i < c; ++i, ++k) { t = data[i]; data[i] = data[k]; data[k] = t; } } @Override public void setCapacity (int requiredCapacity) { int[] newData = new int[strideSize * requiredCapacity]; System.arraycopy(data, 0, newData, 0, Math.min(data.length, newData.length)); super.data = data = newData; } } @SuppressWarnings("unchecked") public class ObjectChannel<T> extends Channel { Class<T> componentType; public T[] data; public ObjectChannel (int id, int strideSize, int size, Class<T> type) { super(id, ArrayReflection.newInstance(type, size * strideSize), strideSize); componentType = type; this.data = (T[])super.data; } @Override public void add (int index, Object... objects) { for (int i = strideSize * size, c = i + strideSize, k = 0; i < c; ++i, ++k) { this.data[i] = (T)objects[k]; } } @Override public void swap (int i, int k) { T t; i = strideSize * i; k = strideSize * k; for (int c = i + strideSize; i < c; ++i, ++k) { t = data[i]; data[i] = data[k]; data[k] = t; } } @Override public void setCapacity (int requiredCapacity) { T[] newData = (T[])ArrayReflection.newInstance(componentType, strideSize * requiredCapacity); System.arraycopy(data, 0, newData, 0, Math.min(data.length, newData.length)); super.data = data = newData; } } /** the channels added to the array */ Array<Channel> arrays; /** the maximum amount of elements that this array can hold */ public int capacity; /** the current amount of defined elements, do not change manually unless you know what you are doing. */ public int size; public ParallelArray (int capacity) { arrays = new Array<Channel>(false, 2, Channel.class); this.capacity = capacity; size = 0; } /** Adds and returns a channel described by the channel descriptor parameter. If a channel with the same id already exists, no * allocation is performed and that channel is returned. */ public <T extends Channel> T addChannel (ChannelDescriptor channelDescriptor) { return addChannel(channelDescriptor, null); } /** Adds and returns a channel described by the channel descriptor parameter. If a channel with the same id already exists, no * allocation is performed and that channel is returned. Otherwise a new channel is allocated and initialized with the * initializer. */ public <T extends Channel> T addChannel (ChannelDescriptor channelDescriptor, ChannelInitializer<T> initializer) { T channel = getChannel(channelDescriptor); if (channel == null) { channel = allocateChannel(channelDescriptor); if (initializer != null) initializer.init(channel); arrays.add(channel); } return channel; } @SuppressWarnings({"unchecked", "rawtypes"}) private <T extends Channel> T allocateChannel (ChannelDescriptor channelDescriptor) { if (channelDescriptor.type == float.class) { return (T)new FloatChannel(channelDescriptor.id, channelDescriptor.count, capacity); } else if (channelDescriptor.type == int.class) { return (T)new IntChannel(channelDescriptor.id, channelDescriptor.count, capacity); } else { return (T)new ObjectChannel(channelDescriptor.id, channelDescriptor.count, capacity, channelDescriptor.type); } } /** Removes the channel with the given id */ public <T> void removeArray (int id) { arrays.removeIndex(findIndex(id)); } private int findIndex (int id) { for (int i = 0; i < arrays.size; ++i) { Channel array = arrays.items[i]; if (array.id == id) return i; } return -1; } /** Adds an element considering the values in the same order as the current channels in the array. The n_th value must have the * same type and stride of the given channel at position n */ public void addElement (Object... values) { /* FIXME make it grow... */ if (size == capacity) throw new GdxRuntimeException("Capacity reached, cannot add other elements"); int k = 0; for (Channel strideArray : arrays) { strideArray.add(k, values); k += strideArray.strideSize; } ++size; } /** Removes the element at the given index and swaps it with the last available element */ public void removeElement (int index) { int last = size - 1; // Swap for (Channel strideArray : arrays) { strideArray.swap(index, last); } size = last; } /** @return the channel with the same id as the one in the descriptor */ @SuppressWarnings("unchecked") public <T extends Channel> T getChannel (ChannelDescriptor descriptor) { for (Channel array : arrays) { if (array.id == descriptor.id) return (T)array; } return null; } /** Removes all the channels and sets size to 0 */ public void clear () { arrays.clear(); size = 0; } /** Sets the capacity. Each contained channel will be resized to match the required capacity and the current data will be * preserved. */ public void setCapacity (int requiredCapacity) { if (capacity != requiredCapacity) { for (Channel channel : arrays) { channel.setCapacity(requiredCapacity); } capacity = requiredCapacity; } } }