From 7175a287537e4b95a396f703d6f2afe633061507 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 24 Nov 2019 16:02:20 -0600 Subject: [PATCH] More progress, working on getting viewer --- .../warsmash/datasources/DataSource.java | 46 +++ .../datasources/DataSourceDescriptor.java | 9 + .../datasources/FolderDataSource.java | 68 ++++ .../FolderDataSourceDescriptor.java | 57 +++ .../warsmash/util/RenderMathUtils.java | 44 +++ .../warsmash/viewer5/AudioContext.java | 47 +++ .../com/etheller/warsmash/viewer5/Bounds.java | 5 + .../com/etheller/warsmash/viewer5/Camera.java | 326 ++++++++++++++++++ .../warsmash/viewer5/CanvasProvider.java | 7 + .../warsmash/viewer5/EmittedObject.java | 11 + .../viewer5/EmittedObjectUpdater.java | 40 +++ .../etheller/warsmash/viewer5/Emitter.java | 75 ++++ .../com/etheller/warsmash/viewer5/Grid.java | 119 +++++++ .../etheller/warsmash/viewer5/GridCell.java | 44 +++ .../com/etheller/warsmash/viewer5/Model.java | 9 + .../warsmash/viewer5/ModelInstance.java | 47 +++ .../warsmash/viewer5/ModelViewer.java | 169 +++++++++ .../etheller/warsmash/viewer5/Resource.java | 12 + .../warsmash/viewer5/ResourceLoader.java | 4 + .../com/etheller/warsmash/viewer5/Scene.java | 290 ++++++++++++++++ .../warsmash/viewer5/TextureMapper.java | 25 ++ .../viewer5/deprecated/ShaderProgram.java | 15 + .../deprecated/ShaderUnitDeprecated.java | 31 ++ .../etheller/warsmash/viewer5/gl/WebGL.java | 139 ++++++++ .../warsmash/viewer5/handlers/Batch.java | 22 ++ .../viewer5/handlers/BatchDescriptor.java | 9 + .../viewer5/handlers/EmitterObject.java | 5 + .../warsmash/viewer5/handlers/Handler.java | 5 + 28 files changed, 1680 insertions(+) create mode 100644 core/src/com/etheller/warsmash/datasources/DataSource.java create mode 100644 core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java create mode 100644 core/src/com/etheller/warsmash/datasources/FolderDataSource.java create mode 100644 core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/AudioContext.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Bounds.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Camera.java create mode 100644 core/src/com/etheller/warsmash/viewer5/CanvasProvider.java create mode 100644 core/src/com/etheller/warsmash/viewer5/EmittedObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Emitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Grid.java create mode 100644 core/src/com/etheller/warsmash/viewer5/GridCell.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Model.java create mode 100644 core/src/com/etheller/warsmash/viewer5/ModelInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/ModelViewer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Resource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/ResourceLoader.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Scene.java create mode 100644 core/src/com/etheller/warsmash/viewer5/TextureMapper.java create mode 100644 core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java create mode 100644 core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/WebGL.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/Batch.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/Handler.java diff --git a/core/src/com/etheller/warsmash/datasources/DataSource.java b/core/src/com/etheller/warsmash/datasources/DataSource.java new file mode 100644 index 0000000..8a279d1 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/DataSource.java @@ -0,0 +1,46 @@ +package com.etheller.warsmash.datasources; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +public interface DataSource { + /** + * Efficiently return a stream instance that will read the data source file's + * contents directly from the data source. For example, this will read a file + * within an MPQ or CASC storage without extracting it. + * + * @param filepath + * @return + * @throws IOException + */ + InputStream getResourceAsStream(String filepath) throws IOException; + + /** + * Inefficiently copy a file from the data source onto the Hard Drive of the + * computer, and then return a java File instance pointed at the file. + * + * @param filepath + * @return + * @throws IOException + */ + File getFile(String filepath) throws IOException; + + /** + * Returns true if the data source contains a valid entry for a particular file. + * Some data sources (MPQs) may contain files for which this returns true, even + * though they cannot list the file in their Listfile. + * + * @param filepath + * @return + */ + boolean has(String filepath); + + /** + * @return a list of data source contents, or null if no list is provided + */ + Collection getListfile(); + + void close() throws IOException; +} diff --git a/core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java new file mode 100644 index 0000000..2b4fd30 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.datasources; + +import java.io.Serializable; + +public interface DataSourceDescriptor extends Serializable { + DataSource createDataSource(); + + String getDisplayName(); +} diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java new file mode 100644 index 0000000..7fac978 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java @@ -0,0 +1,68 @@ +package com.etheller.warsmash.datasources; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +public class FolderDataSource implements DataSource { + + private final Path folderPath; + private final Set listfile; + + public FolderDataSource(final Path folderPath) { + this.folderPath = folderPath; + this.listfile = new HashSet<>(); + try { + Files.walk(folderPath).filter(Files::isRegularFile).forEach(new Consumer() { + @Override + public void accept(final Path t) { + FolderDataSource.this.listfile.add(folderPath.relativize(t).toString()); + } + }); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public InputStream getResourceAsStream(final String filepath) throws IOException { + if (!has(filepath)) { + return null; + } + return Files.newInputStream(this.folderPath.resolve(filepath), StandardOpenOption.READ); + } + + @Override + public File getFile(final String filepath) throws IOException { + if (!has(filepath)) { + return null; + } + return new File(this.folderPath.toString() + File.separatorChar + filepath); + } + + @Override + public boolean has(final String filepath) { + if ("".equals(filepath)) { + return false; // special case for folder data source, dont do this + } + return Files.exists(this.folderPath.resolve(filepath)); + } + + @Override + public Collection getListfile() { + return this.listfile; + } + + @Override + public void close() { + } + +} diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java new file mode 100644 index 0000000..174aad3 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.datasources; + +import java.nio.file.Paths; + +public class FolderDataSourceDescriptor implements DataSourceDescriptor { + /** + * Generated serial id + */ + private static final long serialVersionUID = -476724730967709309L; + private final String folderPath; + + public FolderDataSourceDescriptor(final String folderPath) { + this.folderPath = folderPath; + } + + @Override + public DataSource createDataSource() { + return new FolderDataSource(Paths.get(this.folderPath)); + } + + @Override + public String getDisplayName() { + return "Folder: " + this.folderPath; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((this.folderPath == null) ? 0 : this.folderPath.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FolderDataSourceDescriptor other = (FolderDataSourceDescriptor) obj; + if (this.folderPath == null) { + if (other.folderPath != null) { + return false; + } + } + else if (!this.folderPath.equals(other.folderPath)) { + return false; + } + return true; + } + +} diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 96feea1..8ba3a8c 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.util; +import java.util.List; + import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; @@ -287,4 +289,46 @@ public enum RenderMathUtils { return out; } + + public static int testCell(final List planes, final int left, final int right, final int bottom, + final int top, int first) { + if (first == -1) { + first = 0; + } + + for (int i = 0; i < 6; i++) { + final int index = (first + i) % 6; + final Vector4 plane = planes.get(index); + + if ((distance2Plane2(plane, left, bottom) < 0) && (distance2Plane2(plane, left, top) < 0) + && (distance2Plane2(plane, right, top) < 0) && (distance2Plane2(plane, right, bottom) < 0)) { + return index; + } + } + + return -1; + } + + public static int testCell(final Vector4[] planes, final int left, final int right, final int bottom, final int top, + int first) { + if (first == -1) { + first = 0; + } + + for (int i = 0; i < 6; i++) { + final int index = (first + i) % 6; + final Vector4 plane = planes[index]; + + if ((distance2Plane2(plane, left, bottom) < 0) && (distance2Plane2(plane, left, top) < 0) + && (distance2Plane2(plane, right, top) < 0) && (distance2Plane2(plane, right, bottom) < 0)) { + return index; + } + } + + return -1; + } + + public static float distance2Plane2(final Vector4 plane, final int px, final int py) { + return (plane.x * px) + (plane.y * py) + plane.w; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java new file mode 100644 index 0000000..77ffc6d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5; + +public class AudioContext { + private boolean running = false; + public Listener listener = new Listener(); + + public void suspend() { + this.running = false; + } + + public boolean isRunning() { + return this.running; + } + + public void resume() { + this.running = true; + } + + public static class Listener { + private float x; + private float y; + private float z; + private float forwardX; + private float forwardY; + private float forwardZ; + private float upX; + private float upY; + private float upZ; + + public void setPosition(final float x, final float y, final float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setOrientation(final float forwardX, final float forwardY, final float forwardZ, final float upX, + final float upY, final float upZ) { + this.forwardX = forwardX; + this.forwardY = forwardY; + this.forwardZ = forwardZ; + this.upX = upX; + this.upY = upY; + this.upZ = upZ; + + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Bounds.java b/core/src/com/etheller/warsmash/viewer5/Bounds.java new file mode 100644 index 0000000..cc99170 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Bounds.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5; + +public class Bounds { + public int x, y, r; +} diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java new file mode 100644 index 0000000..cbd9fc3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -0,0 +1,326 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.Vector4; + +public class Camera { + private static final Vector3 vectorHeap = new Vector3(); + private static final Vector3 vectorHeap2 = new Vector3(); + private static final Vector3 vectorHeap3 = new Vector3(); + private static final Quaternion quatHeap = new Quaternion(); + private static final Matrix4 matHeap = new Matrix4(); + + public final Rectangle rect; + + private boolean isPerspective; + private float fov; + private float aspect; + + private boolean isOrtho; + private float leftClipPlane; + private float rightClipPlane; + private float bottomClipPlane; + private float topClipPlane; + + private float nearClipPlane; + private float farClipPlane; + + public final Vector3 location; + public final Quaternion rotation; + + public Quaternion inverseRotation; + private final Matrix4 worldMatrix; + private final Matrix4 projectionMatrix; + private final Matrix4 worldProjectionMatrix; + private final Matrix4 inverseWorldMatrix; + private final Matrix4 inverseRotationMatrix; + private final Matrix4 inverseWorldProjectionMatrix; + public final Vector3 directionX; + public final Vector3 directionY; + public final Vector3 directionZ; + private final Vector3[] vectors; + private final Vector3[] billboardedVectors; + + public final Vector4[] planes; + private boolean dirty; + + public Camera() { + // rencered viewport + this.rect = new Rectangle(); + + // perspective values + this.isPerspective = true; + this.fov = 0; + this.aspect = 0; + + // Orthogonal values + this.isOrtho = false; + this.leftClipPlane = 0f; + this.rightClipPlane = 0f; + this.bottomClipPlane = 0f; + this.topClipPlane = 0f; + + // Shared values + this.nearClipPlane = 0f; + this.farClipPlane = 0f; + + // World values + this.location = new Vector3(); + this.rotation = new Quaternion(); + + // Derived values. + this.inverseRotation = new Quaternion(); + this.worldMatrix = new Matrix4(); + this.projectionMatrix = new Matrix4(); + this.worldProjectionMatrix = new Matrix4(); + this.inverseWorldMatrix = new Matrix4(); + this.inverseRotationMatrix = new Matrix4(); + this.inverseWorldProjectionMatrix = new Matrix4(); + this.directionX = new Vector3(); + this.directionY = new Vector3(); + this.directionZ = new Vector3(); + + // First four vectors are the corners of a 2x2 rectangle, the last three vectors + // are the unit axes + this.vectors = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0), + new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1) }; + + // First four vectors are the corners of a 2x2 rectangle billboarded to the + // camera, the last three vectors are the unit axes billboarded + this.billboardedVectors = new Vector3[] { new Vector3(), new Vector3(), new Vector3(), new Vector3(), + new Vector3(), new Vector3(), new Vector3() }; + + // Left, right, top, bottom, near, far + this.planes = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4(), + new Vector4() }; + + this.dirty = true; + } + + public void perspective(final float fov, final float aspect, final float near, final float far) { + this.isPerspective = true; + this.isOrtho = false; + this.fov = fov; + this.aspect = aspect; + this.nearClipPlane = near; + this.farClipPlane = far; + + this.dirty = true; + } + + public void ortho(final float left, final float right, final float bottom, final float top, final float near, + final float far) { + this.isPerspective = false; + this.isOrtho = true; + this.leftClipPlane = left; + this.rightClipPlane = right; + this.bottomClipPlane = bottom; + this.topClipPlane = top; + this.nearClipPlane = near; + this.farClipPlane = far; + } + + public void viewport(final Rectangle viewport) { + this.rect.set(viewport); + + this.aspect = viewport.width / viewport.height; + + this.dirty = true; + } + + public void setLocation(final Vector3 location) { + this.location.set(location); + + this.dirty = true; + } + + public void move(final Vector3 offset) { + this.location.add(offset); + + this.dirty = true; + } + + public void setRotation(final Quaternion rotation) { + this.rotation.set(rotation); + + this.dirty = true; + } + + public void rotate(final Quaternion rotation) { + this.rotation.mul(rotation); + + this.dirty = true; + } + + public void setRotationAngles(final float horizontalAngle, final float verticalAngle) { + this.rotation.idt(); +// this.rotateAngles(horizontalAngle, verticalAngle); + throw new UnsupportedOperationException( + "Ghostwolf called a function that does not exist, so I did not know what to do here"); + } + + public void rotateAround(final Quaternion rotation, final Vector3 point) { + this.rotate(rotation); + + quatHeap.conjugate(); // TODO ????????? + vectorHeap.set(this.location); + vectorHeap.sub(point); + rotation.transform(vectorHeap); + vectorHeap.add(point); + this.location.set(vectorHeap); + } + + public void setRotationAround(final Quaternion rotation, final Vector3 point) { + this.setRotation(rotation); + ; + + final float length = vectorHeap.set(this.location).sub(point).len(); + + quatHeap.conjugate(); // TODO ????????? + vectorHeap.set(RenderMathUtils.VEC3_UNIT_Z); + quatHeap.transform(vectorHeap); + vectorHeap.scl(length); + this.location.set(vectorHeap.add(point)); + } + + public void setRotationAroundAngles(final float horizontalAngle, final float verticalAngle, final Vector3 point) { + quatHeap.idt(); + RenderMathUtils.rotateX(quatHeap, quatHeap, verticalAngle); + RenderMathUtils.rotateY(quatHeap, quatHeap, horizontalAngle); + + this.setRotationAround(quatHeap, point); + } + + public void face(final Vector3 point, final Vector3 worldUp) { + matHeap.setToLookAt(this.location, point, worldUp); + matHeap.getRotation(this.rotation); + + this.dirty = true; + } + + public void moveToAndFace(final Vector3 location, final Vector3 target, final Vector3 worldUp) { + this.location.set(location); + this.face(target, worldUp); + } + + public void reset() { + this.location.set(0, 0, 0); + this.rotation.idt(); + + this.dirty = true; + } + + public void update() { + if (this.dirty) { + this.dirty = true; + + final Vector3 location = this.location; + final Quaternion rotation = this.rotation; + final Quaternion inverseRotation = this.inverseRotation; + final Matrix4 worldMatrix = this.worldMatrix; + final Matrix4 projectionMatrix = this.projectionMatrix; + final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix; + final Vector3[] vectors = this.vectors; + final Vector3[] billboardedVectors = this.billboardedVectors; + + if (this.isPerspective) { + RenderMathUtils.perspective(projectionMatrix, this.fov, this.aspect, this.nearClipPlane, + this.farClipPlane); + } + else { + RenderMathUtils.ortho(projectionMatrix, this.leftClipPlane, this.rightClipPlane, this.bottomClipPlane, + this.topClipPlane, this.nearClipPlane, this.farClipPlane); + } + + rotation.toMatrix(projectionMatrix.val); + worldMatrix.translate(vectorHeap.set(location).scl(-1)); + inverseRotation.set(rotation).conjugate(); + + // World projection matrix + // World space -> NDC space + worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix); + + // Recalculate the camera's frustum planes + RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix); + + // Inverse world matrix + // Camera space -> world space + this.inverseWorldMatrix.set(worldMatrix).inv(); + + this.directionX.set(RenderMathUtils.VEC3_UNIT_X); + inverseRotation.transform(this.directionX); + this.directionY.set(RenderMathUtils.VEC3_UNIT_Y); + inverseRotation.transform(this.directionY); + this.directionZ.set(RenderMathUtils.VEC3_UNIT_Z); + inverseRotation.transform(this.directionZ); + + // Inverse world projection matrix + // NDC space -> World space + this.inverseWorldProjectionMatrix.set(worldProjectionMatrix); + this.inverseWorldProjectionMatrix.inv(); + + for (int i = 0; i < 7; i++) { + billboardedVectors[i].set(vectors[i]); + inverseRotation.transform(billboardedVectors[i]); + } + } + } + + public boolean testSphere(final Vector3 center, final float radius) { + for (final Vector4 plane : this.planes) { + if (RenderMathUtils.distanceToPlane(plane, center) <= -radius) { + return false; + } + } + return true; + } + + public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) { + return out.set(v).prj(this.inverseWorldMatrix); + } + + public Vector3 worldToCamera(final Vector3 out, final Vector3 v) { + return out.set(v).prj(this.worldMatrix); + } + + public Vector2 worldToScreen(final Vector2 out, final Vector3 v) { + final Rectangle viewport = this.rect; + + vectorHeap.set(v); + vectorHeap.prj(this.inverseWorldMatrix); + + out.x = Math.round(((vectorHeap.x + 1) / 2) * viewport.width); + out.y = Math.round(((vectorHeap.y + 1) / 2) * viewport.height); + + return out; + } + + public float[] screenToWorldRay(final float[] out, final Vector2 v) { + final Vector3 a = vectorHeap; + final Vector3 b = vectorHeap2; + final Vector3 c = vectorHeap3; + final float x = v.x; + final float y = v.y; + final Rectangle viewport = this.rect; + + // Intersection on the near-plane + RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport); + + // Intersection on the far-plane + RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport); + + out[0] = a.x; + out[1] = a.y; + out[2] = a.z; + out[3] = b.x; + out[4] = b.y; + out[5] = b.z; + + return out; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/CanvasProvider.java b/core/src/com/etheller/warsmash/viewer5/CanvasProvider.java new file mode 100644 index 0000000..7d0aaab --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/CanvasProvider.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5; + +public interface CanvasProvider { + float getWidth(); + + float getHeight(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/EmittedObject.java b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java new file mode 100644 index 0000000..b39a1e3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.viewer5; + +public abstract class EmittedObject { + abstract void update(float dt); + + public float health; + public Emitter emitter; + public int index; + + protected abstract void bind(int flags); +} diff --git a/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java b/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java new file mode 100644 index 0000000..b9d4086 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java @@ -0,0 +1,40 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +public class EmittedObjectUpdater { + final List objects; + private int alive; + + public EmittedObjectUpdater() { + this.objects = new ArrayList<>(); + this.alive = 0; + } + + public void add(final EmittedObject object) { + this.objects.add(object); + this.alive++; + } + + public void update(final float dt) { + for (int i = 0; i < this.alive; i++) { + final EmittedObject object = this.objects.get(i); + + object.update(dt); + + if (object.health <= 0) { + this.alive -= 1; + + object.emitter.kill(object); + + // Swap between this object and the last living object. + // Decrement the iterator so the swapped object is updated this frame. + if (i != this.alive) { + this.objects.set(i, this.objects.remove(this.alive)); + i -= 1; + } + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Emitter.java b/core/src/com/etheller/warsmash/viewer5/Emitter.java new file mode 100644 index 0000000..6f8a63b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Emitter.java @@ -0,0 +1,75 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public abstract class Emitter { + + private final ModelInstance instance; + private final EmitterObject emitterObject; + private final List objects; + private int alive; + private int currentEmission; + + public Emitter(final ModelInstance instance, final EmitterObject emitterObject) { + this.instance = instance; + this.emitterObject = emitterObject; + this.objects = new ArrayList<>(); + this.alive = 0; + this.currentEmission = 0; + } + + public final EmittedObject emitObject(final int flags) { + if (this.alive == this.objects.size()) { + this.objects.add(this.createObject()); + } + + final EmittedObject object = this.objects.get(this.alive); + object.index = this.alive; + object.bind(flags); + + this.alive += 1; + this.currentEmission -= 1; + + this.instance.scene.emitterObjectUpdater.add(object); + + return object; + } + + public final void update(final float dt) { + this.updateEmission(dt); + + final int currentEmission = this.currentEmission; + if (currentEmission >= 1) { + for (int i = 0; i < currentEmission; i += 1) { + this.emit(); + } + } + } + + public final void kill(final EmittedObject object) { + this.alive -= 1; + + final EmittedObject otherObject = this.objects.get(this.alive); + this.objects.set(object.index, otherObject); + this.objects.set(this.alive, object); + + otherObject.index = object.index; + object.index = -1; + } + + public final void clear() { + for (int i = 0; i < this.alive; i++) { + this.objects.get(i).health = 0; + } + this.currentEmission = 0; + } + + protected abstract void updateEmission(float dt); + + protected abstract void emit(); + + protected abstract EmittedObject createObject(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/Grid.java b/core/src/com/etheller/warsmash/viewer5/Grid.java new file mode 100644 index 0000000..448ad83 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Grid.java @@ -0,0 +1,119 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.math.Vector3; + +public class Grid { + private final int x; + private final int y; + private final int width; + private final int depth; + private final int cellWidth; + private final int cellDepth; + private final int columns; + private final int rows; + final GridCell[] cells; + + public Grid(final int x, int y, final int width, final int depth, final int cellWidth, final int cellDepth) { + final int columns = width / cellWidth; + final int rows = depth / cellDepth; + + this.x = x; + this.y = y; + this.width = width; + this.depth = depth; + this.cellWidth = cellWidth; + this.cellDepth = cellDepth; + this.columns = columns; + this.rows = rows; + this.cells = new GridCell[rows * columns]; + + for (int row = 0; row < rows; row++) { + for (int column = 0; column < columns; column++) { + final int left = x + (columns * cellWidth); + final int right = left + cellWidth; + final int bottom = y = row * cellDepth; + final int top = bottom + cellDepth; + + this.cells[(row * columns) + column] = new GridCell(left, right, bottom, top); + } + } + } + + public void add(final ModelInstance instance) { + final int left = instance.left; + final int right = instance.right + 1; + final int bottom = instance.bottom; + final int top = instance.top + 1; + + if (left != -1) { + for (int y = bottom; y < top; y++) { + for (int x = left; x < right; x++) { + this.cells[(y * this.columns) + x].add(instance); + } + } + } + } + + public void remove(final ModelInstance instance) { + final int left = instance.left; + final int right = instance.right + 1; + final int bottom = instance.bottom; + final int top = instance.top + 1; + + if (left != -1) { + instance.left = -1; + + for (int y = bottom; y < top; y++) { + for (int x = left; x < right; x++) { + this.cells[(y * this.columns) + x].remove(instance); + } + } + } + } + + public void moved(final ModelInstance instance) { + final Bounds bounds = instance.model.bounds; + final float x = (instance.worldLocation.x + bounds.x) - this.x; + final float y = (instance.worldLocation.y + bounds.y) - this.y; + final float r = bounds.r; + final Vector3 s = instance.worldScale; + int left = (int) (Math.floor((x - (r * s.x)) / this.cellWidth)); + int right = (int) (Math.floor((x + (r * s.x)) / this.cellWidth)); + int bottom = (int) (Math.floor((y - (r * s.y)) / this.cellDepth)); + int top = (int) (Math.floor((y + (r * s.y)) / this.cellDepth)); + + if ((right < 0) || (left > (this.columns - 1)) || (top < 0) || (bottom > (this.rows - 1))) { + // The instance is outside of the grid, so remove it. + this.remove(instance); + } + else { + // Clamp the values so they are in the grid. + left = Math.max(left, 0); + right = Math.min(right, this.columns - 1); + bottom = Math.max(bottom, 0); + top = Math.min(top, this.rows - 1); + + // If the values actually changed, update the cells. + if ((left != instance.left) || (right != instance.right) || (bottom != instance.bottom) + || (top != instance.top)) { + /// TODO: This can be optimized by checking if there are shared cells. + /// That can be done in precisely the same way as done a few lines above, i.e. + /// simple rectangle intersection. + this.remove(instance); + + instance.left = left; + instance.right = right; + instance.bottom = bottom; + instance.top = top; + + this.add(instance); + } + } + } + + public void clear() { + for (final GridCell cell : this.cells) { + cell.clear(); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/GridCell.java b/core/src/com/etheller/warsmash/viewer5/GridCell.java new file mode 100644 index 0000000..dc48f23 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/GridCell.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.RenderMathUtils; + +public class GridCell { + public final int left; + public final int right; + public final int bottom; + public final int top; + public int plane; + final List instances; + public final boolean visible; + + public GridCell(final int left, final int right, final int bottom, final int top) { + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = top; + this.plane = -1; + this.instances = new ArrayList(); + this.visible = false; + } + + public void add(final ModelInstance instance) { + this.instances.add(instance); + } + + public void remove(final ModelInstance instance) { + this.instances.remove(instance); + } + + public void clear() { + this.instances.clear(); + } + + public boolean isVisible(final Camera camera) { + this.plane = RenderMathUtils.testCell(camera.planes, this.left, this.right, this.bottom, this.top, this.plane); + + return this.plane == -1; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Model.java b/core/src/com/etheller/warsmash/viewer5/Model.java new file mode 100644 index 0000000..c7096f8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Model.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5; + +import com.etheller.warsmash.viewer5.handlers.Handler; + +public class Model { + public Bounds bounds; + public boolean ok; + public Handler handler; +} diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java new file mode 100644 index 0000000..1113241 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.math.Vector3; + +public class ModelInstance { + public Model model; + public TextureMapper textureMapper; + + public int left = -1; + public int right = -1; + public int bottom = -1; + public int top = -1; + public int plane = -1; + public int depth = 0; + + public Vector3 worldLocation; + public Vector3 worldScale; + public Scene scene; + public boolean rendered; + public int cullFrame; + public int updateFrame; + + public boolean isVisible(final Camera camera) { + // TODO Auto-generated method stub + return false; + } + + public void update(final float dt, final Scene scene) { + // TODO Auto-generated method stub + + } + + public boolean isBatched() { + // TODO Auto-generated method stub + return false; + } + + public void renderOpaque() { + // TODO Auto-generated method stub + + } + + public void renderTranslucent() { + // TODO Auto-generated method stub + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java new file mode 100644 index 0000000..6fbad31 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -0,0 +1,169 @@ +package com.etheller.warsmash.viewer5; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Texture; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.viewer5.gl.WebGL; + +public class ModelViewer { + private final DataSource dataSource; + public final CanvasProvider canvas; + public List resources; + public Map fetchCache; + public int frameTime; + public GL20 gl; + public WebGL webGL; + public List scenes; + private int visibleCells; + private int visibleInstances; + private int updatedParticles; + public int frame; + private final int rectBuffer; + private final boolean enableAudio; + private final Map> textureMappers; + + public ModelViewer(final DataSource dataSource, final CanvasProvider canvas) { + this.dataSource = dataSource; + this.canvas = canvas; + this.resources = new ArrayList<>(); + this.fetchCache = new HashMap<>(); + this.frameTime = 1000 / 60; + this.gl = Gdx.gl; + this.webGL = new WebGL(this.gl); + this.scenes = new ArrayList<>(); + this.visibleCells = 0; + this.visibleInstances = 0; + this.updatedParticles = 0; + + this.frame = 0; + + this.rectBuffer = this.gl.glGenBuffer(); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer); + final ByteBuffer temp = ByteBuffer.allocate(6); + temp.put((byte) 0); + temp.put((byte) 1); + temp.put((byte) 2); + temp.put((byte) 0); + temp.put((byte) 2); + temp.put((byte) 3); + temp.clear(); + this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, temp.capacity(), temp, GL20.GL_STATIC_DRAW); + this.enableAudio = false; + this.textureMappers = new HashMap>(); + } + + public Scene addScene() { + final Scene scene = new Scene(this); + + this.scenes.add(scene); + + return scene; + } + + public boolean removeScene(final Scene scene) { + return this.scenes.remove(scene); + } + + public void clear() { + this.scenes.clear(); + } + + public boolean has(final String key) { + return this.fetchCache.containsKey(key); + } + + public Resource get(final String key) { + return this.fetchCache.get(key); + } + + public void updateAndRender() { + update(); + } + +// public Resource loadGeneric(String path, String dataType, ) + public void update() { + final float dt = this.frameTime * 0.001f; + + this.frame += 1; + + this.visibleCells = 0; + this.visibleInstances = 0; + this.updatedParticles = 0; + + for (final Scene scene : this.scenes) { + scene.update(dt); + + this.visibleCells += scene.visibleCells; + this.visibleInstances += scene.visibleInstances; + this.updatedParticles += scene.updatedParticles; + } + } + + public void startFrame() { + this.gl.glDepthMask(true); + this.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + } + + public void render() { + this.renderOpaque(); + this.renderTranslucent(); + } + + private void renderOpaque() { + for (final Scene scene : this.scenes) { + scene.renderOpaque(); + } + } + + private void renderTranslucent() { + for (final Scene scene : this.scenes) { + scene.renderTranslucent(); + } + } + + public TextureMapper baseTextureMapper(final ModelInstance instance) { + final Model model = instance.model; + List mappers = this.textureMappers.get(model); + if (mappers == null) { + mappers = new ArrayList<>(); + this.textureMappers.put(model, mappers); + } + if (mappers.isEmpty()) { + mappers.add(new TextureMapper(model)); + } + return mappers.get(0); + } + + public TextureMapper changeTextureMapper(final ModelInstance instance, final Object key, final Texture texture) { + final Map map = new HashMap<>(instance.textureMapper.textures); + + if (texture instanceof Texture) { // not null? + map.put(key, texture); + } + else { + map.remove(key); + } + + final Model model = instance.model; + final List mappers = this.textureMappers.get(model); + + for (final TextureMapper mapper : mappers) { + if (mapper.textures.equals(map)) { + return mapper; + } + } + + final TextureMapper mapper = new TextureMapper(model, map); + + mappers.add(mapper); + + return mapper; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Resource.java b/core/src/com/etheller/warsmash/viewer5/Resource.java new file mode 100644 index 0000000..2761957 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Resource.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5; + +public abstract class Resource { + public ModelViewer viewer; + + public Resource(final ModelViewer viewer) { + this.viewer = viewer; + } + + public abstract void bind(int unit); + +} diff --git a/core/src/com/etheller/warsmash/viewer5/ResourceLoader.java b/core/src/com/etheller/warsmash/viewer5/ResourceLoader.java new file mode 100644 index 0000000..ce56cf8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/ResourceLoader.java @@ -0,0 +1,4 @@ +package com.etheller.warsmash.viewer5; + +public interface ResourceLoader { +} diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java new file mode 100644 index 0000000..77caad9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -0,0 +1,290 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.viewer5.handlers.Batch; +import com.etheller.warsmash.viewer5.handlers.BatchDescriptor; + +/** + * A scene. + * + * Every scene has its own list of model instances, and its own camera and + * viewport. + * + * In addition, in Ghostwolf's original code every scene may have its own + * AudioContext if enableAudio() is called. If audo is enabled, the + * AudioContext's listener's location will be updated automatically. Note that + * due to browser policies, this may be done only after user interaction with + * the web page. + * + * In "Warsmash", we are starting from an attempt to replicate Ghostwolf, but + * audio is always on in LibGDX generally. So we will probably simplify or skip + * over those behaviors other than a boolean on/off toggle for audio. + */ +public class Scene { + + private final ModelViewer viewer; + private final Camera camera; + private final Grid grid; + public int visibleCells; + public int visibleInstances; + public int updatedParticles; + private boolean audioEnabled; + private AudioContext audioContext; + + private final List instances; + private final int currentInstance; + private final List batchedInstances; + private final int currentBatchedInstance; + public final EmittedObjectUpdater emitterObjectUpdater; + private final Map batches; + private final Comparator instanceDepthComparator; + + public Scene(final ModelViewer viewer) { + final CanvasProvider canvas = viewer.canvas; + this.viewer = viewer; + this.camera = new Camera(); + this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); + + this.visibleCells = 0; + this.visibleInstances = 0; + this.updatedParticles = 0; + + this.audioEnabled = false; + this.audioContext = null; + + // Use the whole canvas, and standard perspective projection values. + this.camera.viewport(new Rectangle(0, 0, canvas.getWidth(), canvas.getHeight())); + this.camera.perspective((float) (Math.PI / 4), canvas.getWidth() / canvas.getHeight(), 8, 10000); + + this.instances = new ArrayList<>(); + this.currentInstance = 0; + + this.batchedInstances = new ArrayList<>(); + this.currentBatchedInstance = 0; + + this.emitterObjectUpdater = new EmittedObjectUpdater(); + + this.batches = new HashMap<>(); + this.instanceDepthComparator = new InstanceDepthComparator(); + } + + public boolean enableAudio() { + if (this.audioContext == null) { + this.audioContext = new AudioContext(); + } + if (!this.audioContext.isRunning()) { + this.audioContext.resume(); + } + this.audioEnabled = this.audioContext.isRunning(); + return this.audioEnabled; + } + + public void disableAudio() { + if (this.audioContext != null) { + this.audioContext.suspend(); + } + this.audioEnabled = false; + } + + public boolean addInstance(final ModelInstance instance) { + if (instance.scene != this) { + if (instance.scene != null) { + instance.scene.removeInstance(instance); + } + + instance.scene = this; + + // Only allow instances that are actually ok to be added the scene. + if (instance.model.ok) { + this.grid.moved(instance); + + return true; + } + } + + return false; + } + + public boolean removeInstance(final ModelInstance instance) { + if (instance.scene == this) { + this.grid.remove(instance); + + instance.scene = null; + + return true; + } + return false; + } + + public void clear() { + // First remove references to this scene stored in the instances. + for (final GridCell cell : this.grid.cells) { + for (final ModelInstance instance : cell.instances) { + instance.scene = null; + } + } + + // Then remove references to the instances. + this.grid.clear(); + } + + public boolean detach() { + if (this.viewer != null) { + return this.viewer.removeScene(this); + } + return false; + } + + public void addToBatch(final ModelInstance instance) { + final TextureMapper textureMapper = instance.textureMapper; + Batch batch = this.batches.get(textureMapper); + + if (batch == null) { + final Model model = instance.model; + final BatchDescriptor batchDescriptor = model.handler.batchDescriptor; + batch = batchDescriptor.create(this, model, textureMapper); + + this.batches.put(textureMapper, batch); + } + + batch.add(instance); + } + + public void update(final float dt) { + this.camera.update(); + + if (this.audioEnabled) { + final float x = this.camera.location.x; + final float y = this.camera.location.y; + final float z = this.camera.location.z; + final float forwardX = this.camera.directionY.x; + final float forwardY = this.camera.directionY.y; + final float forwardZ = this.camera.directionY.z; + final float upX = this.camera.directionZ.x; + final float upY = this.camera.directionZ.y; + final float upZ = this.camera.directionZ.z; + final AudioContext.Listener listener = this.audioContext.listener; + + listener.setPosition(-x, -y, -z); + listener.setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ); + } + + final int frame = this.viewer.frame; + + int currentInstance = 0; + int currentBatchedInstance = 0; + + this.visibleCells = 0; + this.visibleInstances = 0; + + // Update and collect all of the visible instances. + for (final GridCell cell : this.grid.cells) { + if (cell.isVisible(this.camera)) { + this.visibleCells += 1; + + for (final ModelInstance instance : cell.instances) { + if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { + instance.cullFrame = frame; + + if (instance.updateFrame < frame) { + instance.update(dt, this); + } + + if (instance.isBatched()) { + if (currentBatchedInstance < this.batchedInstances.size()) { + this.batchedInstances.set(currentBatchedInstance++, instance); + } + else { + this.batchedInstances.add(instance); + currentBatchedInstance++; + } + } + else { + if (currentInstance < this.instances.size()) { + this.instances.set(currentInstance++, instance); + } + else { + this.instances.add(instance); + currentInstance++; + } + } + + this.visibleInstances += 1; + } + } + } + } + + for (int i = this.batchedInstances.size() - 1; i >= currentBatchedInstance; i--) { + this.batchedInstances.remove(i); + } + + for (int i = this.instances.size() - 1; i >= currentInstance; i--) { + this.instances.remove(i); + } + Collections.sort(this.instances, this.instanceDepthComparator); + + this.emitterObjectUpdater.update(dt); + this.updatedParticles = this.emitterObjectUpdater.objects.size(); + } + + public void renderOpaque() { + final Rectangle viewport = this.camera.rect; + this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); + + // Clear all of the batches. + for (final Batch batch : this.batches.values()) { + batch.clear(); + } + + // Add all of the batched instances to batches. + for (final ModelInstance instance : this.batchedInstances) { + this.addToBatch(instance); + } + + // Render all of the batches. + for (final Batch batch : this.batches.values()) { + batch.render(); + } + + // Render all of the opaque things of non-batched instances. + for (final ModelInstance instance : this.instances) { + instance.renderOpaque(); + } + } + + /** + * Renders all translucent things in this scene. Automatically applies the + * camera's viewport. + */ + public void renderTranslucent() { + final Rectangle viewport = this.camera.rect; + + this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); + + for (final ModelInstance instance : this.instances) { + instance.renderTranslucent(); + } + + } + + public void clearEmitterObjects() { + for (final EmittedObject object : this.emitterObjectUpdater.objects) { + object.health = 0; + } + } + + private static final class InstanceDepthComparator implements Comparator { + @Override + public int compare(final ModelInstance o1, final ModelInstance o2) { + return o2.depth - o1.depth; + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/TextureMapper.java b/core/src/com/etheller/warsmash/viewer5/TextureMapper.java new file mode 100644 index 0000000..82915fd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/TextureMapper.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5; + +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.graphics.Texture; + +public class TextureMapper { + public final Model model; + public final Map textures; + + public TextureMapper(final Model model) { + this.model = model; + this.textures = new HashMap<>(); + } + + public TextureMapper(final Model model, final Map textures) { + this.model = model; + this.textures = new HashMap<>(textures); + } + + public Texture get(final Object key) { + return this.textures.get(key); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java new file mode 100644 index 0000000..51117e5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.viewer5.deprecated; + +import com.etheller.warsmash.viewer5.gl.WebGL; + +public class ShaderProgram { + + public boolean ok; + public int attribsCount; + public int webglResource; + + public ShaderProgram(final WebGL webGL, final ShaderUnitDeprecated vertexShader, final ShaderUnitDeprecated fragmentShader) { + // TODO Auto-generated constructor stub + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java new file mode 100644 index 0000000..75897cd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.deprecated; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; + +import com.badlogic.gdx.graphics.GL20; + +public class ShaderUnitDeprecated { + + public boolean ok; + private final int webglResource; + private final String src; + private final int shaderType; + + public ShaderUnitDeprecated(final GL20 gl, final String src, final int type) { + final int id = gl.glCreateShader(type); + this.ok = false; + this.webglResource = id; + this.src = src; + this.shaderType = type; + + gl.glShaderSource(id, src); + gl.glCompileShader(id); + + final IntBuffer success = ByteBuffer.allocateDirect(8).order(ByteOrder.nativeOrder()).asIntBuffer(); + gl.glGetShaderiv(id, GL20.GL_COMPILE_STATUS, success); + throw new UnsupportedOperationException("Not yet implemented, probably using library instead"); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java new file mode 100644 index 0000000..e9f081b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java @@ -0,0 +1,139 @@ +package com.etheller.warsmash.viewer5.gl; + +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.deprecated.ShaderUnitDeprecated; + +/** + * This needs a rename. Just a ripoff of ghostwolf's wrapper utility class, it's + * a utility, not a webgl + */ +public class WebGL { + public GL20 gl; + public Map shaderUnits; + public Map shaderPrograms; + public ShaderProgram currentShaderProgram; + public String floatPrecision; + public final Texture emptyTexture; + + public WebGL(final GL20 gl) { + gl.glDepthFunc(GL20.GL_LEQUAL); + gl.glEnable(GL20.GL_DEPTH_TEST); + + // TODO here ghostwolf throws exceptions for unsupported versions of opengl + + this.gl = gl; + + this.shaderUnits = new HashMap<>(); + + this.shaderPrograms = new HashMap<>(); + + this.currentShaderProgram = null; + this.floatPrecision = "precision mediump float;\n"; + + final Pixmap imageData = new Pixmap(2, 2, Pixmap.Format.RGBA8888); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + imageData.drawPixel(i, j, 0x000000FF); + } + } + this.emptyTexture = new Texture(imageData); + } + + public ShaderUnitDeprecated createShaderUnit(final String src, final int type) { + final int hash = stringHash(src); // TODO: why on earth are we doing this, what about hash collisions? + if (!this.shaderUnits.containsKey(hash)) { + this.shaderUnits.put(hash, new ShaderUnitDeprecated(this.gl, src, type)); + } + return this.shaderUnits.get(hash); + } + + public ShaderProgram createShaderProgram(final String vertexSrc, final String fragmentSrc) { + final Map shaderPrograms = this.shaderPrograms; + + final int hash = stringHash(vertexSrc + fragmentSrc); + if (!shaderPrograms.containsKey(hash)) { + shaderPrograms.put(hash, new ShaderProgram(vertexSrc, fragmentSrc)); + } + + final ShaderProgram shaderProgram = shaderPrograms.get(hash); + + if (shaderProgram.isCompiled()) { + return shaderProgram; + } + return null; + } + + public void enableVertexAttribs(final int start, final int end) { + final GL20 gl = this.gl; + + for (int i = start; i < end; i++) { + gl.glEnableVertexAttribArray(i); + } + } + + public void disableVertexAttribs(final int start, final int end) { + final GL20 gl = this.gl; + + for (int i = start; i < end; i++) { + gl.glDisableVertexAttribArray(i); + } + } + + public void useShaderProgram(final ShaderProgram shaderProgram) { + final ShaderProgram currentShaderProgram = this.currentShaderProgram; + + if ((shaderProgram != null) && shaderProgram.isCompiled() && (shaderProgram != currentShaderProgram)) { + int oldAttribs = 0; + final int newAttribs = shaderProgram.getAttributes().length; + + if (currentShaderProgram != null) { + oldAttribs = currentShaderProgram.getAttributes().length; + } + + shaderProgram.begin(); + + if (newAttribs > oldAttribs) { + this.enableVertexAttribs(oldAttribs, newAttribs); + } + else if (newAttribs < oldAttribs) { + this.disableVertexAttribs(newAttribs, oldAttribs); + } + + this.currentShaderProgram = shaderProgram; + } + } + + public void bindTexture(final Texture texture, final int unit) { + final GL20 gl = this.gl; + + gl.glActiveTexture(GL20.GL_TEXTURE0 + unit); + + if (texture != null /* && texture.ok */) { + texture.bind(); + } + else { + this.emptyTexture.bind(); + } + } + + public void setTextureMode(final int wrapS, final int wrapT, final int magFilter, final int minFilter) { + final GL20 gl = this.gl; + + // TODO make sure we dont assign this parameter doubly if we're already using + // libgdx texture, which does do some wrapS and wrapT stuff already + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, wrapS); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, wrapT); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, magFilter); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, minFilter); + } + + private int stringHash(final String src) { + return src.hashCode(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java b/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java new file mode 100644 index 0000000..c107087 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.viewer5.ModelInstance; + +public class Batch { + + public void add(final ModelInstance instance) { + // TODO Auto-generated method stub + + } + + public void clear() { + // TODO Auto-generated method stub + + } + + public void render() { + // TODO Auto-generated method stub + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java b/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java new file mode 100644 index 0000000..0ec39b5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.TextureMapper; + +public interface BatchDescriptor { + Batch create(Scene scene, Model model, TextureMapper textureMapper); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java new file mode 100644 index 0000000..d2cc087 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers; + +public class EmitterObject { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java b/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java new file mode 100644 index 0000000..f77aaba --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers; + +public class Handler { + public BatchDescriptor batchDescriptor; +}