diff --git a/asset-tests/app/build.gradle b/asset-tests/app/build.gradle index 66e41c1d..940deb62 100644 --- a/asset-tests/app/build.gradle +++ b/asset-tests/app/build.gradle @@ -42,8 +42,7 @@ android { applicationVariants.all { variant -> variant.outputs.all { output -> - output.outputFileName = output.outputFile.parent + - rootProject.name + ".apk" + output.outputFileName = rootProject.name + ".apk" } } } diff --git a/framework-tests/app/build.gradle b/framework-tests/app/build.gradle index 616545b0..4d5ed4b7 100644 --- a/framework-tests/app/build.gradle +++ b/framework-tests/app/build.gradle @@ -49,8 +49,7 @@ android { applicationVariants.all { variant -> variant.outputs.all { output -> - output.outputFileName = output.outputFile.parent + - rootProject.name + ".apk" + output.outputFileName = rootProject.name + ".apk" } } diff --git a/framework-tests/app/src/androidTest/java/org/gearvrf/tester/RenderShaderTests.java b/framework-tests/app/src/androidTest/java/org/gearvrf/tester/RenderShaderTests.java new file mode 100644 index 00000000..05fc315f --- /dev/null +++ b/framework-tests/app/src/androidTest/java/org/gearvrf/tester/RenderShaderTests.java @@ -0,0 +1,509 @@ +package org.gearvrf.tester; + +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import net.jodah.concurrentunit.Waiter; + +import org.gearvrf.GVRAndroidResource; +import org.gearvrf.GVRContext; +import org.gearvrf.GVRDirectLight; +import org.gearvrf.GVRMaterial; +import org.gearvrf.GVRScene; +import org.gearvrf.GVRSceneObject; +import org.gearvrf.GVRShaderId; +import org.gearvrf.GVRTexture; +import org.gearvrf.unittestutils.GVRSceneMaker; +import org.gearvrf.unittestutils.GVRTestUtils; +import org.gearvrf.unittestutils.GVRTestableActivity; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidJUnit4.class) +public class RenderShaderTests +{ + private GVRTestUtils gvrTestUtils; + private Waiter mWaiter; + private boolean mDoCompare = false; + + public RenderShaderTests() { + super(); + } + + @Rule + public ActivityTestRule ActivityRule = new + ActivityTestRule(GVRTestableActivity.class); + + + @After + public void tearDown() + { + GVRScene scene = gvrTestUtils.getMainScene(); + if (scene != null) + { + scene.clear(); + } + } + + @Before + public void setUp() throws TimeoutException { + gvrTestUtils = new GVRTestUtils(ActivityRule.getActivity()); + mWaiter = new Waiter(); + gvrTestUtils.waitForOnInit(); + } + + private List createMeshFormats() { + final String type = "type: polygon"; + final String vertices = "vertices: [-0.5, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, 0.5, 0.0, 0.5, -0.5, 0.0]"; + final String normals = "normals: [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0]"; + final String texcoords = "texcoords: [[0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0]]"; + final String triangles = "triangles: [ 0, 1, 2, 1, 3, 2 ]"; + + final String weights = "bone_weights: [0.0, 1.0, 0.0, 0.0, 0.75, 0.25, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.2, 0.2, 0.2, 0.4]"; + final String indices = "bone_indices: [ 0, 0, 0, 0, 0, 1, 0, 0, 1, 3, 0, 0, 0, 1, 3, 2 ]"; + + List meshFormats = new ArrayList(); + + // Mesh with positions, normals, texcoords + meshFormats.add("{" + type + ", " + vertices + ", " + triangles + ", " + normals + ", " + + texcoords + "}"); + // Mesh with positions, texcoords + meshFormats.add("{" + type + ", " + vertices + ", " + triangles + ", " + texcoords + "}"); + // Mesh with positions, normals + meshFormats.add("{" + type + ", " + vertices + ", " + triangles + ", " + normals + "}"); + // Mesh with positions, normals, texcoords, bone indices, bone weights + meshFormats.add("{" + type + ", " + vertices + ", " + triangles + ", " + normals + ", " + + texcoords + ", " + indices + ", " + weights + "}"); + // Mesh with positions, texcoords, bone indices, bone weights + meshFormats.add("{" + type + ", " + vertices + ", " + triangles + ", " + texcoords + ", " + + indices + ", " + weights + "}"); + // Mesh with positions, bone indices, bone weights + meshFormats.add("{" + type + ", " + vertices + ", " + triangles + ", " + + indices + ", " + weights + "}"); + + return meshFormats; + } + + private String createLightType(String type, float r, float g, float b, float x) { + String ambientIntensity = "ambientintensity: {r:" + 0.3f * r + ", g:" + 0.3f * g + + ", b:" + 0.3f * b + ", a: 1.0}"; + String diffuseIntensity = "diffuseintensity: {r:" + r + ", g:" + g + ", b:" + b + + ", a: 1.0}"; + String specularIntensity = "specularintensity: {r:" + r + ", g:" + g + ", b:" + b + + ", a: 1.0}"; + String position = "position: {x:" + x + ",y: 0.0, z: 0.0}"; + String rotation = "rotation: {w: 1.0, x: 0.0, y: 0.0, z: 0.0}"; + String spotCone = "innerconeangle: 20.0f, outerconeangle: 30.0f"; + + return "{type:" + type + "," + rotation + ", " + position + ", " + spotCone + "," + + ambientIntensity + ", " + diffuseIntensity + ", " + specularIntensity + "}"; + } + + private List createLightingTypes() throws JSONException { + List lightingTypes = new ArrayList(); + + // No lighting + lightingTypes.add(""); + + // one directional light + lightingTypes.add("[" + + createLightType("directional", 1.0f, 0.3f, 0.3f, 0.0f) + "]"); + // one spot light + lightingTypes.add("[" + + createLightType("spot", 1.0f, 0.3f, 0.3f, 0.0f) + "]"); + // one point light + lightingTypes.add("[" + + createLightType("point", 1.0f, 0.3f, 0.3f, 0.0f) + "]"); + // two directional lights + lightingTypes.add("[" + + createLightType("directional", 1.0f, 0.3f, 0.3f, -1.0f) + ", " + + createLightType("directional", 1.0f, 0.3f, 0.3f, 1.0f) + "]"); + // two spot lights + lightingTypes.add("[" + + createLightType("spot", 1.0f, 0.3f, 0.3f, -1.0f) + "," + + createLightType("spot", 1.0f, 0.3f, 0.3f, 1.0f) + "]"); + // two point lights + lightingTypes.add("[" + + createLightType("point", 1.0f, 0.3f, 0.3f, -1.0f) + "," + + createLightType("point", 1.0f, 0.3f, 0.3f, 1.0f) + "]"); + // one directional, one spot + lightingTypes.add("[" + + createLightType("directional", 1.0f, 0.3f, 0.3f, -1.0f) + ", " + + createLightType("spot", 1.0f, 0.3f, 0.3f, 1.0f) + "]"); + // one directional, one point + lightingTypes.add("[" + + createLightType("directional", 1.0f, 0.3f, 0.3f, -1.0f) + ", " + + createLightType("point", 1.0f, 0.3f, 0.3f, 1.0f) + "]"); + // one directional, one point, one spot + lightingTypes.add("[" + + createLightType("directional", 1.0f, 0.3f, 0.3f, -1.0f) + ", " + + createLightType("spot", 1.0f, 0.3f, 0.3f, 0.0f) + ", " + + createLightType("point", 1.0f, 0.3f, 0.3f, 1.0f) + "]"); + + return lightingTypes; + } + + private String createMaterialFormat(GVRShaderId shaderId, int textureResourceID) { + String materialFormat = ""; + + String type = shaderId == GVRMaterial.GVRShaderType.Phong.ID ? + "shader: phong," : "shader: texture,"; + + if (textureResourceID == -1) { + final String color = "color: {r: 0.0, g: 1.0, b: 0.0, a: 1.0}"; + materialFormat = "{ " + type + color + "}"; + } else { + final String textureFormat = "textures: [{" + + "id: default, " + + "type: bitmap," + + "resource_id:" + textureResourceID + "}]"; + + materialFormat = "{" + type + textureFormat + "}"; + } + + return materialFormat; + } + + private String createMaterialFormat(GVRShaderId shaderId) { + return createMaterialFormat(shaderId, -1); + } + + private List createMaterialFormats() { + List materials = new ArrayList(); + + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Phong.ID)); + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Texture.ID)); + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Phong.ID, R.drawable.checker)); + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Texture.ID, R.drawable.checker)); + + return materials; + } + + private List createMaterialWithTextureFormats() { + List materials = new ArrayList(); + int[] textures = new int[] {R.drawable.checker, R.drawable.rock_normal, R.raw.jpg_opaque}; + for (int j = 0; j < textures.length; j++) { // Textures + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Phong.ID, textures[j])); + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Texture.ID, textures[j])); + } + return materials; + } + + /** + * Test mesh formats and shader combinations. + * + * @throws TimeoutException + */ + @Test + public void meshFormatsWithShaderCombinationsTest() throws TimeoutException { + // Mesh with positions, normals, texcoords, bone indices, bone weights + JSONObject jsonScene = null; + List meshFormats = null; + List materials = null; + List lightingTypes = null; + String screenshotName = null; + try { + materials = createMaterialFormats(); + meshFormats = createMeshFormats(); + lightingTypes = createLightingTypes(); + + for (int i = 0; i < meshFormats.size(); i++) { // Mesh formats + for (int j = 0; j < lightingTypes.size(); j++) { // Lighting + for (int k = 0; k < materials.size(); k+= 2) { // Materials/Shaders + jsonScene = new JSONObject("{\"id\": \"scene" + i + "\"}"); + JSONArray objects = new JSONArray(); + JSONObject objectPhong = new JSONObject(); + objectPhong.put("geometry", new JSONObject(meshFormats.get(i))); + objectPhong.put("material", new JSONObject(materials.get(k))); + objectPhong.put("position", new JSONObject("{x: -1.0f, z: -2.0}")); + objectPhong.put("scale", new JSONObject("{x: 2.0, y: 2.0, z: 2.0}")); + + JSONObject objectTexture = new JSONObject(); + objectTexture.put("geometry", new JSONObject(meshFormats.get(i))); + objectTexture.put("material", new JSONObject(materials.get(k + 1))); + objectTexture.put("position", new JSONObject("{x: 1.0f, z: -2.0}")); + objectTexture.put("scale", new JSONObject("{x: 2.0, y: 2.0, z: 2.0}")); + + objects.put(objectPhong); + objects.put(objectTexture); + + jsonScene.put("objects", objects); + if (j > 0) { // No lighting when j == 0 + jsonScene.put("lights", new JSONArray(lightingTypes.get(j))); + } + + GVRSceneMaker.makeScene(gvrTestUtils.getGvrContext(), gvrTestUtils.getMainScene(), + jsonScene); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMesh" + i + "Lighting" + j; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + gvrTestUtils.getMainScene().clear(); + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Test + public void meshStartWithoutTextureTest() throws TimeoutException { + String screenshotName = null; + JSONObject jsonScene = null; + GVRContext ctx = gvrTestUtils.getGvrContext(); + GVRScene scene = gvrTestUtils.getMainScene(); + int[] textures = new int[] {R.drawable.checker, R.drawable.rock_normal, R.raw.jpg_opaque}; + + try { + List materials = new ArrayList(); + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Phong.ID)); + materials.add(createMaterialFormat(GVRMaterial.GVRShaderType.Texture.ID)); + + for (int i = 0; i < materials.size(); i++) { // Materials without textures + for (int j = 0; j < textures.length; j++) { // Textures + jsonScene = new JSONObject("{\"id\": \"scene" + j + "\"}"); + String objName = "cubeObj" + j; + JSONObject object = new JSONObject(String.format("{name: %s}", objName)); + object.put("geometry", new JSONObject("{type: cube}")); + object.put("material", new JSONObject(materials.get(i))); + object.put("position", new JSONObject("{z: -2.0}")); + object.put("rotation", new JSONObject("{w: 0.5f, x: 0.2f, y: 1.0f, z:0.0f}")); + + jsonScene.put("objects", new JSONArray().put(object)); + GVRSceneMaker.makeScene(ctx, scene, jsonScene); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshWithoutTexture" + i + "." + j; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + GVRSceneObject obj = scene.getSceneObjectByName(objName); + GVRTexture text = ctx.getAssetLoader().loadTexture(new + GVRAndroidResource(ctx, textures[j])); + obj.getRenderData().getMaterial().setMainTexture(text); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshAddTexture" + i + "." + j; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + + scene.clear(); + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Test + public void meshStartWithtTextureTest() throws TimeoutException { + String screenshotName = null; + JSONObject jsonScene = null; + GVRContext ctx = gvrTestUtils.getGvrContext(); + GVRScene scene = gvrTestUtils.getMainScene(); + List materials = null; + + try { + materials = createMaterialWithTextureFormats(); + + for (int i = 0; i < materials.size(); i++) { // Materials with textures + jsonScene = new JSONObject("{\"id\": \"scene" + i + "\"}"); + String objName = "cubeObj" + i; + JSONObject object = new JSONObject(String.format("{name: %s}", objName)); + object.put("geometry", new JSONObject("{type: cube}")); + object.put("material", new JSONObject(materials.get(i))); + object.put("position", new JSONObject("{z: -2.0}")); + object.put("rotation", new JSONObject("{w: 0.5f, x: 0.2f, y: 1.0f, z:0.0f}")); + + jsonScene.put("objects", new JSONArray().put(object)); + GVRSceneMaker.makeScene(ctx, scene, jsonScene); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshWithTexture" + i; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + GVRSceneObject obj = scene.getSceneObjectByName(objName); + obj.getRenderData().getMaterial().setMainTexture(null); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshRemoveTexture" + i; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + scene.clear(); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Test + public void meshSwitchTexcoordSet() throws TimeoutException { + String screenshotName = null; + JSONObject jsonScene = null; + GVRContext ctx = gvrTestUtils.getGvrContext(); + GVRScene scene = gvrTestUtils.getMainScene(); + + try { + + String material = createMaterialFormat(GVRMaterial.GVRShaderType.Phong.ID, + R.drawable.colortex); + + final String type = "type: quad"; + final String descriptor = "descriptor: \"float3 a_position float2 a_texcoord float3 " + + "a_normal float2 a_texcoord1\""; + String geometry = "{" + type + ", " + descriptor + "}"; + + jsonScene = new JSONObject("{id: scene}"); + String objName = "cubeObj"; + JSONObject object = new JSONObject(String.format("{name: %s}", objName)); + object.put("geometry", new JSONObject(geometry)); + object.put("material", new JSONObject(material)); + object.put("position", new JSONObject("{z: -2.0}")); + + jsonScene.put("objects", new JSONArray().put(object)); + GVRSceneMaker.makeScene(ctx, scene, jsonScene); + GVRSceneObject obj = scene.getSceneObjectByName(objName); + obj.getRenderData().getMesh().setTexCoords( + new float[]{0.5F, 0.5F, 1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F}, 1); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshWithFirstSetTextCoord"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + // change text coordinates + obj.getRenderData().getMaterial().setTexCoord("diffuseTexture", + "a_texcoord1", + "diffuse_coord"); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshWithSecondSetTextCoord"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + // test failed, textcoord is not changing + + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Test + public void meshWithoutLightingTest() throws TimeoutException { + String screenshotName = null; + + try { + JSONObject jsonScene = new JSONObject("{id: scene}"); + + JSONObject object = new JSONObject(); + object.put("geometry", new JSONObject("{type: cube}")); + object.put("material", new JSONObject(createMaterialFormat( + GVRMaterial.GVRShaderType.Phong.ID, R.drawable.checker))); + object.put("position", new JSONObject("{x: -1.0, z: -2.0}")); + + jsonScene.put("objects", new JSONArray().put(object)); + GVRSceneMaker.makeScene(gvrTestUtils.getGvrContext(), gvrTestUtils.getMainScene(), jsonScene); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshWithoutLighting"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + + // add lighting on the scene + GVRSceneObject lightNode = new GVRSceneObject(gvrTestUtils.getGvrContext()); + GVRDirectLight light = new GVRDirectLight(gvrTestUtils.getGvrContext(), lightNode); + lightNode.getTransform().setPosition(0, 0, 0); + lightNode.getTransform().setRotation(1, 0, 0, 0); + light.setAmbientIntensity(0.3f * 1, 0.3f * 0.3f, 0.3f * 0.3f, 1); + light.setDiffuseIntensity(1, 0.3f, 0.3f, 1); + light.setSpecularIntensity(1, 0.3f, 0.3f, 1); + + gvrTestUtils.getMainScene().addSceneObject(lightNode); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshAddLighting"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Test + public void meshWithLightingTest() throws TimeoutException { + String screenshotName = null; + + try { + JSONObject jsonScene = new JSONObject("{id: scene}"); + + JSONObject object = new JSONObject(); + object.put("geometry", new JSONObject("{type: cube}")); + object.put("material", new JSONObject(createMaterialFormat( + GVRMaterial.GVRShaderType.Phong.ID, R.drawable.checker))); + object.put("position", new JSONObject("{x: -1.0, z: -2.0}")); + + jsonScene.put("objects", new JSONArray().put(object)); + jsonScene.put("lights", new JSONArray("[" + + createLightType("directional", 1.0f, 0.3f, 0.3f, 0.0f) + "]")); + GVRSceneMaker.makeScene(gvrTestUtils.getGvrContext(), gvrTestUtils.getMainScene(), jsonScene); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshWithLighting"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + // remove lighting from the scene + GVRSceneObject lightNode = gvrTestUtils.getMainScene().getSceneObjectByName("lightNode"); + gvrTestUtils.getMainScene().removeSceneObject(lightNode); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshRemoveLighting"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Test + public void meshWithLightingDisableRenderDataLightTest() throws TimeoutException { + String screenshotName = null; + + try { + JSONObject jsonScene = new JSONObject("{id: scene}"); + + JSONObject object = new JSONObject(); + object.put("geometry", new JSONObject("{type: cube}")); + object.put("material", new JSONObject(createMaterialFormat( + GVRMaterial.GVRShaderType.Phong.ID, R.drawable.checker))); + object.put("position", new JSONObject("{x: -1.0, z: -2.0}")); + + jsonScene.put("objects", new JSONArray().put(object)); + jsonScene.put("lights", new JSONArray("[" + + createLightType("directional", 1.0f, 0.3f, 0.3f, 0.0f) + "]")); + GVRSceneMaker.makeScene(gvrTestUtils.getGvrContext(), gvrTestUtils.getMainScene(), jsonScene); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshWithLightAndRenderDataEnabled"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + // disable light in object render data + GVRSceneObject cubeSceneObj = gvrTestUtils.getMainScene().getSceneObjectByName("cubeSceneObj"); + cubeSceneObj.getRenderData().disableLight(); + + gvrTestUtils.waitForSceneRendering(); + screenshotName = "testMeshDisableRenderDataLight"; + gvrTestUtils.screenShot(getClass().getSimpleName(), screenshotName, mWaiter, mDoCompare); + + } catch (JSONException e) { + e.printStackTrace(); + } + } +} diff --git a/gvr-unittestutils/build.gradle b/gvr-unittestutils/build.gradle index 817a4169..4761d2f8 100644 --- a/gvr-unittestutils/build.gradle +++ b/gvr-unittestutils/build.gradle @@ -31,7 +31,10 @@ android { } } +project.ext.jomlVersion = "1.9.1-SNAPSHOT" + dependencies { + compile "org.joml:joml-android:${jomlVersion}" compile fileTree(dir: 'libs', include: ['*.jar']) if (findProject(':framework')) { diff --git a/gvr-unittestutils/src/main/java/org/gearvrf/unittestutils/GVRSceneMaker.java b/gvr-unittestutils/src/main/java/org/gearvrf/unittestutils/GVRSceneMaker.java new file mode 100644 index 00000000..f145e928 --- /dev/null +++ b/gvr-unittestutils/src/main/java/org/gearvrf/unittestutils/GVRSceneMaker.java @@ -0,0 +1,719 @@ +package org.gearvrf.unittestutils; + +import android.opengl.GLES30; +import android.util.ArrayMap; + +import org.gearvrf.GVRAndroidResource; +import org.gearvrf.GVRContext; +import org.gearvrf.GVRDirectLight; +import org.gearvrf.GVRMaterial; +import org.gearvrf.GVRMesh; +import org.gearvrf.GVRPointLight; +import org.gearvrf.GVRRenderData; +import org.gearvrf.GVRScene; +import org.gearvrf.GVRSceneObject; +import org.gearvrf.GVRSpotLight; +import org.gearvrf.GVRTexture; +import org.gearvrf.GVRTransform; +import org.gearvrf.scene_objects.GVRCubeSceneObject; +import org.gearvrf.scene_objects.GVRSphereSceneObject; +import org.gearvrf.utility.Log; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/* + +scene: +{ + id: "scene name" + bgcolor: {red: [0.0-1.0], green: [0.0-1.0], blue: [0.0-1.0], alpha: [0.0-1.0]} + lights: [...] + objects: [...] +} + +light: +{ + type: ("spot" | "directional" | "point") + castshadow: ("true" | "false") + position: {x: 0, y: 0, z: 0} + rotation: {w: 0, x: 0, y: 0, z: 0} + ambientintensity: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + diffuseintensity: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + specularintensity: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + innerconeangle: [0.0-9.0]+ + outerconeangle: [0.0-9.0]+ +} + +object: +{ + name: "object name" + geometry: {...} + material: {..} + position: {x: [0.0-9.0]+, y: [0.0-9.0]+, z: [0.0-9.0]+} + rotation: {x: [0.0-9.0]+, y: [0.0-9.0]+, z: [0.0-9.0]+} + scale: {x: [0.0-9.0]+, y: [0.0-9.0]+, z: [0.0-9.0]+} + renderconfig: {...} +} + +geometry: +{ + type: ("quad" | "cylinder" | "sphere" | "cube" | "polygon") + width: [0.0-9.0]+ + height: [0.0-9.0]+ + depth: [0.0-9.0]+ + radius: [0.0-9.0]+ + vertices: [[0.0-9.0]+, ...] + normals: [[0.0-9.0]+, ...] + texcoords: [[0.0-9.0]+, ...] + triangles: [[0-9]+, ...] + bone_weights: [[0.0-9.0]+, ...] + bone_indices: [[0-9]+, ...] +} + +material: +{ + shader: ("phong" | "texture" | "cube") + color: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + textures:[...] +} + +texture: +{ + id: "texture id" + name: ("u_texture" | "diffuseTexture") + type: ("bitmap", "cube", "compressed") + resourceid: [0-9]+ +} + +renderconfig: +{ + drawmode: (GL_TRIANGLES | GL_TRIANGLE_STRIP | GL_TRIANGLE_FAN + | GL_LINES | GL_LINE_STRIP | GL_LINE_LOOP) +} + */ + +/** + * Parse a JSON object to build the equivalent GVRScene. + */ +public class GVRSceneMaker { + private static class RGBAColor { + public final float r; + public final float g; + public final float b; + public final float a; + + public RGBAColor(float r, float g, float b, float a){ + this. r = r; + this. g = g; + this. b = b; + this. a = a; + } + } + + private static float[] jsonToFloatArray(JSONArray jsonArray) throws JSONException { + float[] array = new float[jsonArray.length()]; + + for (int i = 0; i < jsonArray.length(); i++) { + array[i] = (float) jsonArray.getDouble(i); + } + + return array; + } + + private static int[] jsonToIntArray(JSONArray jsonArray) throws JSONException { + int[] array = new int[jsonArray.length()]; + + for (int i = 0; i < jsonArray.length(); i++) { + array[i] = jsonArray.getInt(i); + } + + return array; + } + + private static GVRTexture createBitmapTexture(GVRContext gvrContext, JSONObject jsonTexture) throws JSONException { + + int resourceId = jsonTexture.optInt("resource_id", -1); + if (resourceId == -1) { + return null; + } + + return gvrContext.getAssetLoader().loadTexture( + new GVRAndroidResource(gvrContext, resourceId)); + } + + /* + { + r: 1, g: 1, b: 1, a: 0 + } + */ + private static RGBAColor getColorCoordinates(JSONObject jsonObject) throws + JSONException { + + float cordR = (float) jsonObject.optDouble("r", 0.0f); + float cordG = (float) jsonObject.optDouble("g", 0.0f); + float cordB = (float) jsonObject.optDouble("b", 0.0f); + float cordA = (float) jsonObject.optDouble("a", 1.0f); + + RGBAColor coordinates = new RGBAColor(cordR, cordG, cordB, cordA); + return coordinates; + } + + /* + { + id: "texture id" + name: ("u_texture" | "diffuseTexture") + type: ("bitmap", "cube", "compressed") + resourceid: [0-9]+ + } + */ + private static GVRTexture createTexture(GVRContext gvrContext, JSONObject jsonTexture) throws JSONException { + GVRTexture texture = null; + + String type = jsonTexture.optString("type"); + if (type.equals("compressed")) { + } else if (type.equals("cube")) { + } else { + // type.equals("bitmap") || type.isEmpty() + texture = createBitmapTexture(gvrContext, jsonTexture); + } + + return texture; + } + + /* + { + shader: ("phong" | "texture" | "cube") + color: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + textures:[...] + } + */ + private static GVRMaterial createMaterial(GVRContext gvrContext, + ArrayMap textures, + JSONObject jsonObject) throws JSONException { + GVRMaterial material; + String shader_type = jsonObject.optString("shader", "texture"); + + if (shader_type.equals("phong")) { + material = new GVRMaterial(gvrContext, GVRMaterial.GVRShaderType.Phong.ID); + } else if (shader_type.equals("cube")) { + material = new GVRMaterial(gvrContext, GVRMaterial.GVRShaderType.Cubemap.ID); + } else { + material = new GVRMaterial(gvrContext, GVRMaterial.GVRShaderType.Texture.ID); + } + + JSONArray jsonArray = jsonObject.optJSONArray("textures"); + if (jsonArray != null) { + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject jsonTexture = jsonArray.getJSONObject(i); + + String sharedId = jsonTexture.optString("shared"); + GVRTexture texture = sharedId.isEmpty() ? createTexture(gvrContext, jsonTexture) : + textures.get(sharedId); + + String texture_name = jsonTexture.optString("name"); + if (texture_name.isEmpty()) { + if (material.getShaderType() == GVRMaterial.GVRShaderType.Phong.ID) { + material.setTexture("diffuseTexture", texture); + } else { + material.setMainTexture(texture); + } + } else { + material.setTexture(texture_name, texture); + } + } + } + + JSONObject jsonColor = jsonObject.optJSONObject("color"); + if (jsonColor != null) { + RGBAColor color = getColorCoordinates(jsonColor); + if (material.getShaderType() == GVRMaterial.GVRShaderType.Texture.ID) { + material.setColor(color.r, color.g, color.b); + } else { + material.setDiffuseColor(color.r, color.g, color.b, color.a); + } + } + + return material; + } + + private static void setScale(GVRTransform transform, JSONObject jsonScale) + throws JSONException { + + float x = (float) jsonScale.optDouble("x", 1.0f); + float y = (float) jsonScale.optDouble("y", 1.0f); + float z = (float) jsonScale.optDouble("z", 1.0f); + + transform.setScale(x, y, z); + } + + private static void setPosition(GVRTransform transform, JSONObject jsonPosition) + throws JSONException { + + float x = (float) jsonPosition.optDouble("x", 0.0f); + float y = (float) jsonPosition.optDouble("y", 0.0f); + float z = (float) jsonPosition.optDouble("z", 0.0f); + + transform.setPosition(x, y, z); + } + + private static void setRotation(GVRTransform transform, JSONObject jsonRotation) + throws JSONException { + + float x = (float) jsonRotation.optDouble("x", 0.0f); + float y = (float) jsonRotation.optDouble("y", 0.0f); + float z = (float) jsonRotation.optDouble("z", 0.0f); + float w = (float) jsonRotation.optDouble("w", 0.0f); + + transform.setRotation(w, x, y, z); + } + + /* + { + position: {x: 0, y: 0, z: 0} + scale: {x: 0, y: 0, z: 0} + rotation: {w: 0, x: 0, y: 0, z: 0} + } + */ + private static void setTransform(GVRTransform transform, JSONObject jsonObject) throws JSONException { + + JSONObject jsonPosition = jsonObject.optJSONObject("position"); + if (jsonPosition != null) { + setPosition(transform, jsonPosition); + } + + JSONObject jsonRotation = jsonObject.optJSONObject("rotation"); + if (jsonRotation != null) { + setRotation(transform, jsonRotation); + } + + JSONObject jsonScale = jsonObject.optJSONObject("scale"); + if (jsonScale != null) { + setScale(transform, jsonScale); + } + } + + /* + { + drawmode: (GL_TRIANGLES | GL_TRIANGLE_STRIP | GL_TRIANGLE_FAN + | GL_LINES | GL_LINE_STRIP | GL_LINE_LOOP) + } + */ + private static void setRenderConfig(GVRRenderData renderData, JSONObject jsonConfig) + throws JSONException { + renderData.setDrawMode(jsonConfig.optInt("drawmode", GLES30.GL_TRIANGLES)); + } + + private static void setPointLightIntensity(GVRPointLight light, JSONObject jsonLight) + throws JSONException { + + JSONObject jsonAmbientIntensity = jsonLight.optJSONObject("ambientintensity"); + if (jsonAmbientIntensity != null) { + RGBAColor ambientCoord = getColorCoordinates(jsonAmbientIntensity); + light.setAmbientIntensity(ambientCoord.r, ambientCoord.g, ambientCoord.b, + ambientCoord.a); + } + + JSONObject jsonDiffuseIntensity = jsonLight.optJSONObject("diffuseintensity"); + if (jsonDiffuseIntensity != null) { + RGBAColor diffuseCoord = getColorCoordinates(jsonDiffuseIntensity); + light.setDiffuseIntensity(diffuseCoord.r, diffuseCoord.g, diffuseCoord.b, + diffuseCoord.a); + } + + JSONObject jsonSpecularIntensity = jsonLight.optJSONObject("specularintensity"); + if (jsonSpecularIntensity != null) { + RGBAColor specularCoord = getColorCoordinates(jsonSpecularIntensity); + light.setSpecularIntensity(specularCoord.r, specularCoord.g, specularCoord.b, + specularCoord.a); + } + } + + private static void setDirectLightIntensity(GVRDirectLight light, JSONObject jsonLight) + throws JSONException { + + JSONObject jsonAmbientIntensity = jsonLight.optJSONObject("ambientintensity"); + if (jsonAmbientIntensity != null) { + RGBAColor ambientCoord = getColorCoordinates(jsonAmbientIntensity); + light.setAmbientIntensity(ambientCoord.r, ambientCoord.g, ambientCoord.b, + ambientCoord.a); + } + + JSONObject jsonDiffuseIntensity = jsonLight.optJSONObject("diffuseintensity"); + if (jsonDiffuseIntensity != null) { + RGBAColor diffuseCoord = getColorCoordinates(jsonDiffuseIntensity); + light.setDiffuseIntensity(diffuseCoord.r, diffuseCoord.g, diffuseCoord.b, + diffuseCoord.a); + } + + JSONObject jsonSpecularIntensity = jsonLight.optJSONObject("specularintensity"); + if (jsonSpecularIntensity != null) { + RGBAColor specularCoord = getColorCoordinates(jsonSpecularIntensity); + light.setSpecularIntensity(specularCoord.r, specularCoord.g, specularCoord.b, + specularCoord.a); + } + } + + private static void setLightConeAngle(GVRSpotLight light, JSONObject jsonLight) + throws JSONException { + + float innerAngle = (float) jsonLight.optDouble("innerconeangle"); + if (!Double.isNaN(innerAngle)) { + light.setInnerConeAngle(innerAngle); + } + + float outAngle = (float) jsonLight.optDouble("outerconeangle"); + if (!Double.isNaN(outAngle)) { + light.setOuterConeAngle(outAngle); + } + } + + private static GVRSceneObject createSpotLight(GVRContext gvrContext, JSONObject jsonLight) + throws JSONException { + + GVRSceneObject lightObj = new GVRSceneObject(gvrContext); + GVRSpotLight spotLight = new GVRSpotLight(gvrContext); + setPointLightIntensity(spotLight, jsonLight); + setLightConeAngle(spotLight, jsonLight); + lightObj.attachLight(spotLight); + + return lightObj; + } + + private static GVRSceneObject createDirectLight(GVRContext gvrContext, JSONObject jsonLight) + throws JSONException { + + GVRSceneObject lightObj = new GVRSceneObject(gvrContext); + lightObj.setName("lightNode"); + GVRDirectLight directLight = new GVRDirectLight(gvrContext); + setDirectLightIntensity(directLight, jsonLight); + lightObj.attachLight(directLight); + + return lightObj; + } + + private static GVRSceneObject createPointLight(GVRContext gvrContext, JSONObject jsonLight) + throws JSONException { + + GVRSceneObject lightObj = new GVRSceneObject(gvrContext); + GVRPointLight pointLight = new GVRPointLight(gvrContext); + setPointLightIntensity(pointLight, jsonLight); + lightObj.attachLight(pointLight); + + return lightObj; + } + + /* + { + type: ("spot" | "directional" | "point") + castshadow: ("true" | "false") + position: {x: 0, y: 0, z: 0} + rotation: {w: 0, x: 0, y: 0, z: 0} + ambientintensity: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + diffuseintensity: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + specularintensity: {r: [0.0-1.0], g: [0.0-1.0], b: [0.0-1.0], a: [0.0-1.0]} + innerconeangle: [0.0-9.0]+ + outerconeangle: [0.0-9.0]+ + } + */ + private static GVRSceneObject createLight(GVRContext gvrContext, JSONObject jsonLight) throws + JSONException { + + GVRSceneObject light = null; + String type = jsonLight.optString("type"); + + if (type.equals("spot")) { + light = createSpotLight(gvrContext, jsonLight); + } else if (type.equals("directional")) { + light = createDirectLight(gvrContext, jsonLight); + } else if (type.equals("point")) { + light = createPointLight(gvrContext, jsonLight); + } + + if (light != null) { + light.getLight().setCastShadow(jsonLight.optBoolean("castshadow")); + setTransform(light.getTransform(), jsonLight); + } + + return light; + } + + /* + { + vertices: [0, ... n] + normals: [0, ... n] + texcoords: [[0, ... n], [0, ... n]] + triangles: [0, ... n] + } + */ + private static GVRMesh createPolygonMesh(GVRContext gvrContext, JSONObject jsonObject) + throws JSONException { + String descriptor = "float3 a_position"; + + if (jsonObject.has( "texcoords")) { + descriptor += " float2 a_texcoord"; + } + + if (jsonObject.has("normals")) { + descriptor += " float3 a_normal"; + } + + GVRMesh mesh = new GVRMesh(gvrContext, descriptor); + + if (jsonObject.has("vertices")) { + mesh.setVertices(jsonToFloatArray(jsonObject.optJSONArray("vertices"))); + } + + if (jsonObject.has("normals")) { + mesh.setNormals(jsonToFloatArray(jsonObject.optJSONArray("normals"))); + } + + if (jsonObject.has("triangles")) { + mesh.setTriangles(jsonToIntArray(jsonObject.optJSONArray("triangles"))); + } + + if (jsonObject.has("texcoords")) { + JSONArray jsonCorrds = jsonObject.optJSONArray("texcoords"); + if (jsonCorrds != null) { + for (int i = 0; i < jsonCorrds.length(); i++) { + mesh.setTexCoords(jsonToFloatArray(jsonCorrds.optJSONArray(i)), i); + } + } + } + + return mesh; + } + + private static GVRSceneObject createQuad(GVRContext gvrContext, JSONObject jsonObject) + throws JSONException { + float width = 1.0f; + float height = 1.0f; + String descriptor = "float3 a_position float2 a_texcoord float3 a_normal"; + + if (jsonObject != null) { + width = (float) jsonObject.optDouble("width", width); + height = (float) jsonObject.optDouble("height", height); + descriptor = jsonObject.optString("descriptor", descriptor); + } + + return new GVRSceneObject(gvrContext, + GVRMesh.createQuad(gvrContext, descriptor, width, height)); + } + + private static GVRSceneObject createPolygon(GVRContext gvrContext, JSONObject jsonObject) + throws JSONException { + return new GVRSceneObject(gvrContext, createPolygonMesh(gvrContext, jsonObject)); + } + + private static GVRSceneObject createCube(GVRContext gvrContext, JSONObject jsonObject) + throws JSONException { + String descriptor = "float3 a_position float2 a_texcoord float3 a_normal"; + + float width = (float) jsonObject.optDouble("width", 1.0f); + float height = (float) jsonObject.optDouble("height", 1.0f); + float depth = (float) jsonObject.optDouble("depth", 1.0f); + boolean facing_out = jsonObject.optBoolean("facing_out", true); + descriptor = jsonObject.optString("descriptor", descriptor); + + GVRMesh mesh = GVRCubeSceneObject.createCube(gvrContext, descriptor, facing_out, + new org.joml.Vector3f(width, height, depth)); + + return new GVRSceneObject(gvrContext, mesh); + } + + private static GVRSceneObject createCylinder(GVRContext gvrContext, JSONObject jsonObject) + throws JSONException { + return null; + } + + private static GVRSceneObject createSphere(GVRContext gvrContext, JSONObject jsonObject) + throws JSONException { + float radius = (float) jsonObject.optDouble("radius", 1.0f); + boolean facing_out = jsonObject.optBoolean("facing_out", true); + + return new GVRSphereSceneObject(gvrContext, facing_out, radius); + } + + /* + { + type: ("quad" | "cylinder" | "sphere" | "cube" | "polygon") + width: [0.0-9.0]+ + height: [0.0-9.0]+ + depth: [0.0-9.0]+ + radius: [0.0-9.0]+ + vertices: [[0.0-9.0]+, ...] + normals: [[0.0-9.0]+, ...] + texcoords: [[0.0-9.0]+, ...] + triangles: [[0-9]+, ...] + bone_weights: [[0.0-9.0]+, ...] + bone_indices: [[0-9]+, ...] + } + */ + private static GVRSceneObject createGeometry(GVRContext gvrContext, JSONObject jsonObject) + throws JSONException { + GVRSceneObject sceneObject = null; + + String type = jsonObject.optString("type"); + + if (type.equals("polygon")) { + sceneObject = createPolygon(gvrContext, jsonObject); + } else if (type.equals("cube")) { + sceneObject = createCube(gvrContext, jsonObject); + sceneObject.setName("cubeSceneObj"); + } else if (type.equals("cylinder")) { + sceneObject = createCylinder(gvrContext, jsonObject); + } else if (type.equals("sphere")) { + sceneObject = createSphere(gvrContext, jsonObject); + } else { + sceneObject = createQuad(gvrContext, jsonObject); + } + + return sceneObject; + } + + /* + { + name: "object name" + geometry: {...} + material: {..} + position: {x: [0.0-9.0]+, y: [0.0-9.0]+, z: [0.0-9.0]+} + rotation: {x: [0.0-9.0]+, y: [0.0-9.0]+, z: [0.0-9.0]+} + scale: {x: [0.0-9.0]+, y: [0.0-9.0]+, z: [0.0-9.0]+} + } + */ + private static GVRSceneObject createChildObject(GVRContext gvrContext, + ArrayMap textures, + ArrayMap materials, + JSONObject jsonObject) throws JSONException { + + JSONObject jsonGeometry = jsonObject.optJSONObject("geometry"); + GVRSceneObject child = (jsonGeometry != null) ? createGeometry(gvrContext, jsonGeometry) : + createQuad(gvrContext, null); + + String objectName = jsonObject.optString("name"); + if (!objectName.isEmpty()){ + child.setName(objectName); + } + + setTransform(child.getTransform(), jsonObject); + + JSONObject jsonMaterial = jsonObject.optJSONObject("material"); + if (jsonMaterial != null) { + String sharedId = jsonMaterial.optString("shared"); + GVRMaterial material = sharedId.isEmpty() ? + createMaterial(gvrContext, textures, jsonMaterial) : materials.get(sharedId); + + child.getRenderData().setMaterial(material); + } + + JSONObject jsonRenderConf = jsonObject.optJSONObject("renderconfig"); + + if (jsonRenderConf != null) { + setRenderConfig(child.getRenderData(), jsonRenderConf); + } + + return child; + } + + private static void addChildrenObjects(GVRContext gvrContext, GVRSceneObject root, + ArrayMap textures, + ArrayMap materials, + JSONArray jsonChildren) throws JSONException { + for (int i = 0; i < jsonChildren.length(); i++) { + GVRSceneObject child = createChildObject(gvrContext, + textures, materials, jsonChildren.getJSONObject(i)); + root.addChildObject(child); + } + } + + private static void addChildrenLights(GVRContext gvrContext, GVRSceneObject root, JSONArray + jsonChildren) throws JSONException { + for (int i = 0; i < jsonChildren.length(); i++) { + GVRSceneObject child = createLight(gvrContext, jsonChildren.getJSONObject(i)); + root.addChildObject(child); + } + } + + private static void createShareables(GVRContext gvrContext, + ArrayMap textures, + ArrayMap materials, + JSONObject jsonObject) throws JSONException { + // Shared textures + JSONArray texArray = jsonObject.optJSONArray("textures"); + if (texArray != null) { + for (int i = 0; i < texArray.length(); i++) { + String id = texArray.optJSONObject(i).optString("id"); + if (!id.isEmpty()) { + textures.put(id, createTexture(gvrContext, texArray.getJSONObject(i))); + } + } + } + + // Shared materials + JSONArray matArray = jsonObject.optJSONArray("materials"); + if (matArray != null) { + for (int i = 0; i < matArray.length(); i++) { + String id = matArray.optJSONObject(i).optString("id"); + if (!id.isEmpty()) { + materials.put(id, + createMaterial(gvrContext, textures, matArray.getJSONObject(i))); + } + } + } + } + + public static void makeScene(GVRContext gvrContext, GVRScene scene, JSONObject jsonScene, + JSONObject jsonShareables) throws JSONException { + ArrayMap textures = new ArrayMap<>(); + ArrayMap materials = new ArrayMap<>(); + + Log.d("SceneMaker", jsonScene.toString()); + + if (jsonShareables != null) { + createShareables(gvrContext, textures, materials, jsonShareables); + } + + createShareables(gvrContext, textures, materials, jsonScene); + + JSONObject jsonBgColor = jsonScene.optJSONObject("bgcolor"); + if (jsonBgColor != null) { + RGBAColor bgcolorCoord = getColorCoordinates(jsonBgColor); + scene.setBackgroundColor(bgcolorCoord.r, bgcolorCoord.g, bgcolorCoord.b, + bgcolorCoord.a); + } + + JSONArray jsonChildrenLights = jsonScene.optJSONArray("lights"); + if (jsonChildrenLights != null) { + addChildrenLights(gvrContext, scene.getRoot(), jsonChildrenLights); + } + + JSONArray jsonChildrenObjects = jsonScene.optJSONArray("objects"); + if (jsonChildrenObjects != null) { + addChildrenObjects(gvrContext, scene.getRoot(), textures, materials, + jsonChildrenObjects); + } + } + + public static void makeScene(GVRContext gvrContext, GVRScene scene, + String jsonScene) throws JSONException { + makeScene(gvrContext, scene, new JSONObject(jsonScene), null); + } + + public static void makeScene(GVRContext gvrContext, GVRScene scene, + String jsonScene, String jsonShareables) throws JSONException { + makeScene(gvrContext, scene, new JSONObject(jsonScene), new JSONObject(jsonShareables)); + } + + /* + { + id: "scene name" + bgcolor: {red: [0.0-1.0], green: [0.0-1.0], blue: [0.0-1.0], alpha: [0.0-1.0]} + lights: [...] + objects: [...] + } + */ + public static void makeScene(GVRContext gvrContext, GVRScene scene, + JSONObject jsonScene) throws JSONException { + makeScene(gvrContext, scene, jsonScene, null); + } +}