/*
Copyright (C) 2010 Copyright 2010 Google Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package com.googlecode.gwtquake.client;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayInteger;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.ErrorEvent;
import com.google.gwt.event.dom.client.ErrorHandler;
import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.html5.client.CanvasElement;
import com.google.gwt.html5.client.CanvasPixelArray;
import com.google.gwt.html5.client.CanvasRenderingContext2D;
import com.google.gwt.html5.client.ImageData;
import com.google.gwt.user.client.Timer;
import com.googlecode.gwtquake.shared.client.Renderer;
import com.googlecode.gwtquake.shared.common.Com;
import com.googlecode.gwtquake.shared.common.ResourceLoader;
import com.googlecode.gwtquake.shared.render.GlRenderer;
import com.googlecode.gwtquake.shared.render.GlState;
import com.googlecode.gwtquake.shared.render.Images;
import com.googlecode.gwtquake.shared.render.Image;
import com.googlecode.gwtquake.shared.sys.KBD;
import static com.google.gwt.webgl.client.WebGLRenderingContext.*;
public class GwtWebGLRenderer extends GlRenderer implements Renderer {
KBD kbd = new GwtKBD();
public KBD getKeyboardHandler() {
return kbd;
}
static class VideoElement extends Element {
protected VideoElement() {
}
native final JavaScriptObject getError() /*-{
return this.error;
}-*/;
public final native void pause() /*-{
this.pause();
}-*/;
public final native void play() /*-{
this.play();
}-*/;
public final native double getDuration() /*-{
return this.duration;
}-*/;
public final native double getCurrentTime() /*-{
return this.currentTime;
}-*/;
public final native void setCurrentTime(double s) /*-{
return this.currentTime = s;
}-*/;
public final boolean ended() {
// System.out.println("video.error: " + getError() +
// " duration: " + getDuration() +
// " currentTime: " + getCurrentTime());
//
return getError() != null || !(Double.isNaN(getDuration()) ||
getCurrentTime() < getDuration());
}
}
static final int IMAGE_CHECK_TIME = 250;
static final int MAX_IMAGE_REQUEST_COUNT = 12;
static int HOLODECK_TEXTURE_SIZE = 128;
static int MASK = 15;
static int HIT = MASK/2;
ByteBuffer holoDeckTexture = ByteBuffer.allocateDirect(HOLODECK_TEXTURE_SIZE *
HOLODECK_TEXTURE_SIZE * 4);
WebGLGl1Contect webGL;
CanvasElement canvas1;
CanvasElement canvas2;
ArrayList<Image> imageQueue = new ArrayList<Image>();
int waitingForImages;
VideoElement video;
CanvasElement canvas;
public GwtWebGLRenderer(CanvasElement canvas, Element video) {
super(canvas.getWidth(), canvas.getHeight());
GlState.gl = this.webGL = new WebGLGl1Contect(canvas);
this.canvas = canvas;
this.video = (VideoElement) video;
for (int y = 0; y < HOLODECK_TEXTURE_SIZE; y++) {
for (int x = 0; x < HOLODECK_TEXTURE_SIZE; x++) {
holoDeckTexture.put((byte) 0);
holoDeckTexture.put((byte) (((x & MASK) == HIT) || ((y & MASK) == HIT) ? 255 : 0));
holoDeckTexture.put((byte) 0);
holoDeckTexture.put((byte) 0xff);
}
}
holoDeckTexture.rewind();
canvas1 = (CanvasElement) Document.get().createElement("canvas");
canvas1.getStyle().setDisplay(Display.NONE);
canvas1.setWidth(128);
canvas1.setHeight(128);
Document.get().getBody().appendChild(canvas1);
canvas2 = (CanvasElement) Document.get().createElement("canvas");
canvas2.setWidth(128);
canvas2.setHeight(128);
canvas2.getStyle().setDisplay(Display.NONE);
Document.get().getBody().appendChild(canvas2);
init();
}
@Override
public void GL_ResampleTexture(int[] in, int inwidth, int inheight,
int[] out, int outwidth, int outheight) {
if (canvas1.getWidth() < inwidth) {
canvas1.setWidth(inwidth);
}
if (canvas1.getHeight() < inheight) {
canvas1.setHeight(inheight);
}
CanvasRenderingContext2D inCtx = canvas1.getContext2D();
ImageData data = inCtx.createImageData(inwidth, inheight);
CanvasPixelArray pixels = data.getData();
int len = inwidth * inheight;
int p = 0;
for(int i = 0; i < len; i++) {
int abgr = in[i];
pixels.set(p, (abgr & 255));
pixels.set(p + 1, (abgr >> 8) & 255);
pixels.set(p + 2, (abgr >> 16) & 255);
pixels.set(p + 3, (abgr >> 24) & 255);
p += 4;
}
inCtx.putImageData(data, 0, 0);
if (canvas2.getWidth() < outwidth) {
canvas2.setWidth(outwidth);
}
if (canvas2.getHeight() < outheight) {
canvas2.setHeight(outheight);
}
CanvasRenderingContext2D outCtx = canvas2.getContext2D();
outCtx.drawImage(canvas1, 0, 0, inwidth, inheight, 0, 0, outwidth, outheight);
data = outCtx.getImageData(0, 0, outwidth, outheight);
pixels = data.getData();
len = outwidth * outheight;
p = 0;
for(int i = 0; i < len; i++) {
int r = pixels.get(p) & 255;
int g = pixels.get(p + 1) & 255;
int b = pixels.get(p + 2) & 255;
int a = pixels.get(p + 3) & 255;
p += 4;
out[i] = (a << 24) | (b << 16) | (g << 8) | r;
}
}
static native JsArrayInteger getImageSize(String name) /*-{
return $wnd.__imageSizes[name];
}-*/;
public Image GL_LoadNewImage(final String name, int type) {
final Image image = Images.GL_Find_free_image_t(name, type);
int cut = name.lastIndexOf('.');
String normalizedName = cut == -1 ? name : name.substring(0, cut);
JsArrayInteger d = getImageSize(normalizedName);
if (d == null) {
GlState.gl.log("Size not found for " + name);
image.width = 128;
image.height = 128;
} else {
image.width = d.get(0);
image.height = d.get(1);
}
if (type != com.googlecode.gwtquake.shared.common.QuakeImage.it_pic) {
GlState.gl.glTexImage2D(TEXTURE_2D, 0, RGBA, HOLODECK_TEXTURE_SIZE, HOLODECK_TEXTURE_SIZE, 0, RGBA,
UNSIGNED_BYTE, holoDeckTexture);
GlState.gl.glTexParameterf(TEXTURE_2D, TEXTURE_MIN_FILTER, LINEAR);
GlState.gl.glTexParameterf(TEXTURE_2D, TEXTURE_MAG_FILTER, LINEAR);
}
imageQueue.add(image);
if(imageQueue.size() == 1) {
new ImageLoader().schedule();
}
return image;
}
class ImageLoader extends Timer {
@Override
public void run() {
Document doc = Document.get();
while(!ResourceLoader.Pump() && waitingForImages < MAX_IMAGE_REQUEST_COUNT && imageQueue.size() > 0) {
final Image image = imageQueue.remove(0);
final ImageElement img = doc.createImageElement();
String picUrl = convertPicName(image.name, image.type);
/* if (picUrl.endsWith("ggrat6_2.png")) {
picUrl = convertPicName("textures/tron_poster.jpg", 0);
}*/
img.setSrc(picUrl);
img.getStyle().setDisplay(Display.NONE);
doc.getBody().appendChild(img);
if (img.getPropertyBoolean("complete")) {
loaded(image, img);
} else {
waitingForImages(+1);
com.google.gwt.user.client.ui.Image imgWidget =
com.google.gwt.user.client.ui.Image.wrap(img);
final ImageElement finalImg = img;
imgWidget.addLoadHandler(new LoadHandler() {
public void onLoad(LoadEvent event) {
waitingForImages(-1);
loaded(image, finalImg);
}
});
imgWidget.addErrorHandler(new ErrorHandler() {
public void onError(ErrorEvent event) {
// String src = finalImg.getSrc();
// if (src.endsWith("&rt&rt&rt&rt")) {
GlState.gl.log("load errors for " + finalImg.getSrc() );
waitingForImages(-1);
image.complete = true;
// } else {
// finalImg.setSrc(finalImg.getSrc() + "&rt");
// }
}
});
} // else
} // while
if (imageQueue.size() > 0) {
schedule();
}
}
protected void waitingForImages(int i) {
waitingForImages += i;
if (waitingForImages > 0) {
Com.Printf("Waiting for " + waitingForImages + " images\n");
}
}
public void schedule() {
schedule(IMAGE_CHECK_TIME);
}
}
public void loaded(Image image, ImageElement img) {
setPicDataHighLevel(image, img);
// setPicDataLowLevel(image, img);
}
ByteBuffer bb = ByteBuffer.allocateDirect(128*128*4);
public void setPicDataHighLevel(Image image, ImageElement img) {
image.has_alpha = true;
image.complete = true;
image.height = img.getHeight();
image.width = img.getWidth();
boolean mipMap = image.type != com.googlecode.gwtquake.shared.common.QuakeImage.it_pic &&
image.type != com.googlecode.gwtquake.shared.common.QuakeImage.it_sky;
Images.GL_Bind(image.texnum);
int p2w = 1 << ((int) Math.ceil(Math.log(image.width) / Math.log(2)));
int p2h = 1 << ((int) Math.ceil(Math.log(image.height) / Math.log(2)));
if (mipMap) {
p2w = p2h = Math.max(p2w, p2h);
}
image.upload_width = p2w;
image.upload_height = p2h;
int level = 0;
do {
canvas1.setWidth(p2w);
canvas1.setHeight(p2h);
canvas1.getContext2D().clearRect(0, 0, p2w, p2h);
canvas1.getContext2D().drawImage(img, 0, 0, p2w, p2h);
webGL.glTexImage2d(TEXTURE_2D, level++, RGBA, RGBA, UNSIGNED_BYTE, canvas1);
p2w = p2w / 2;
p2h = p2h / 2;
}
while(mipMap && p2w > 0);
GlState.gl.glTexParameterf(TEXTURE_2D, TEXTURE_MIN_FILTER,
mipMap ? LINEAR_MIPMAP_NEAREST : LINEAR);
GlState.gl.glTexParameterf(TEXTURE_2D, TEXTURE_MAG_FILTER, LINEAR);
}
public void __setPicDataHighLevel(Image image, ImageElement img) {
image.has_alpha = true;
image.complete = true;
image.height = img.getHeight();
image.width = img.getWidth();
image.upload_height = image.height;
image.upload_width = image.width;
Images.GL_Bind(image.texnum);
webGL.glTexImage2d(TEXTURE_2D, 0, RGBA, RGBA, UNSIGNED_BYTE, img);
GlState.gl.glTexParameterf(TEXTURE_2D, TEXTURE_MIN_FILTER, LINEAR);
GlState.gl.glTexParameterf(TEXTURE_2D, TEXTURE_MAG_FILTER, LINEAR);
}
public void setPicDataLowLevel(Image image, ImageElement img) {
CanvasElement canvas = (CanvasElement) Document.get().createElement("canvas");
int w = img.getWidth();
int h = img.getHeight();
canvas.setWidth(w);
canvas.setHeight(h);
// canvas.getStyle().setProperty("border", "solid 1px green");
canvas.getStyle().setDisplay(Display.NONE);
Document.get().getBody().appendChild(canvas);
CanvasRenderingContext2D ctx = canvas.getContext2D();
ctx.drawImage(img, 0, 0);
ImageData data = ctx.getImageData(0, 0, w, h);
CanvasPixelArray pixels = data.getData();
int count = w * h * 4;
byte[] pic = new byte[count];
for (int i = 0; i < count; i += 4) {
pic[i + 3] = (byte) pixels.get(i + 3); // alpha, then bgr
pic[i + 2] = (byte) pixels.get(i + 2);
pic[i + 1] = (byte) pixels.get(i + 1);
pic[i] = (byte) pixels.get(i);
}
image.setData(pic, w, h, 32);
}
protected void debugLightmap(IntBuffer lightmapBuffer, int w, int h, float scale) {
CanvasElement canvas = (CanvasElement) Document.get().createElement("canvas");
canvas.setWidth(w);
canvas.setHeight(h);
Document.get().getBody().appendChild(canvas);
ImageData id = canvas.getContext2D().createImageData(w, h);
CanvasPixelArray pd = id.getData();
for (int i = 0; i < w*h; i++) {
int abgr = lightmapBuffer.get(i);
pd.set(i*4, abgr & 255);
pd.set(i*4+1, abgr & 255);
pd.set(i*4+2, abgr & 255);
pd.set(i*4+3, abgr & 255);
}
canvas.getContext2D().putImageData(id, 0, 0);
}
private static String convertPicName(String name, int type) {
int dotIdx = name.indexOf('.');
assert dotIdx != -1;
return "baseq2/" + name.substring(0, dotIdx) + ".png";
}
public boolean updateVideo() {
return !video.ended();
}
public void CinematicSetPalette(byte[] palette) {
setVideoVisible(palette != null);
}
boolean videoVisible = false;
private void setVideoVisible(boolean show) {
if (videoVisible == show) {
return;
}
System.out.println("setVideoVisible(" + show + ")");
videoVisible = show;
if (show) {
canvas.getStyle().setProperty("display", "none");
video.getStyle().setProperty("display", "");
if (video.getAttribute("src") != null && !video.ended()) {
video.play();
}
} else {
canvas.getStyle().setProperty("display", "");
video.getStyle().setProperty("display", "none");
if (video.getAttribute("src") != null && !video.ended()) {
video.pause();
}
}
}
public boolean showVideo(String name) {
if (name == null) {
setVideoVisible(false);
return true;
}
// String src = GWT.getModuleBaseURL();
// int cut = src.indexOf("/", 8);
// if (cut == -1) {
// cut = src.length();
// }
String src = "baseq2/video/" + name + ".mp4";
System.out.println("trying to play video: " + src);
video.setAttribute("class", "video-stream");
video.setAttribute("src", src);
if (!Double.isNaN(video.getDuration())) {
video.setCurrentTime(0);
}
setVideoVisible(true);
return true;
}
}