/* * Copyright 2014 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.rendering.nui.layers.mainMenu; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.utilities.Assets; import org.terasology.assets.ResourceUrn; import org.terasology.assets.module.ModuleAwareAssetTypeManager; import org.terasology.config.Config; import org.terasology.context.Context; import org.terasology.context.internal.ContextImpl; import org.terasology.engine.SimpleUri; import org.terasology.engine.bootstrap.EnvironmentSwitchHandler; import org.terasology.engine.module.ModuleManager; import org.terasology.entitySystem.Component; import org.terasology.entitySystem.metadata.ComponentLibrary; import org.terasology.math.TeraMath; import org.terasology.module.DependencyResolver; import org.terasology.module.ModuleEnvironment; import org.terasology.module.ResolutionResult; import org.terasology.module.exceptions.UnresolvedDependencyException; import org.terasology.reflection.metadata.FieldMetadata; import org.terasology.registry.CoreRegistry; import org.terasology.registry.In; import org.terasology.rendering.assets.texture.Texture; import org.terasology.rendering.assets.texture.TextureData; import org.terasology.rendering.nui.CoreScreenLayer; import org.terasology.rendering.nui.NUIManager; import org.terasology.rendering.nui.WidgetUtil; import org.terasology.rendering.nui.animation.MenuAnimationSystems; import org.terasology.rendering.nui.databinding.Binding; import org.terasology.rendering.nui.layers.mainMenu.preview.FacetLayerPreview; import org.terasology.rendering.nui.layers.mainMenu.preview.PreviewGenerator; import org.terasology.rendering.nui.layouts.PropertyLayout; import org.terasology.rendering.nui.properties.Property; import org.terasology.rendering.nui.properties.PropertyOrdering; import org.terasology.rendering.nui.properties.PropertyProvider; import org.terasology.rendering.nui.widgets.UIButton; import org.terasology.rendering.nui.widgets.UIImage; import org.terasology.rendering.nui.widgets.UISlider; import org.terasology.rendering.nui.widgets.UIText; import org.terasology.world.generator.WorldConfigurator; import org.terasology.world.generator.WorldGenerator; import org.terasology.world.generator.internal.WorldGeneratorManager; import org.terasology.world.generator.plugin.TempWorldGeneratorPluginLibrary; import org.terasology.world.generator.plugin.WorldGeneratorPluginLibrary; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; /** * Shows a preview of the generated world and provides some * configuration options to tweak the generation process. */ public class PreviewWorldScreen extends CoreScreenLayer { public static final ResourceUrn ASSET_URI = new ResourceUrn("engine:previewWorldScreen"); private static final Logger logger = LoggerFactory.getLogger(PreviewWorldScreen.class); @In private ModuleManager moduleManager; @In private ModuleAwareAssetTypeManager assetTypeManager; @In private WorldGeneratorManager worldGeneratorManager; @In private Config config; @In private Context context; private WorldGenerator worldGenerator; private UIImage previewImage; private UISlider zoomSlider; private UIButton applyButton; private UIText seed; private PreviewGenerator previewGen; private Context subContext; private ModuleEnvironment environment; private Texture texture; private boolean triggerUpdate; public PreviewWorldScreen() { } public void setEnvironment() throws Exception { // TODO: pass world gen and module list directly rather than using the config SimpleUri worldGenUri = config.getWorldGeneration().getDefaultGenerator(); DependencyResolver resolver = new DependencyResolver(moduleManager.getRegistry()); ResolutionResult result = resolver.resolve(config.getDefaultModSelection().listModules()); if (result.isSuccess()) { subContext = new ContextImpl(context); CoreRegistry.setContext(subContext); environment = moduleManager.loadEnvironment(result.getModules(), false); subContext.put(WorldGeneratorPluginLibrary.class, new TempWorldGeneratorPluginLibrary(environment, subContext)); EnvironmentSwitchHandler environmentSwitchHandler = context.get(EnvironmentSwitchHandler.class); environmentSwitchHandler.handleSwitchToPreviewEnvironment(subContext, environment); genTexture(); worldGenerator = WorldGeneratorManager.createWorldGenerator(worldGenUri, subContext, environment); worldGenerator.setWorldSeed(seed.getText()); previewGen = new FacetLayerPreview(environment, worldGenerator); configureProperties(); } else { throw new UnresolvedDependencyException("Unable to resolve depencencies for " + worldGenUri); } } private void genTexture() { int imgWidth = 384; int imgHeight = 384; ByteBuffer buffer = ByteBuffer.allocateDirect(imgWidth * imgHeight * Integer.BYTES); ByteBuffer[] data = new ByteBuffer[]{buffer}; ResourceUrn uri = new ResourceUrn("engine:terrainPreview"); TextureData texData = new TextureData(imgWidth, imgHeight, data, Texture.WrapMode.CLAMP, Texture.FilterMode.LINEAR); texture = Assets.generateAsset(uri, texData, Texture.class); previewImage = find("preview", UIImage.class); previewImage.setImage(texture); } @Override public void update(float delta) { super.update(delta); if (triggerUpdate) { updatePreview(); triggerUpdate = false; } } private void configureProperties() { PropertyLayout propLayout = find("properties", PropertyLayout.class); propLayout.setOrdering(PropertyOrdering.byLabel()); propLayout.clear(); WorldConfigurator worldConfig = worldGenerator.getConfigurator(); Map<String, Component> params = worldConfig.getProperties(); for (String key : params.keySet()) { Class<? extends Component> clazz = params.get(key).getClass(); Component comp = config.getModuleConfig(worldGenerator.getUri(), key, clazz); if (comp != null) { worldConfig.setProperty(key, comp); // use the data from the config instead of defaults } } ComponentLibrary compLib = subContext.get(ComponentLibrary.class); for (String label : params.keySet()) { PropertyProvider provider = new PropertyProvider() { @Override protected <T> Binding<T> createTextBinding(Object target, FieldMetadata<Object, T> fieldMetadata) { return new WorldConfigBinding<>(worldConfig, label, compLib, fieldMetadata); } @Override protected Binding<Float> createFloatBinding(Object target, FieldMetadata<Object, ?> fieldMetadata) { return new WorldConfigNumberBinding(worldConfig, label, compLib, fieldMetadata); } }; Component target = params.get(label); List<Property<?, ?>> properties = provider.createProperties(target); propLayout.addProperties(label, properties); } } private void resetEnvironment() { CoreRegistry.setContext(context); if (environment != null) { EnvironmentSwitchHandler environmentSwitchHandler = context.get(EnvironmentSwitchHandler.class); environmentSwitchHandler.handleSwitchBackFromPreviewEnvironment(subContext); environment.close(); environment = null; } previewGen.close(); WorldConfigurator worldConfig = worldGenerator.getConfigurator(); Map<String, Component> params = worldConfig.getProperties(); if (params != null) { config.setModuleConfigs(worldGenerator.getUri(), params); } } @Override public void initialise() { setAnimationSystem(MenuAnimationSystems.createDefaultSwipeAnimation()); zoomSlider = find("zoomSlider", UISlider.class); if (zoomSlider != null) { zoomSlider.setValue(2f); } seed = find("seed", UIText.class); applyButton = find("apply", UIButton.class); if (applyButton != null) { applyButton.subscribe(widget -> updatePreview()); } WidgetUtil.trySubscribe(this, "close", w -> { resetEnvironment(); triggerBackAnimation(); }); } @Override public boolean isLowerLayerVisible() { return false; } public void bindSeed(Binding<String> binding) { if (seed == null) { // TODO: call initialize through NUIManager instead of onOpened() seed = find("seed", UIText.class); } seed.bindText(binding); } private void updatePreview() { final NUIManager manager = context.get(NUIManager.class); final WaitPopup<TextureData> popup = manager.pushScreen(WaitPopup.ASSET_URI, WaitPopup.class); popup.setMessage("Updating Preview", "Please wait ..."); ProgressListener progressListener = progress -> popup.setMessage("Updating Preview", String.format("Please wait ... %d%%", (int) (progress * 100f))); Callable<TextureData> operation = () -> { if (seed != null) { worldGenerator.setWorldSeed(seed.getText()); } int zoom = TeraMath.floorToInt(zoomSlider.getValue()); TextureData data = texture.getData(); previewGen.render(data, zoom, progressListener); return data; }; popup.onSuccess(texture::reload); popup.startOperation(operation, true); } /** * Updates a world configurator through setProperty() whenever Binding#set() is called. */ private static class WorldConfigBinding<T> implements Binding<T> { private final String label; private final WorldConfigurator worldConfig; private final FieldMetadata<Object, T> fieldMetadata; private final ComponentLibrary compLib; protected WorldConfigBinding(WorldConfigurator config, String label, ComponentLibrary compLib, FieldMetadata<Object, T> fieldMetadata) { this.worldConfig = config; this.label = label; this.compLib = compLib; this.fieldMetadata = fieldMetadata; } @Override public T get() { Component comp = worldConfig.getProperties().get(label); return fieldMetadata.getValue(comp); } @Override public void set(T value) { T old = get(); if (!Objects.equals(old, value)) { cloneAndSet(label, value); } } private void cloneAndSet(String group, Object value) { Component comp = worldConfig.getProperties().get(group); Component clone = compLib.copy(comp); fieldMetadata.setValue(clone, value); // notify the world generator about the new component worldConfig.setProperty(label, clone); } } private static class WorldConfigNumberBinding implements Binding<Float> { private WorldConfigBinding<? extends Number> binding; @SuppressWarnings("unchecked") protected WorldConfigNumberBinding(WorldConfigurator config, String label, ComponentLibrary compLib, FieldMetadata<Object, ?> field) { Class<?> type = field.getType(); if (type == Integer.TYPE || type == Integer.class) { this.binding = new WorldConfigBinding<>(config, label, compLib, (FieldMetadata<Object, Integer>) field); } else if (type == Float.TYPE || type == Float.class) { this.binding = new WorldConfigBinding<>(config, label, compLib, (FieldMetadata<Object, Float>) field); } } @Override public Float get() { Number val = binding.get(); if (val instanceof Float) { // use boxed instance directly return (Float) val; } // create a boxed instance otherwise return val.floatValue(); } @Override @SuppressWarnings("unchecked") public void set(Float value) { Class<? extends Number> type = binding.fieldMetadata.getType(); if (type == Integer.TYPE || type == Integer.class) { ((Binding<Integer>) binding).set(value.intValue()); } else if (type == Float.TYPE || type == Float.class) { ((Binding<Float>) binding).set(value); } } } }