// Copyright 2008 Google Inc.
//
// 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.google.android.stardroid.renderer;
import android.util.Log;
import com.google.android.stardroid.R;
import com.google.android.stardroid.renderer.util.IndexBuffer;
import com.google.android.stardroid.renderer.util.NightVisionColorBuffer;
import com.google.android.stardroid.renderer.util.SkyRegionMap;
import com.google.android.stardroid.renderer.util.TexCoordBuffer;
import com.google.android.stardroid.renderer.util.TextureManager;
import com.google.android.stardroid.renderer.util.TextureReference;
import com.google.android.stardroid.renderer.util.VertexBuffer;
import com.google.android.stardroid.source.PointSource;
import com.google.android.stardroid.units.Vector3;
import com.google.android.stardroid.util.MathUtil;
import com.google.android.stardroid.util.VectorUtil;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import javax.microedition.khronos.opengles.GL10;
public class PointObjectManager extends RendererObjectManager {
private static final int NUM_STARS_IN_TEXTURE = 2;
// Small sets of point aren't worth breaking up into regions.
// Right now, I'm arbitrarily setting the threshold to 200.
private static final int MINIMUM_NUM_POINTS_FOR_REGIONS = 200;
private class RegionData {
// TODO(jpowell): This is a convenient hack until the catalog tells us the
// region for all of its sources. Remove this once we add that.
List<PointSource> sources = new ArrayList<PointSource>();
private VertexBuffer mVertexBuffer = new VertexBuffer(true);
private NightVisionColorBuffer mColorBuffer = new NightVisionColorBuffer(true);
private TexCoordBuffer mTexCoordBuffer = new TexCoordBuffer(true);
private IndexBuffer mIndexBuffer = new IndexBuffer(true);
}
// Should we compute the regions for the points?
// If false, we just put them in the catchall region.
private static final boolean COMPUTE_REGIONS = true;
private int mNumPoints = 0;
private SkyRegionMap<RegionData> mSkyRegions = new SkyRegionMap<RegionData>();
private TextureReference mTextureRef = null;
public PointObjectManager(int layer, TextureManager textureManager) {
super(layer, textureManager);
// We want to initialize the labels of a sky region to an empty set of data.
mSkyRegions.setRegionDataFactory(
new SkyRegionMap.RegionDataFactory<RegionData>() {
public RegionData construct() { return new RegionData(); }
});
}
public void updateObjects(List<PointSource> points, EnumSet<UpdateType> updateType) {
boolean onlyUpdatePoints = true;
// We only care about updates to positions, ignore any other updates.
if (updateType.contains(UpdateType.Reset)) {
onlyUpdatePoints = false;
} else if (updateType.contains(UpdateType.UpdatePositions)) {
// Sanity check: make sure the number of points is unchanged.
if (points.size() != mNumPoints) {
Log.e("PointObjectManager",
"Updating PointObjectManager a different number of points: update had " +
points.size() + " vs " + mNumPoints + " before");
return;
}
} else {
return;
}
mNumPoints = points.size();
mSkyRegions.clear();
if (COMPUTE_REGIONS) {
// Find the region for each point, and put it in a separate list
// for that region.
for (PointSource point : points) {
int region = points.size() < MINIMUM_NUM_POINTS_FOR_REGIONS
? SkyRegionMap.CATCHALL_REGION_ID
: SkyRegionMap.getObjectRegion(point.getLocation());
mSkyRegions.getRegionData(region).sources.add(point);
}
} else {
mSkyRegions.getRegionData(SkyRegionMap.CATCHALL_REGION_ID).sources = points;
}
// Generate the resources for all of the regions.
for (RegionData data : mSkyRegions.getDataForAllRegions()) {
int numVertices = 4 * data.sources.size();
int numIndices = 6 * data.sources.size();
data.mVertexBuffer.reset(numVertices);
data.mColorBuffer.reset(numVertices);
data.mTexCoordBuffer.reset(numVertices);
data.mIndexBuffer.reset(numIndices);
Vector3 up = new Vector3(0, 1, 0);
// By inspecting the perspective projection matrix, you can show that,
// to have a quad at the center of the screen to be of size k by k
// pixels, the width and height are both:
// k * tan(fovy / 2) / screenHeight
// This is not difficult to derive. Look at the transformation matrix
// in SkyRenderer if you're interested in seeing why this is true.
// I'm arbitrarily deciding that at a 60 degree field of view, and 480
// pixels high, a size of 1 means "1 pixel," so calculate sizeFactor
// based on this. These numbers mostly come from the fact that that's
// what I think looks reasonable.
float fovyInRadians = 60 * MathUtil.PI / 180.0f;
float sizeFactor = MathUtil.tan(fovyInRadians * 0.5f) / 480;
Vector3 bottomLeftPos = new Vector3(0, 0, 0);
Vector3 topLeftPos = new Vector3(0, 0, 0);
Vector3 bottomRightPos = new Vector3(0, 0, 0);
Vector3 topRightPos = new Vector3(0, 0, 0);
Vector3 su = new Vector3(0, 0, 0);
Vector3 sv = new Vector3(0, 0, 0);
short index = 0;
float starWidthInTexels = 1.0f / NUM_STARS_IN_TEXTURE;
for (PointSource p : data.sources) {
int color = 0xff000000 | p.getColor(); // Force alpha to 0xff
short bottomLeft = index++;
short topLeft = index++;
short bottomRight = index++;
short topRight = index++;
// First triangle
data.mIndexBuffer.addIndex(bottomLeft);
data.mIndexBuffer.addIndex(topLeft);
data.mIndexBuffer.addIndex(bottomRight);
// Second triangle
data.mIndexBuffer.addIndex(topRight);
data.mIndexBuffer.addIndex(bottomRight);
data.mIndexBuffer.addIndex(topLeft);
int starIndex = p.getPointShape().getImageIndex();
float texOffsetU = starWidthInTexels * starIndex;
data.mTexCoordBuffer.addTexCoords(texOffsetU, 1);
data.mTexCoordBuffer.addTexCoords(texOffsetU, 0);
data.mTexCoordBuffer.addTexCoords(texOffsetU + starWidthInTexels, 1);
data.mTexCoordBuffer.addTexCoords(texOffsetU + starWidthInTexels, 0);
Vector3 pos = p.getLocation();
Vector3 u = VectorUtil.normalized(VectorUtil.crossProduct(pos, up));
Vector3 v = VectorUtil.crossProduct(u, pos);
float s = p.getSize() * sizeFactor;
su.assign(s*u.x, s*u.y, s*u.z);
sv.assign(s*v.x, s*v.y, s*v.z);
bottomLeftPos.assign(pos.x - su.x - sv.x, pos.y - su.y - sv.y, pos.z - su.z - sv.z);
topLeftPos.assign(pos.x - su.x + sv.x, pos.y - su.y + sv.y, pos.z - su.z + sv.z);
bottomRightPos.assign(pos.x + su.x - sv.x, pos.y + su.y - sv.y, pos.z + su.z - sv.z);
topRightPos.assign(pos.x + su.x + sv.x, pos.y + su.y + sv.y, pos.z + su.z + sv.z);
// Add the vertices
data.mVertexBuffer.addPoint(bottomLeftPos);
data.mColorBuffer.addColor(color);
data.mVertexBuffer.addPoint(topLeftPos);
data.mColorBuffer.addColor(color);
data.mVertexBuffer.addPoint(bottomRightPos);
data.mColorBuffer.addColor(color);
data.mVertexBuffer.addPoint(topRightPos);
data.mColorBuffer.addColor(color);
}
Log.i("PointObjectManager",
"Vertices: " + data.mVertexBuffer.size() + ", Indices: " + data.mIndexBuffer.size());
data.sources = null;
}
}
@Override
public void reload(GL10 gl, boolean fullReload) {
mTextureRef = textureManager().getTextureFromResource(gl, R.drawable.stars_texture);
for (RegionData data : mSkyRegions.getDataForAllRegions()) {
data.mVertexBuffer.reload();
data.mColorBuffer.reload();
data.mTexCoordBuffer.reload();
data.mIndexBuffer.reload();
}
}
@Override
protected void drawInternal(GL10 gl) {
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glEnable(GL10.GL_CULL_FACE);
gl.glFrontFace(GL10.GL_CW);
gl.glCullFace(GL10.GL_BACK);
gl.glEnable(GL10.GL_ALPHA_TEST);
gl.glAlphaFunc(GL10.GL_GREATER, 0.5f);
gl.glEnable(GL10.GL_TEXTURE_2D);
mTextureRef.bind(gl);
gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);
// Render all of the active sky regions.
SkyRegionMap.ActiveRegionData activeRegions = getRenderState().getActiveSkyRegions();
ArrayList<RegionData> activeRegionData = mSkyRegions.getDataForActiveRegions(activeRegions);
for (RegionData data : activeRegionData) {
if (data.mVertexBuffer.size() == 0) {
continue;
}
data.mVertexBuffer.set(gl);
data.mColorBuffer.set(gl, getRenderState().getNightVisionMode());
data.mTexCoordBuffer.set(gl);
data.mIndexBuffer.draw(gl, GL10.GL_TRIANGLES);
}
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisable(GL10.GL_TEXTURE_2D);
gl.glDisable(GL10.GL_ALPHA_TEST);
}
}