Java tutorial
/* * Copyright 2013 MovingBlocks * * 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.world.block.loader; import com.google.common.base.Charsets; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.asset.AssetManager; import org.terasology.asset.AssetType; import org.terasology.asset.AssetUri; import org.terasology.asset.Assets; import org.terasology.registry.CoreRegistry; import org.terasology.math.Rotation; import org.terasology.math.Side; import org.terasology.persistence.ModuleContext; import org.terasology.utilities.gson.CaseInsensitiveEnumTypeAdapterFactory; import org.terasology.utilities.gson.JsonMergeUtil; import org.terasology.utilities.gson.Vector3fTypeAdapter; import org.terasology.utilities.gson.Vector4fTypeAdapter; import org.terasology.world.block.Block; import org.terasology.world.block.BlockAppearance; import org.terasology.world.block.BlockPart; import org.terasology.world.block.BlockUri; import org.terasology.world.block.family.BlockBuilderHelper; import org.terasology.world.block.family.BlockFamily; import org.terasology.world.block.family.BlockFamilyFactory; import org.terasology.world.block.family.BlockFamilyFactoryRegistry; import org.terasology.world.block.family.HorizontalBlockFamily; import org.terasology.world.block.family.SymmetricFamily; import org.terasology.world.block.shapes.BlockMeshPart; import org.terasology.world.block.shapes.BlockShape; import javax.vecmath.Vector2f; import javax.vecmath.Vector3f; import javax.vecmath.Vector4f; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.util.EnumMap; import java.util.List; import java.util.Map; /** * Block Loader processes all the block assets, creating a set of Block Families and Freeform block uris. * * @author Immortius */ public class BlockLoader implements BlockBuilderHelper { public static final String AUTO_BLOCK_URL_FRAGMENT = "/auto/"; private static final Logger logger = LoggerFactory.getLogger(BlockLoader.class); private AssetManager assetManager; private JsonParser parser; private Gson gson; private BlockShape cubeShape; private BlockShape loweredShape; private BlockShape trimmedLoweredShape; private final WorldAtlas atlas; private BlockFamilyFactoryRegistry blockFamilyFactoryRegistry; public BlockLoader(AssetManager assetManager, BlockFamilyFactoryRegistry blockFamilyFactoryRegistry, WorldAtlas atlas) { this.atlas = atlas; this.assetManager = assetManager; parser = new JsonParser(); gson = new GsonBuilder().registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory()) .registerTypeAdapter(BlockDefinition.Tiles.class, new BlockTilesDefinitionHandler()) .registerTypeAdapter(BlockDefinition.ColorSources.class, new BlockColorSourceDefinitionHandler()) .registerTypeAdapter(BlockDefinition.ColorOffsets.class, new BlockColorOffsetDefinitionHandler()) .registerTypeAdapter(Vector3f.class, new Vector3fTypeAdapter()) .registerTypeAdapter(Vector4f.class, new Vector4fTypeAdapter()).create(); cubeShape = (BlockShape) Assets.get(AssetType.SHAPE, "engine:cube"); loweredShape = (BlockShape) Assets.get(AssetType.SHAPE, "engine:loweredCube"); trimmedLoweredShape = (BlockShape) Assets.get(AssetType.SHAPE, "engine:trimmedLoweredCube"); this.blockFamilyFactoryRegistry = blockFamilyFactoryRegistry; } public LoadBlockDefinitionResults loadBlockDefinitions() { logger.info("Loading Blocks..."); LoadBlockDefinitionResults result = new LoadBlockDefinitionResults(); for (AssetUri blockDefUri : Assets.list(AssetType.BLOCK_DEFINITION)) { try (ModuleContext.ContextSpan ignored = ModuleContext.setContext(blockDefUri.getModuleName())) { JsonElement rawJson = readJson(blockDefUri); if (rawJson != null) { JsonObject blockDefJson = rawJson.getAsJsonObject(); // Don't process templates if (blockDefJson.has("template") && blockDefJson.get("template").getAsBoolean()) { continue; } logger.debug("Loading {}", blockDefUri); BlockDefinition blockDef = createBlockDefinition(inheritData(blockDefUri, blockDefJson)); if (isShapelessBlockFamily(blockDef)) { result.shapelessDefinitions.add(new FreeformFamily( new BlockUri(blockDefUri.getModuleName(), blockDefUri.getAssetName()), blockDef.categories)); } else { if (blockDef.liquid) { blockDef.rotation = null; blockDef.shapes.clear(); blockDef.shape = trimmedLoweredShape.getURI().toSimpleString(); } if (blockDef.shapes.isEmpty()) { BlockFamilyFactory familyFactory = blockFamilyFactoryRegistry .getBlockFamilyFactory(blockDef.rotation); result.families.add( familyFactory.createBlockFamily(this, blockDefUri, blockDef, blockDefJson)); } else { result.families.addAll(processMultiBlockFamily(blockDefUri, blockDef)); } } } } catch (Exception e) { logger.error("Error loading block {}", blockDefUri, e); } } result.shapelessDefinitions.addAll(loadAutoBlocks()); return result; } @Override public BlockDefinition getBlockDefinitionForSection(JsonObject json, String sectionName) { if (json.has(sectionName) && json.get(sectionName).isJsonObject()) { JsonObject sectionJson = json.getAsJsonObject(sectionName); json.remove(sectionName); JsonMergeUtil.mergeOnto(json, sectionJson); return createBlockDefinition(sectionJson); } return null; } public BlockFamily loadWithShape(BlockUri uri) { try (ModuleContext.ContextSpan ignored = ModuleContext.setContext(uri.getModuleName())) { BlockShape shape = cubeShape; if (uri.hasShape()) { AssetUri shapeUri = uri.getShapeUri(); if (!shapeUri.isValid()) { return null; } shape = (BlockShape) Assets.get(shapeUri); if (shape == null) { return null; } } AssetUri blockDefUri = new AssetUri(AssetType.BLOCK_DEFINITION, uri.getModuleName(), uri.getFamilyName()); BlockDefinition def; if (assetManager.getAssetURLs(blockDefUri).isEmpty()) { // An auto-block def = new BlockDefinition(); } else { def = createBlockDefinition(inheritData(blockDefUri, readJson(blockDefUri).getAsJsonObject())); } def.shape = (shape.getURI().toSimpleString()); if (shape.isCollisionYawSymmetric()) { Block block = constructSingleBlock(blockDefUri, def); if (block.getDisplayName().isEmpty()) { block.setDisplayName(shape.getDisplayName()); } else if (!shape.getDisplayName().isEmpty()) { block.setDisplayName(block.getDisplayName() + " " + shape.getDisplayName()); } return new SymmetricFamily(uri, block, def.categories); } else { Map<Side, Block> blockMap = Maps.newEnumMap(Side.class); constructHorizontalBlocks(blockDefUri, def, blockMap); for (Block block : blockMap.values()) { if (block.getDisplayName().isEmpty()) { block.setDisplayName(shape.getDisplayName()); } else if (!shape.getDisplayName().isEmpty()) { block.setDisplayName(block.getDisplayName() + " " + shape.getDisplayName()); } } return new HorizontalBlockFamily(uri, blockMap, def.categories); } } catch (Exception e) { logger.error("Error loading block shape {}", uri, e); } return null; } private List<FreeformFamily> loadAutoBlocks() { logger.debug("Loading Auto Blocks..."); List<FreeformFamily> result = Lists.newArrayList(); for (AssetUri blockTileUri : Assets.list(AssetType.BLOCK_TILE)) { if (assetManager.getAssetURLs(blockTileUri).get(0).getPath().contains(AUTO_BLOCK_URL_FRAGMENT)) { logger.debug("Loading auto block {}", blockTileUri); BlockUri uri = new BlockUri(blockTileUri.getModuleName(), blockTileUri.getAssetName()); result.add(new FreeformFamily(uri)); } } return result; } private boolean isShapelessBlockFamily(BlockDefinition blockDef) { return blockDef.shapes.isEmpty() && blockDef.shape.isEmpty() && blockDef.rotation == null && !blockDef.liquid && blockDef.tiles == null; } private JsonObject inheritData(AssetUri rootAssetUri, JsonObject blockDefJson) { JsonObject parentObj = blockDefJson; while (parentObj.has("basedOn")) { AssetUri parentUri = Assets.resolveAssetUri(AssetType.BLOCK_DEFINITION, parentObj.get("basedOn").getAsString()); if (rootAssetUri.equals(parentUri)) { logger.error("Circular inheritance detected in {}", rootAssetUri); break; } else if (!parentUri.isValid()) { logger.error("{} based on invalid uri: {}", rootAssetUri, parentObj.get("basedOn").getAsString()); break; } JsonObject parent = readJson(parentUri).getAsJsonObject(); JsonMergeUtil.mergeOnto(parent, blockDefJson); parentObj = parent; } return blockDefJson; } private List<BlockFamily> processMultiBlockFamily(AssetUri blockDefUri, BlockDefinition blockDef) { List<BlockFamily> result = Lists.newArrayList(); for (String shapeString : blockDef.shapes) { BlockShape shape = (BlockShape) Assets.get(AssetType.SHAPE, shapeString); if (shape != null) { BlockUri familyUri; if (shape.equals(cubeShape)) { familyUri = new BlockUri(blockDefUri.getModuleName(), blockDefUri.getAssetName()); } else { familyUri = new BlockUri(blockDefUri.getModuleName(), blockDefUri.getAssetName(), shape.getURI().getModuleName(), shape.getURI().getAssetName()); } blockDef.shape = shapeString; if (shape.isCollisionYawSymmetric()) { Block block = constructSingleBlock(blockDefUri, blockDef); result.add(new SymmetricFamily(familyUri, block, blockDef.categories)); } else { Map<Side, Block> blockMap = Maps.newEnumMap(Side.class); constructHorizontalBlocks(blockDefUri, blockDef, blockMap); result.add(new HorizontalBlockFamily(familyUri, blockMap, blockDef.categories)); } } } return result; } @Override public Block constructSimpleBlock(AssetUri blockDefUri, BlockDefinition blockDefinition) { return constructSingleBlock(blockDefUri, blockDefinition); } private Block constructSingleBlock(AssetUri blockDefUri, BlockDefinition blockDef) { Map<BlockPart, AssetUri> tileUris = prepareTiles(blockDef, blockDefUri); Map<BlockPart, Block.ColorSource> colorSourceMap = prepareColorSources(blockDef); Map<BlockPart, Vector4f> colorOffsetsMap = prepareColorOffsets(blockDef); BlockShape shape = getShape(blockDef); Block block = createRawBlock(blockDef, properCase(blockDefUri.getAssetName())); block.setPrimaryAppearance(createAppearance(shape, tileUris, Rotation.none())); setBlockFullSides(block, shape, Rotation.none()); block.setCollision(shape.getCollisionOffset(Rotation.none()), shape.getCollisionShape(Rotation.none())); for (BlockPart part : BlockPart.values()) { block.setColorSource(part, colorSourceMap.get(part)); block.setColorOffset(part, colorOffsetsMap.get(part)); } // Lowered mesh for liquids if (block.isLiquid()) { applyLoweredShape(block, loweredShape, tileUris); } return block; } @Override public Map<Side, Block> constructHorizontalRotatedBlocks(AssetUri blockDefUri, BlockDefinition blockDefinition) { Map<Side, Block> result = Maps.newHashMap(); constructHorizontalBlocks(blockDefUri, blockDefinition, result); return result; } @Override public Block constructTransformedBlock(AssetUri blockDefUri, BlockDefinition blockDef, Rotation rotation) { Map<BlockPart, AssetUri> tileUris = prepareTiles(blockDef, blockDefUri); Map<BlockPart, Block.ColorSource> colorSourceMap = prepareColorSources(blockDef); Map<BlockPart, Vector4f> colorOffsetsMap = prepareColorOffsets(blockDef); BlockShape shape = getShape(blockDef); Block block = createRawBlock(blockDef, properCase(blockDefUri.getAssetName())); block.setDirection(rotation.rotate(Side.FRONT)); block.setPrimaryAppearance(createAppearance(shape, tileUris, rotation)); setBlockFullSides(block, shape, rotation); block.setCollision(shape.getCollisionOffset(rotation), shape.getCollisionShape(rotation)); for (BlockPart part : BlockPart.values()) { block.setColorSource(part, colorSourceMap.get(part)); block.setColorOffset(part, colorOffsetsMap.get(part)); } return block; } private void constructHorizontalBlocks(AssetUri blockDefUri, BlockDefinition blockDef, Map<Side, Block> blockMap) { for (Rotation rot : Rotation.horizontalRotations()) { blockMap.put(rot.rotate(Side.FRONT), constructTransformedBlock(blockDefUri, blockDef, rot)); } } private Map<BlockPart, Vector4f> prepareColorOffsets(BlockDefinition blockDef) { Map<BlockPart, Vector4f> result = Maps.newEnumMap(BlockPart.class); for (BlockPart part : BlockPart.values()) { result.put(part, blockDef.colorOffset); } if (blockDef.colorOffsets != null) { for (BlockPart part : BlockPart.values()) { if (blockDef.colorOffsets.map.get(part) != null) { result.put(part, blockDef.colorOffsets.map.get(part)); } } } return result; } private Map<BlockPart, Block.ColorSource> prepareColorSources(BlockDefinition blockDef) { Map<BlockPart, Block.ColorSource> result = Maps.newEnumMap(BlockPart.class); for (BlockPart part : BlockPart.values()) { result.put(part, blockDef.colorSource); } if (blockDef.colorSources != null) { for (BlockPart part : BlockPart.values()) { if (blockDef.colorSources.map.get(part) != null) { result.put(part, blockDef.colorSources.map.get(part)); } } } return result; } private BlockShape getShape(BlockDefinition blockDef) { BlockShape shape = null; if (!blockDef.shape.isEmpty()) { shape = (BlockShape) Assets.get(AssetType.SHAPE, blockDef.shape); } if (shape == null) { return cubeShape; } return shape; } private BlockAppearance createAppearance(BlockShape shape, Map<BlockPart, AssetUri> tileUris, Rotation rot) { Map<BlockPart, BlockMeshPart> meshParts = Maps.newEnumMap(BlockPart.class); Map<BlockPart, Vector2f> textureAtlasPositions = Maps.newEnumMap(BlockPart.class); for (BlockPart part : BlockPart.values()) { // TODO: Need to be more sensible with the texture atlas. Because things like block particles read from a part that may not exist, we're being fairly lenient Vector2f atlasPos = atlas.getTexCoords(tileUris.get(part), shape.getMeshPart(part) != null); BlockPart targetPart = part.rotate(rot); textureAtlasPositions.put(targetPart, atlasPos); if (shape.getMeshPart(part) != null) { meshParts.put(targetPart, shape.getMeshPart(part).rotate(rot.getQuat4f()).mapTexCoords(atlasPos, atlas.getRelativeTileSize())); } } return new BlockAppearance(meshParts, textureAtlasPositions); } private void setBlockFullSides(Block block, BlockShape shape, Rotation rot) { for (Side side : Side.values()) { BlockPart targetPart = BlockPart.fromSide(rot.rotate(side)); block.setFullSide(targetPart.getSide(), shape.isBlockingSide(side)); } } private void applyLoweredShape(Block block, BlockShape shape, Map<BlockPart, AssetUri> tileUris) { for (Side side : Side.values()) { BlockPart part = BlockPart.fromSide(side); BlockMeshPart meshPart = shape.getMeshPart(part).rotate(Rotation.none().getQuat4f()) .mapTexCoords(atlas.getTexCoords(tileUris.get(part), true), atlas.getRelativeTileSize()); block.setLoweredLiquidMesh(part.getSide(), meshPart); } } private Block createRawBlock(BlockDefinition def, String defaultName) { Block block = new Block(); block.setLiquid(def.liquid); block.setWater(def.water); block.setLava(def.lava); block.setGrass(def.grass); block.setIce(def.ice); block.setHardness(def.hardness); block.setAttachmentAllowed(def.attachmentAllowed); block.setReplacementAllowed(def.replacementAllowed); block.setSupportRequired(def.supportRequired); block.setPenetrable(def.penetrable); block.setTargetable(def.targetable); block.setClimbable(def.climbable); block.setInvisible(def.invisible); block.setTranslucent(def.translucent); block.setDoubleSided(def.doubleSided); block.setShadowCasting(def.shadowCasting); block.setWaving(def.waving); block.setLuminance(def.luminance); block.setTint(def.tint); if (!def.displayName.isEmpty()) { block.setDisplayName(def.displayName); } else { block.setDisplayName(properCase(defaultName)); } block.setMass(def.mass); block.setDebrisOnDestroy(def.debrisOnDestroy); if (def.entity != null) { block.setPrefab(def.entity.prefab); block.setKeepActive(def.entity.keepActive); } if (def.inventory != null) { block.setStackable(def.inventory.stackable); block.setDirectPickup(def.inventory.directPickup); } return block; } private Map<BlockPart, AssetUri> prepareTiles(BlockDefinition blockDef, AssetUri uri) { AssetUri tileUri = getDefaultTile(blockDef, uri); Map<BlockPart, AssetUri> tileUris = Maps.newEnumMap(BlockPart.class); for (BlockPart part : BlockPart.values()) { tileUris.put(part, tileUri); } if (blockDef.tiles != null) { for (BlockPart part : BlockPart.values()) { String partTile = blockDef.tiles.map.get(part); if (partTile != null) { tileUri = assetManager.resolve(AssetType.BLOCK_TILE, blockDef.tiles.map.get(part)); tileUris.put(part, tileUri); } } } return tileUris; } private AssetUri getDefaultTile(BlockDefinition blockDef, AssetUri uri) { String defaultName = uri.toSimpleString(); if (!blockDef.tile.isEmpty()) { defaultName = blockDef.tile; } return new AssetUri(AssetType.BLOCK_TILE, defaultName); } private JsonElement readJson(AssetUri blockDefUri) { try (InputStream stream = CoreRegistry.get(AssetManager.class).getAssetStream(blockDefUri)) { if (stream != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8)); return parser.parse(reader); } else { logger.error("Failed to load block definition '{}'", blockDefUri); } } catch (JsonParseException e) { logger.error("Failed to parse block definition '{}'", blockDefUri, e); } catch (IOException e) { logger.error("Failed to load block definition '{}'", blockDefUri, e); } return null; } private BlockDefinition createBlockDefinition(JsonElement element) { return gson.fromJson(element, BlockDefinition.class); } public static <T> void deserializeBlockPartMap(EnumMap<BlockPart, T> target, JsonObject jsonObj, Class<T> type, JsonDeserializationContext context) { if (jsonObj.has("all")) { T value = context.deserialize(jsonObj.get("all"), type); for (BlockPart part : BlockPart.values()) { target.put(part, value); } } if (jsonObj.has("sides")) { T value = context.deserialize(jsonObj.get("sides"), type); for (Side side : Side.horizontalSides()) { target.put(BlockPart.fromSide(side), value); } } if (jsonObj.has("topBottom")) { T value = context.deserialize(jsonObj.get("topBottom"), type); target.put(BlockPart.TOP, value); target.put(BlockPart.BOTTOM, value); } if (jsonObj.has("top")) { T value = context.deserialize(jsonObj.get("top"), type); target.put(BlockPart.TOP, value); } if (jsonObj.has("bottom")) { T value = context.deserialize(jsonObj.get("bottom"), type); target.put(BlockPart.BOTTOM, value); } if (jsonObj.has("front")) { T value = context.deserialize(jsonObj.get("front"), type); target.put(BlockPart.FRONT, value); } if (jsonObj.has("back")) { T value = context.deserialize(jsonObj.get("back"), type); target.put(BlockPart.BACK, value); } if (jsonObj.has("left")) { T value = context.deserialize(jsonObj.get("left"), type); target.put(BlockPart.LEFT, value); } if (jsonObj.has("right")) { T value = context.deserialize(jsonObj.get("right"), type); target.put(BlockPart.RIGHT, value); } if (jsonObj.has("center")) { T value = context.deserialize(jsonObj.get("center"), type); target.put(BlockPart.CENTER, value); } } private String properCase(String s) { if (s.length() > 1) { return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); } else { return s.toUpperCase(); } } public static class LoadBlockDefinitionResults { public List<BlockFamily> families = Lists.newArrayList(); public List<FreeformFamily> shapelessDefinitions = Lists.newArrayList(); } private static class BlockTilesDefinitionHandler implements JsonDeserializer<BlockDefinition.Tiles> { @Override public BlockDefinition.Tiles deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonObject()) { BlockDefinition.Tiles result = new BlockDefinition.Tiles(); deserializeBlockPartMap(result.map, json.getAsJsonObject(), String.class, context); return result; } return null; } } private static class BlockColorSourceDefinitionHandler implements JsonDeserializer<BlockDefinition.ColorSources> { @Override public BlockDefinition.ColorSources deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonObject()) { BlockDefinition.ColorSources result = new BlockDefinition.ColorSources(); deserializeBlockPartMap(result.map, json.getAsJsonObject(), Block.ColorSource.class, context); return result; } return null; } } private static class BlockColorOffsetDefinitionHandler implements JsonDeserializer<BlockDefinition.ColorOffsets> { @Override public BlockDefinition.ColorOffsets deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonObject()) { BlockDefinition.ColorOffsets result = new BlockDefinition.ColorOffsets(); deserializeBlockPartMap(result.map, json.getAsJsonObject(), Vector4f.class, context); return result; } return null; } } }