/* * Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.model.shapes; import com.google.common.collect.Lists; import com.google.gson.*; import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; import gnu.trove.procedure.TIntProcedure; import org.terasology.math.Side; import org.terasology.model.structures.AABB; import javax.vecmath.Vector2f; import javax.vecmath.Vector3f; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.util.List; import java.util.Locale; /** * @author Immortius */ public class JsonBlockShapePersister { private Gson gson; private String currentShapeName; public JsonBlockShapePersister() { gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(BlockShape.class, new BlockShapeHandler()) .registerTypeAdapter(BlockMeshPart.class, new BlockMeshPartHandler()) .registerTypeAdapter(Vector3f.class, new Vector3fHandler()) .registerTypeAdapter(Vector2f.class, new Vector2fHandler()) .create(); } public BlockShape load(String title, InputStream stream) throws IOException { currentShapeName = title; return gson.fromJson(new InputStreamReader(stream), BlockShape.class); } private class BlockShapeHandler implements JsonDeserializer<BlockShape> { @Override public BlockShape deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { BlockShape shape = new BlockShape(currentShapeName); JsonObject shapeObj = json.getAsJsonObject(); if (shapeObj.has("center")) { shape.setCenterMesh((BlockMeshPart) context.deserialize(shapeObj.get("center"), BlockMeshPart.class)); } for (Side side : Side.values()) { if (shapeObj.has(side.toString().toLowerCase(Locale.ENGLISH))) { JsonObject sideMeshObj = shapeObj.getAsJsonObject(side.toString().toLowerCase(Locale.ENGLISH)); shape.setSideMesh(side, (BlockMeshPart) context.deserialize(sideMeshObj, BlockMeshPart.class)); if (sideMeshObj.has("fullSide")) { shape.setBlockingSide(side, sideMeshObj.get("fullSide").getAsBoolean()); } } } List<AABB> colliders = Lists.newArrayList(); if (shapeObj.has("colliders") && shapeObj.get("colliders").isJsonArray()) { JsonArray colliderArray = shapeObj.get("colliders").getAsJsonArray(); for (JsonElement item : colliderArray) { if (item.isJsonObject()) { JsonObject collider = item.getAsJsonObject(); Vector3f pos = context.deserialize(collider.get("position"), Vector3f.class); Vector3f extent = context.deserialize(collider.get("extents"), Vector3f.class); if (pos == null) throw new JsonParseException("Collider missing position"); if (extent == null) throw new JsonParseException("Collider missing extents"); colliders.add(new AABB(pos, extent)); } } } else { colliders.add(new AABB(new Vector3f(), new Vector3f(0.5f, 0.5f, 0.5f))); } shape.setColliders(colliders); return shape; } } private class BlockMeshPartHandler implements JsonDeserializer<BlockMeshPart> { @Override public BlockMeshPart deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { final JsonObject meshObj = json.getAsJsonObject(); final Vector3f[] vertices = context.deserialize(meshObj.get("vertices"), Vector3f[].class); final Vector3f[] normals = context.deserialize(meshObj.get("normals"), Vector3f[].class); final Vector2f[] texCoords = context.deserialize(meshObj.get("texcoords"), Vector2f[].class); if (vertices == null) throw new JsonParseException("Vertices missing"); if (normals == null) throw new JsonParseException("Normals missing"); if (texCoords == null) throw new JsonParseException("Texcoords missing"); if (!meshObj.has("faces")) throw new JsonParseException("Faces missing"); if (vertices.length != normals.length || vertices.length != texCoords.length) { throw new JsonParseException("vertices, normals and texcoords must have the same length"); } // Normalise the normals for safety for (Vector3f norm : normals) { norm.normalize(); } int[][] faces = context.deserialize(meshObj.get("faces"), int[][].class); // Convert faces to indices via triangle fan TIntList indices = new TIntArrayList(); for (int[] face : faces) { for (int tri = 0; tri < face.length - 2; tri++) { indices.add(face[0]); indices.add(face[tri + 1]); indices.add(face[tri + 2]); } } // Check indices in bounds indices.forEach(new TIntProcedure() { @Override public boolean execute(int value) { if (value < 0 || value >= vertices.length) throw new JsonParseException("Face value out of range: " + value + ", max vertex is " + (vertices.length - 1)); return true; } }); return new BlockMeshPart(vertices, normals, texCoords, indices.toArray()); } } private class Vector3fHandler implements JsonDeserializer<Vector3f> { @Override public Vector3f deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonArray jsonArray = json.getAsJsonArray(); return new Vector3f(jsonArray.get(0).getAsFloat(), jsonArray.get(1).getAsFloat(), jsonArray.get(2).getAsFloat()); } } private class Vector2fHandler implements JsonDeserializer<Vector2f> { @Override public Vector2f deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonArray jsonArray = json.getAsJsonArray(); return new Vector2f(jsonArray.get(0).getAsFloat(), jsonArray.get(1).getAsFloat()); } } }