diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5e90407 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +# Normalize as LF in the repository, OS native locally +* text=auto +*.java text + +# Binary files that should not be modified +*.dat binary +*.db binary +*.icns binary +*.ico binary +*.key binary +*.jks binary +*.jpg binary +*.png binary +*.ttf binary +*.wav binary +JavaApplicationStub binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e4ff99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +# Build # +######### +MANIFEST.MF +dependency-reduced-pom.xml + +# Compiled # +############ +bin +build +dist +lib +out +target +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Databases # +############# +*.db +*.sql +*.sqlite + +# Packages # +############ +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Repository # +############## +.git + +# Logging # +########### +/logs +*.log + +# Misc # +######## +*.bak + +# System # +########## +.DS_Store +ehthumbs.db +Thumbs.db + +# Project # +########### +.classpath +.externalToolBuilders +.idea +.project +.settings +nbproject +atlassian-ide-plugin.xml +build.xml +nb-configuration.xml +*.iml +*.ipr +*.iws + +# Runtime # +########### +/cache +/config +/data +/natives +/plugins +/resources +/update +/updates +/worlds diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ae6754c --- /dev/null +++ b/pom.xml @@ -0,0 +1,197 @@ + + 4.0.0 + + org.spout + Spout2 + 1.0.0-SNAPSHOT + jar + + Spout2 + http://maven.apache.org + + + UTF-8 + + + + + + sonatype-nexus-releases + https://oss.sonatype.org/content/repositories/releases + + + sonatype-nexus-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + true + always + + + + 4thline-cling + http://4thline.org/m2 + + true + always + + + + + + + com.beust + jcommander + 1.30 + compile + + + junit + junit + 3.8.1 + test + + + commons-io + commons-io + 2.4 + + + commons-collections + commons-collections + jar + 3.2.1 + + + org.spout + react + 1.0.0-SNAPSHOT + + + com.flowpowered + flow-events + 0.1.0-SNAPSHOT + + + net.sf.trove4j + trove4j + 3.0.3 + compile + + + com.flowpowered + flow-filesystem + 0.1.0-SNAPSHOT + + + ${project.groupId} + spout-math + ${project.version} + + + com.flowpowered + flow-commons + 0.1.0-SNAPSHOT + + + com.google.guava + guava + 15.0 + + + org.spout.flow + flow-chat-commands + 1.0.0-SNAPSHOT + + + ${project.groupId} + caustic + ${project.version} + + + org.spout + simplenbt + 1.0.5-SNAPSHOT + compile + + + org.slf4j + slf4j-jdk14 + 1.7.5 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.7 + 1.7 + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + org.spout.engine.SpoutApplication + ${project.name} + ${project.version}+${ciSystem}-b${buildNumber}.git-${commit} + ${project.organization.name} + ${project.name} API + ${project.version}+${ciSystem}-b${buildNumber}.git-${commit} + ${project.organization.name} + + + + org/spout/ + + true + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.1 + + + + + + *:* + + lib/** + templates/** + META-INF/*.xml + META-INF/*.txt + **/LICENSE + **/NOTICE + **/DEPENDENCIES + **/*.java + + + + + package + + shade + + + + + + + diff --git a/src/main/java/org/spout/api/Client.java b/src/main/java/org/spout/api/Client.java new file mode 100644 index 0000000..c7c831c --- /dev/null +++ b/src/main/java/org/spout/api/Client.java @@ -0,0 +1,59 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api; + +import org.spout.api.entity.Player; +import org.spout.api.geo.World; +import org.spout.api.render.Renderer; + +/** + * Represents the client-specific component of the Spout platform. + */ +public interface Client extends Engine { + /** + * Gets the player on the local machine (the one who is using the client). + * + * @return player + */ + public Player getPlayer(); + + /** + * Gets the current world in-which the player on the local machine is within. + * + * This is always the world the client is currently rendering. + * + * @return world + */ + public World getWorld(); + + /** + * Gets the renderer that the client is using. + * + * @return the renderer in use + */ + public Renderer getRenderer(); +} diff --git a/src/main/java/org/spout/api/Engine.java b/src/main/java/org/spout/api/Engine.java new file mode 100644 index 0000000..cc5dd11 --- /dev/null +++ b/src/main/java/org/spout/api/Engine.java @@ -0,0 +1,89 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api; + +import com.flowpowered.commons.Named; +import com.flowpowered.events.EventManager; +import com.flowpowered.filesystem.FileSystem; +import org.spout.api.geo.WorldManager; +import org.spout.api.scheduler.Scheduler; + +/** + * Represents the core of an implementation of an engine (powers a game). + */ +public interface Engine extends Named { + + /** + * Gets the version. + * + * @return build version + */ + public String getVersion(); + + public Platform getPlatform(); + + /** + * Ends this engine instance safely. All worlds, players, and configuration data is saved, and all threads are ended cleanly.

Players will be sent a default disconnect message. + * + * @return true for for the first stop + */ + public boolean stop(); + + /** + * Ends this engine instance safely. All worlds, players, and configuration data is saved, and all threads are ended cleanly.
If any players are connected, will kick them with the given reason. + * + * @param reason for stopping the game instance + * @return true for for the first stop + */ + public boolean stop(String reason); + + /** + * Returns true if the game is running in debug mode

To start debug mode, start Spout with -debug + * + * @return true if server is started with the -debug flag, false if not + */ + public boolean debugMode(); + + public Scheduler getScheduler(); + + /** + * Gets an abstract representation of the engine Filesystem.

The Filesystem handles the loading of all resources.

On the client, loading a resource will load the resource from the harddrive. + * On the server, it will notify all clients to load the resource, as well as provide a representation of that resource. + * + * @return the filesystem instance + */ + public FileSystem getFileSystem(); + + /** + * Returns the game's {@link EventManager} Event listener registration and calling is handled through this. + * + * @return Our EventManager instance + */ + public EventManager getEventManager(); + + public WorldManager getWorldManager(); +} diff --git a/src/main/java/org/spout/api/Platform.java b/src/main/java/org/spout/api/Platform.java new file mode 100644 index 0000000..4526236 --- /dev/null +++ b/src/main/java/org/spout/api/Platform.java @@ -0,0 +1,51 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api; + +/** + * Platform describes whether the plugin was written for the client, the server, or for both. + */ +public enum Platform { + SERVER(false, true), + CLIENT(true, false), + SINGLEPLAYER(true, true); + + private final boolean client, server; + + private Platform(boolean client, boolean server) { + this.client = client; + this.server = server; + } + + public boolean isClient() { + return client; + } + + public boolean isServer() { + return server; + } +} diff --git a/src/main/java/org/spout/api/Server.java b/src/main/java/org/spout/api/Server.java new file mode 100644 index 0000000..871d15d --- /dev/null +++ b/src/main/java/org/spout/api/Server.java @@ -0,0 +1,84 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api; + +import java.util.Collection; + +import org.spout.api.entity.Player; +import org.spout.api.geo.ServerWorldManager; + +/** + * Represents the server-specific implementation. + */ +public interface Server extends Engine { + /** + * Gets all players currently online + * + * @return array of all active players + */ + public Collection getOnlinePlayers(); + + /** + * Gets the maximum number of players this game can host, or -1 if infinite + * + * @return max players + */ + public int getMaxPlayers(); + + /** + * Broadcasts the given message to all players + * + * The implementation of broadcast is identical to iterating over {@link #getOnlinePlayers()} and invoking {@link Player#sendMessage(String)} for each player. + * + * @param message to send + */ + public void broadcastMessage(String message); + + /** + * Broadcasts the given message to all players + * + * The implementation of broadcast is identical to calling a {@link org.spout.api.event.server.permissions.PermissionGetAllWithNodeEvent} event, iterating over each element in getReceivers, invoking + * {@link org.spout.api.command.CommandSource#sendMessage(String)} for each CommandSource. + * + * @param permission the permission needed to receive the broadcast + * @param message to send + */ + public void broadcastMessage(String permission, String message); + + /** + * Gets the {@link Player} by the given username.

If searching for the exact name, this method will iterate and check for exact matches.

Otherwise, this method will iterate + * over over all players and find the closest match to the given name, by comparing the length of other player names that start with the given parameter.

This method is case-insensitive. + * + * @param name to look up + * @param exact Whether to use exact lookup + * @return Player if found, else null + */ + public Player getPlayer(String name, boolean exact); + + @Override + public ServerWorldManager getWorldManager(); +} diff --git a/src/main/java/org/spout/api/Singleplayer.java b/src/main/java/org/spout/api/Singleplayer.java new file mode 100644 index 0000000..556f476 --- /dev/null +++ b/src/main/java/org/spout/api/Singleplayer.java @@ -0,0 +1,4 @@ +package org.spout.api; + +public interface Singleplayer extends Server, Client { +} diff --git a/src/main/java/org/spout/api/Spout.java b/src/main/java/org/spout/api/Spout.java new file mode 100644 index 0000000..7252ae5 --- /dev/null +++ b/src/main/java/org/spout/api/Spout.java @@ -0,0 +1,217 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.flowpowered.events.EventManager; +import com.flowpowered.filesystem.FileSystem; + +/** + * Represents the Spout core, to get singleton {@link Engine} instance + */ +public final class Spout { + private static Engine instance = null; + private static final Logger logger = Logger.getLogger("Spout"); + + private Spout() { + throw new IllegalStateException("Can not construct Spout instance"); + } + + /** + * Gets the {@link Logger} instance that is used to write to the console. + * + * @return logger + */ + public static Logger getLogger() { + return logger; + } + + /** + * Prints the specified object if debug mode is enabled. + * + * @param obj to print + */ + public static void debug(Object obj) { + if (debugMode()) { + info(obj.toString()); + } + } + + /** + * Logs the specified message to print if debug mode is enabled. + * + * @param log message + * @param t to throw + * @see #debugMode() + */ + public static void debug(String log, Throwable t) { + if (debugMode()) { + info(log, t); + } + } + + /** + * Logs the specified message to print if debug mode is enabled. + * + * @param log message + * @param params of message + * @see #debugMode() + */ + public static void debug(String log, Object... params) { + if (debugMode()) { + info(log, params); + } + } + + public static void finest(String log, Throwable t) { + logger.log(Level.FINEST, log, t); + } + + public static void finest(String log, Object... params) { + logger.log(Level.FINEST, log, params); + } + + public static void finer(String log, Throwable t) { + logger.log(Level.FINER, log, t); + } + + public static void finer(String log, Object... params) { + logger.log(Level.FINER, log, params); + } + + public static void fine(String log, Throwable t) { + logger.log(Level.FINE, log, t); + } + + public static void fine(String log, Object... params) { + logger.log(Level.FINE, log, params); + } + + public static void info(String log, Throwable t) { + logger.log(Level.INFO, log, t); + } + + public static void info(String log, Object... params) { + logger.log(Level.INFO, log, params); + } + + public static void warn(String log, Throwable t) { + logger.log(Level.WARNING, log, t); + } + + public static void warn(String log, Object... params) { + logger.log(Level.WARNING, log, params); + } + + public static void severe(String log, Throwable t) { + logger.log(Level.SEVERE, log, t); + } + + public static void severe(String log, Object... params) { + logger.log(Level.SEVERE, log, params); + } + + public static void setEngine(Engine game) { + if (instance == null) { + instance = game; + } else { + throw new UnsupportedOperationException("Can not redefine singleton Game instance"); + } + } + + /** + * Gets the currently running engine instance. + * + * @return engine + */ + public static Engine getEngine() { + return instance; + } + + /** + * Ends this game instance safely. All worlds, players, and configuration data is saved, and all threads are ended cleanly.

Players will be sent a default disconnect message. + */ + public static void stop() { + instance.stop(); + } + + /** + * Returns the game's {@link EventManager} Event listener registration and calling is handled through this. + * + * @return Our EventManager instance + */ + public static EventManager getEventManager() { + return instance.getEventManager(); + } + /** + * Returns the {@link Platform} that the game is currently running on. + * + * @return current platform type + */ + public static Platform getPlatform() { + return instance.getPlatform(); + } + + /** + * Returns true if the game is running in debug mode

To start debug mode, start Spout with -debug + * + * @return true if server is started with the -debug flag, false if not + */ + public static boolean debugMode() { + return instance.debugMode(); + } + + /** + * Logs the given string using {@Link Logger#info(String)} to the default logger instance. + * + * @param arg to log + */ + public static void log(String arg) { + logger.info(arg); + } + + /** + * Returns the String version of the API. + * + * @return version + */ + public static String getAPIVersion() { + return instance.getClass().getPackage().getImplementationVersion(); + } + + /** + * Gets an abstract representation of the engine's {@link FileSystem}.

The Filesystem handles the loading of all resources.

On the client, loading a resource will load the + * resource from the harddrive.
On the server, it will notify all clients to load the resource, as well as provide a representation of that resource. + * + * @return filesystem from the engine. + */ + public static FileSystem getFileSystem() { + return instance.getFileSystem(); + } +} diff --git a/src/main/java/org/spout/api/component/BaseComponentOwner.java b/src/main/java/org/spout/api/component/BaseComponentOwner.java new file mode 100644 index 0000000..57e373d --- /dev/null +++ b/src/main/java/org/spout/api/component/BaseComponentOwner.java @@ -0,0 +1,257 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.component; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.logging.Level; + +import com.flowpowered.commons.datatable.ManagedHashMap; +import com.flowpowered.events.Listener; + +import com.google.common.base.Preconditions; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import org.spout.api.Spout; + +public class BaseComponentOwner implements ComponentOwner { + /** + * Map of class name, component + */ + private final BiMap, Component> components = HashBiMap.create(); + private final ManagedHashMap data; + + public BaseComponentOwner() { + data = new ManagedHashMap(); + } + + public BaseComponentOwner(ManagedHashMap data) { + this.data = data; + } + + /** + * For use de-serializing a list of components all at once, without having to worry about dependencies + */ + protected void add(Class... components) { + HashSet added = new HashSet<>(); + synchronized (components) { + for (Class type : components) { + if (!this.components.containsKey(type)) { + added.add(add(type, false)); + } + } + } + for (Component type : added) { + type.onAttached(); + } + } + + @Override + public T add(Class type) { + return add(type, true); + } + + /** + * Adds a component to the map + * + * @param type to add + * @param attach whether to call the component onAttached + * @return instantiated component + */ + protected T add(Class type, boolean attach) { + return add(type, type, attach); + } + + /** + * Adds a component to the map + * + * @param key the component class used as the lookup key + * @param type of component to instantiate + * @param attach whether to call the component onAttached + * @return instantiated component + */ + @SuppressWarnings ("unchecked") + protected final T add(Class key, Class type, boolean attach) { + if (type == null || key == null) { + return null; + } + + synchronized (components) { + T component = (T) components.get(key); + + if (component != null) { + return component; + } + + try { + component = (T) type.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + + if (component != null) { + try { + attachComponent(key, component, attach); + } catch (Exception e) { + Spout.getLogger().log(Level.SEVERE, "Error while attaching component " + type + ": ", e); + } + } + return component; + } + } + + protected void attachComponent(Class key, Component component, boolean attach) throws Exception { + if (component.attachTo(this)) { + components.put(key, component); + if (attach) { + try { + component.onAttached(); + } catch (Exception e) { + // Remove the component from the component map if onAttached can't be + // called, pass exception to next catch block. + components.remove(key); + throw e; + } + } + } + } + + @Override + public T detach(Class type) { + return detach(type, false); + } + + @SuppressWarnings ("unchecked") + protected T detach(Class type, boolean force) { + Preconditions.checkNotNull(type); + synchronized (components) { + T component = (T) get(type); + + if (component != null && (component.isDetachable() || force)) { + components.inverse().remove(component); + try { + component.onDetached(); + if (component instanceof Listener) { + Spout.getEventManager().unRegisterEvents((Listener) component); + } + } catch (Exception e) { + Spout.getLogger().log(Level.SEVERE, "Error detaching component " + type + " from holder: ", e); + } + } + + return component; + } + } + + @SuppressWarnings ("unchecked") + @Override + public T get(Class type) { + Preconditions.checkNotNull(type); + Component component = components.get(type); + + if (component == null) { + component = findComponent(type); + } + return (T) component; + } + + @Override + public T getType(Class type) { + Preconditions.checkNotNull(type); + + T component = findComponent(type); + + return component; + } + + @SuppressWarnings ("unchecked") + @Override + public T getExact(Class type) { + Preconditions.checkNotNull(type); + synchronized (components) { + return (T) components.get(type); + } + } + + @SuppressWarnings ("unchecked") + @Override + public Collection getAll(Class type) { + Preconditions.checkNotNull(type); + synchronized (components) { + ArrayList matches = new ArrayList<>(); + for (Component component : components.values()) { + if (type.isAssignableFrom(component.getClass())) { + matches.add((T) component); + } + } + return matches; + } + } + + @SuppressWarnings ("unchecked") + @Override + public Collection getAllOfType(Class type) { + Preconditions.checkNotNull(type); + synchronized (components) { + ArrayList matches = new ArrayList<>(); + for (Component component : components.values()) { + if (type.isAssignableFrom(component.getClass())) { + matches.add((T) component); + } + } + return matches; + } + } + + @Override + public Collection values() { + synchronized (components) { + return new ArrayList<>(components.values()); + } + } + + @Override + public ManagedHashMap getData() { + return data; + } + + @SuppressWarnings ("unchecked") + private T findComponent(Class type) { + Preconditions.checkNotNull(type); + synchronized (components) { + for (Component component : values()) { + if (type.isAssignableFrom(component.getClass())) { + return (T) component; + } + } + } + + return null; + } +} diff --git a/src/main/java/org/spout/api/component/BlockComponentOwner.java b/src/main/java/org/spout/api/component/BlockComponentOwner.java new file mode 100644 index 0000000..01915e8 --- /dev/null +++ b/src/main/java/org/spout/api/component/BlockComponentOwner.java @@ -0,0 +1,70 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.component; + +import com.flowpowered.commons.datatable.ManagedHashMap; + +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.cuboid.Chunk; + +// TODO: I don't see why we shouldn't move this to Spout +public class BlockComponentOwner extends BaseComponentOwner { + /** + * Stored as world, not chunk, coords + */ + private final int x, y, z; + private final World world; + + public BlockComponentOwner(ManagedHashMap chunkData, int x, int y, int z, World world) { + super(new ManagedHashMap(chunkData, "" + (x & Chunk.BLOCKS.MASK) + "," + (y & Chunk.BLOCKS.MASK) + "," + (z & Chunk.BLOCKS.MASK))); + this.x = x; + this.y = y; + this.z = z; + this.world = world; + } + + public Block getBlock() { + return world.getBlock(x, y, z); + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public World getWorld() { + return world; + } +} diff --git a/src/main/java/org/spout/api/component/Component.java b/src/main/java/org/spout/api/component/Component.java new file mode 100644 index 0000000..c3a5bd5 --- /dev/null +++ b/src/main/java/org/spout/api/component/Component.java @@ -0,0 +1,106 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.component; + +import com.flowpowered.commons.datatable.SerializableMap; +import org.spout.api.scheduler.tickable.Tickable; + +public abstract class Component implements Tickable { + private ComponentOwner owner; + + public Component() { + } + + @Override + public boolean canTick() { + return true; + } + + @Override + public final void tick(float dt) { + if (canTick()) { + onTick(dt); + } + } + + @Override + public void onTick(float dt) { + } + + /** + * Attaches to a component owner. + * + * @param owner the component owner to attach to + * @return true if successful + */ + public boolean attachTo(ComponentOwner owner) { + this.owner = owner; + return true; + } + + /** + * Gets the component owner that owns this component. + * + * @return the component owner + */ + public ComponentOwner getOwner() { + if (owner == null) { + throw new IllegalStateException("Trying to access the owner of this component before it was attached"); + } + return owner; + } + + /** + * Called when this component is attached to a owner. + */ + public void onAttached() { + } + + /** + * Called when this component is detached from a owner. + */ + public void onDetached() { + } + + /** + * Specifies whether or not this component can be detached, after it has already been attached to an owner.. + * + * @return true if it can be detached + */ + public boolean isDetachable() { + return true; + } + + /** + * Gets the {@link SerializableMap} which a ComponentOwner always has

This is merely a convenience method. + * + * @return SerializableMap of the owner + */ + public final SerializableMap getData() { + return getOwner().getData(); + } +} diff --git a/src/main/java/org/spout/api/component/ComponentOwner.java b/src/main/java/org/spout/api/component/ComponentOwner.java new file mode 100644 index 0000000..a5f011a --- /dev/null +++ b/src/main/java/org/spout/api/component/ComponentOwner.java @@ -0,0 +1,106 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.component; + +import java.util.Collection; + +import com.flowpowered.commons.datatable.ManagedMap; + +/** + * Represents an object which may own components. + */ +public interface ComponentOwner { + /** + * Adds the component of the specified type to the owner and returns it if it is not present.

Otherwise, it returns the component of the specified type if there was one present.

+ * + * @param type whose component is to be added to the owner + * @return the new component that was added, or the existing one if it had one + */ + public T add(Class type); + + /** + * Returns the component of the specified type (or a child implementation) from the owner if it is present. + * + * @param type whose component is to be returned from the holder + * @return the component, or null if one was not found + */ + public T get(Class type); + + /** + * Returns all components of the specified type (or a child implementation). + * + * @param type whose components are to be returned from the owner + * @return the component list. + */ + public Collection getAll(Class type); + + /** + * Returns all instances of the specified type from the owner if they are present. + * + * @param type whose components are to be returned from the owner + * @return the component list. + */ + public Collection getAllOfType(Class type); + + /** + * Returns the component of the specified type (not a child implementation) from the holder if it is present. + * + * @param type whose component is to be returned from the owner + * @return the component, or null if one was not found. + */ + public T getExact(Class type); + + /** + * Returns an instance of the specified type from the owner if it is present. + * + * @param type whose component is to be returned from the owner + * @return the component, or null if one was not found + */ + public T getType(Class type); + + /** + * Removes the component of the specified type from the owner if it is present. + * + * @param type whose component is to be removed from the owner + * @return the removed component, or null if there was not one + */ + public T detach(Class type); + + /** + * Gets all components held by the owner. + * + * @return A collection of held components + */ + public Collection values(); + + /** + * Gets the {@link ManagedMap} of the owner. + * + * @return datatable component + */ + public ManagedMap getData(); +} diff --git a/src/main/java/org/spout/api/component/block/BlockComponent.java b/src/main/java/org/spout/api/component/block/BlockComponent.java new file mode 100644 index 0000000..e2ab86a --- /dev/null +++ b/src/main/java/org/spout/api/component/block/BlockComponent.java @@ -0,0 +1,87 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.component.block; + +import org.spout.api.component.BlockComponentOwner; +import org.spout.api.component.Component; +import org.spout.api.component.ComponentOwner; +import org.spout.api.entity.Entity; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.discrete.Point; + +public abstract class BlockComponent extends Component { + @Override + public final boolean attachTo(ComponentOwner owner) { + if (!(owner instanceof BlockComponentOwner)) { + throw new IllegalStateException("BlockComponents may only be attached to a BlockComponentOwner."); + } + return super.attachTo(owner); + } + + @Override + public final BlockComponentOwner getOwner() { + return (BlockComponentOwner) super.getOwner(); + } + + /** + * Gets the position of this block component + * + * @return position + */ + public Point getPoint() { + final BlockComponentOwner owner = getOwner(); + if (owner == null) { + throw new IllegalStateException("This component has no owner and therefore no point"); + } + return new Point(owner.getWorld(), owner.getX(), owner.getY(), owner.getZ()); + } + + /** + * Gets the {@link Block} who owns this component. + * + * The structure of BlockComponents differ from the other {@link ComponentOwner}s. {@link BlockComponentOwner} is what does BlockComponent management but Block itself owns the block. To keep things + * easy to access, this convenience method is provided. + * + * @return the block associated with the BlockComponentOwner + */ + public Block getBlock() { + final BlockComponentOwner owner = getOwner(); + if (owner == null) { + throw new IllegalStateException("This component has no owner and therefore no block"); + } + return owner.getBlock(); + } + + /** + * Called when the owning {@link org.spout.api.geo.cuboid.Block} is collided with an {@link Entity}. + * + * @param point the point where collision occurred. + * @param entity the entity that collided with the owner

TODO EntityCollideBlockEvent + */ + public void onCollided(Point point, Entity entity) { + } +} diff --git a/src/main/java/org/spout/api/component/entity/EntityComponent.java b/src/main/java/org/spout/api/component/entity/EntityComponent.java new file mode 100644 index 0000000..2279256 --- /dev/null +++ b/src/main/java/org/spout/api/component/entity/EntityComponent.java @@ -0,0 +1,82 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.component.entity; + +import java.util.Random; + +import org.spout.api.Engine; +import org.spout.api.component.Component; +import org.spout.api.component.ComponentOwner; +import org.spout.api.entity.Entity; +import org.spout.math.GenericMath; + +/** + * Represents a component who shapes the logic behind an {@link Entity}. + */ +public abstract class EntityComponent extends Component { + @Override + public final boolean attachTo(ComponentOwner owner) { + if (!(owner instanceof Entity)) { + throw new IllegalStateException("EntityComponents may only be attached to Entities."); + } + return super.attachTo(owner); + } + + @Override + public Entity getOwner() { + return (Entity) super.getOwner(); + } + + /** + * Returns a deterministic random number generator + * + * @return random the random generator + */ + public final Random getRandom() { + return GenericMath.getRandom(); + } + + public final Engine getEngine() { + final Entity owner = getOwner(); + if (owner == null) { + throw new IllegalStateException("Can not access the engine w/o an owner"); + } + return owner.getEngine(); + } + + /** + * Called when the owner comes within range of another owner with an attached {@link ObserverComponent}.

TODO EntityObservedEvent + */ + public void onObserved() { + } + + /** + * Called when the owner is out of range of any owners with attached {@link ObserverComponent}s.

TODO EntityUnObservedEvent + */ + public void onUnObserved() { + } +} diff --git a/src/main/java/org/spout/api/component/world/WorldComponent.java b/src/main/java/org/spout/api/component/world/WorldComponent.java new file mode 100644 index 0000000..8798e2b --- /dev/null +++ b/src/main/java/org/spout/api/component/world/WorldComponent.java @@ -0,0 +1,46 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.component.world; + +import org.spout.api.component.Component; +import org.spout.api.component.ComponentOwner; +import org.spout.api.geo.World; + +public abstract class WorldComponent extends Component { + @Override + public boolean attachTo(ComponentOwner owner) { + if (!(owner instanceof World)) { + throw new IllegalStateException("WorldComponents are only allowed to be attached to Worlds."); + } + return super.attachTo(owner); + } + + @Override + public World getOwner() { + return (World) super.getOwner(); + } +} diff --git a/src/main/java/org/spout/api/entity/Entity.java b/src/main/java/org/spout/api/entity/Entity.java new file mode 100644 index 0000000..d7682ba --- /dev/null +++ b/src/main/java/org/spout/api/entity/Entity.java @@ -0,0 +1,118 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.entity; + +import java.util.UUID; + +import com.flowpowered.commons.datatable.ManagedMap; + +import org.spout.api.Engine; +import org.spout.api.component.ComponentOwner; +import org.spout.api.geo.WorldSource; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.geo.cuboid.Region; +import org.spout.api.scheduler.tickable.Tickable; + +/** + * Represents an entity, which may or may not be spawned into the world. + */ +public interface Entity extends Tickable, WorldSource, ComponentOwner { + /** + * Gets the current ID of this entity within the current game session + * + * @return The entities' id. + */ + public int getId(); + + /** + * Gets the entity's persistent unique id.

Can be used to look up the entity and persists between starts. + * + * @return persistent {@link UUID} + */ + public UUID getUID(); + + /** + * Gets the {@link Engine} that spawned and is managing this entity + * + * @return {@link Engine} + */ + public Engine getEngine(); + + /** + * Removes the entity. This takes effect at the next snapshot. + */ + public void remove(); + + /** + * True if the entity is removed. + * + * @return removed + */ + public boolean isRemoved(); + + /** + * Sets whether or not the entity should be saved.
+ * + * @param savable True if the entity should be saved, false if not + */ + public void setSavable(boolean savable); + + /** + * Returns true if this entity should be saved. + * + * @return savable + */ + public boolean isSavable(); + + /** + * Gets the {@link Chunk} this entity resides in, or null if removed. + * + * @return {@link Chunk} the entity is in, or null if removed. + */ + public Chunk getChunk(); + + /** + * Gets the region the entity is associated and managed with, or null if removed. + * + * @return {@link Region} the entity is in. + */ + public Region getRegion(); + + /** + * Creates an immutable snapshot of the entity state at the time the method is called + * + * @return immutable snapshot + */ + public EntitySnapshot snapshot(); + + /** + * Gets the {@link ManagedMap} which an Entity always has. + * + * @return {@link ManagedMap} + */ + public ManagedMap getData(); +} diff --git a/src/main/java/org/spout/api/entity/EntityPrefab.java b/src/main/java/org/spout/api/entity/EntityPrefab.java new file mode 100644 index 0000000..e6e21ae --- /dev/null +++ b/src/main/java/org/spout/api/entity/EntityPrefab.java @@ -0,0 +1,46 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.entity; + +import java.util.List; +import java.util.Map; + +import com.flowpowered.commons.Named; + +import org.spout.api.component.Component; +import org.spout.api.geo.discrete.Point; +import org.spout.api.geo.discrete.Transform; + +public interface EntityPrefab extends Named { + public List> getComponents(); + + public Map getData(); + + public Entity createEntity(Point point); + + public Entity createEntity(Transform transform); +} diff --git a/src/main/java/org/spout/api/entity/EntitySnapshot.java b/src/main/java/org/spout/api/entity/EntitySnapshot.java new file mode 100644 index 0000000..4533bce --- /dev/null +++ b/src/main/java/org/spout/api/entity/EntitySnapshot.java @@ -0,0 +1,110 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.entity; + +import java.util.List; +import java.util.UUID; + +import com.flowpowered.commons.datatable.SerializableMap; + +import org.spout.api.component.Component; +import org.spout.api.geo.discrete.Transform; + +/** + * Represents a snapshot of an entity state at a specific UTC timestamp, with immutable values + */ +public interface EntitySnapshot { + /** + * Returns the entity reference, if the entity still exists + * + * @return entity reference if it exists, else null + */ + public Entity getReference(); + + /** + * Gets the id of the entity.

Entity ids' may become invalid if the server has stopped and started. They do not persist across server instances. For persistent ids, use {@link #getUID()}.

+ * + * @return id + */ + public int getId(); + + /** + * Gets the UID for the entity.

This id is persistent across server instances, unique to this entity

+ * + * @return uid + */ + public UUID getUID(); + + /** + * Gets the transform for the entity.

Note: if the world that the entity was in has been unloaded, the world in the transform will be null.

+ * + * @return transform + */ + public Transform getTransform(); + + /** + * Gets the UUID of the world that the entity was in at the time of this snapshot + * + * @return uid + */ + public UUID getWorldUID(); + + /** + * Gets the name of the world that the entity was in at the time of this snapshot + * + * @return world name + */ + public String getWorldName(); + + /** + * Gets a copy of the data map for the entity, created at the time of this snapshot + * + * @return data map + */ + public SerializableMap getDataMap(); + + /** + * Gets the savable flag for the entity at the time of the snapshot + * + * @return savable + */ + public boolean isSavable(); + + /** + * Gets a list of the classes of components attached to this entity + * + * @return entity + */ + public List> getComponents(); + + /** + * Gets the UTC system clock time at the time this snapshot was created

Equivalent to the output of System.currentTimeMillis()

+ * + * @return UTC system time + */ + public long getSnapshotTime(); +} diff --git a/src/main/java/org/spout/api/entity/Player.java b/src/main/java/org/spout/api/entity/Player.java new file mode 100644 index 0000000..9ffe6e5 --- /dev/null +++ b/src/main/java/org/spout/api/entity/Player.java @@ -0,0 +1,142 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.entity; + +import java.util.List; + +import org.spout.flow.commands.CommandSender; + +public interface Player extends CommandSender { + /** + * Gets the player's name. + * + * @return the player's name + */ + public String getName(); + + /** + * Gets the player's display name. + * + * @return the player's display name + */ + public String getDisplayName(); + + /** + * Sets the player's display name. + * + * @param name the player's new display name + */ + public void setDisplayName(String name); + + /** + * Gets if the player is online + * + * @return true if online + */ + public boolean isOnline(); + + /** + * Gets if the player has joined before + * + * @return true if joined before + */ + public boolean hasJoinedBefore(); + + /** + * Kicks the player without giving a reason, or forcing it. + */ + public void kick(); + + /** + * Kicks the player for the given reason. + * + * @param reason the message to send to the player. + */ + public void kick(String reason); + + /** + * Bans the player without giving a reason. + */ + public void ban(); + + /** + * Bans the player for the given reason. + * + * @param kick whether to kick or not + */ + public void ban(boolean kick); + + /** + * Bans the player for the given reason. + * + * @param kick whether to kick or not + * @param reason for ban + */ + public void ban(boolean kick, String reason); + + /** + * Immediately saves the players state to disk + * + * @return true if successful + */ + public boolean save(); + + /** + * If an entity is set as invisible, it will not be sent to the client. + */ + public void setVisible(Entity entity, boolean visible); + + /** + * Retrieves a list of all invisible {@link Entity}'s to the player + * + * @return {@link List<{@link Entity}>} of invisible {@link Entity}'s + */ + public List getInvisibleEntities(); + + /** + * Returns true if the {@link Entity} provided is invisible this this {@link Player} + * + * @param entity Entity to check if invisible to the {@link Player} + * @return true if the {@link Entity} is invisible + */ + public boolean isInvisible(Entity entity); + + /** + * Sends a command to be processed on the opposite Platform. This is basically a shortcut method to prevent the need to register a command locally with a {@link Command.NetworkSendType} of {@code + * SEND}. + * + * @param command to send + * @param args to send + */ + public void sendCommand(String command, String... args); + + /** + * + * @return the entity that this player is controlling + */ + public Entity getEntity(); +} diff --git a/src/main/java/org/spout/api/event/cause/BlockCause.java b/src/main/java/org/spout/api/event/cause/BlockCause.java new file mode 100644 index 0000000..3177eff --- /dev/null +++ b/src/main/java/org/spout/api/event/cause/BlockCause.java @@ -0,0 +1,49 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.event.cause; + +import com.flowpowered.events.Cause; + +import org.spout.api.geo.cuboid.Block; + +public class BlockCause extends Cause { + private final Block block; + + public BlockCause(Block block) { + this(null, block); + } + + public BlockCause(Cause parent, Block block) { + super(parent); + this.block = block; + } + + @Override + public Block getSource() { + return block; + } +} diff --git a/src/main/java/org/spout/api/event/cause/EntityCause.java b/src/main/java/org/spout/api/event/cause/EntityCause.java new file mode 100644 index 0000000..84a69a9 --- /dev/null +++ b/src/main/java/org/spout/api/event/cause/EntityCause.java @@ -0,0 +1,49 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.event.cause; + +import com.flowpowered.events.Cause; + +import org.spout.api.entity.Entity; + +public class EntityCause extends Cause { + private final Entity entity; + + public EntityCause(Entity entity) { + this(null, entity); + } + + public EntityCause(Cause parent, Entity entity) { + super(parent); + this.entity = entity; + } + + @Override + public Entity getSource() { + return entity; + } +} diff --git a/src/main/java/org/spout/api/event/cause/MaterialCause.java b/src/main/java/org/spout/api/event/cause/MaterialCause.java new file mode 100644 index 0000000..47a15e7 --- /dev/null +++ b/src/main/java/org/spout/api/event/cause/MaterialCause.java @@ -0,0 +1,61 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.event.cause; + +import com.flowpowered.events.Cause; + +import org.spout.api.geo.cuboid.Block; +import org.spout.api.material.Material; + +public class MaterialCause extends Cause { + private final T material; + private final Block block; + + public MaterialCause(T material, Block block) { + this(null, material, block); + } + + public MaterialCause(Cause parent, T material, Block block) { + super(parent); + this.material = material; + this.block = block; + } + + @Override + public T getSource() { + return material; + } + + /** + * Gets the block that caused the change + * + * @return block + */ + public Block getBlock() { + return block; + } +} diff --git a/src/main/java/org/spout/api/event/cause/WorldCause.java b/src/main/java/org/spout/api/event/cause/WorldCause.java new file mode 100644 index 0000000..b63a294 --- /dev/null +++ b/src/main/java/org/spout/api/event/cause/WorldCause.java @@ -0,0 +1,49 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.event.cause; + +import com.flowpowered.events.Cause; + +import org.spout.api.geo.World; + +public class WorldCause extends Cause { + private final World world; + + public WorldCause(World world) { + this.world = world; + } + + public WorldCause(Cause parent, World world) { + super(parent); + this.world = world; + } + + @Override + public World getSource() { + return world; + } +} diff --git a/src/main/java/org/spout/api/generator/EmptyWorldGenerator.java b/src/main/java/org/spout/api/generator/EmptyWorldGenerator.java new file mode 100644 index 0000000..74d41bf --- /dev/null +++ b/src/main/java/org/spout/api/generator/EmptyWorldGenerator.java @@ -0,0 +1,52 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.generator; + +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.material.BlockMaterial; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; + +/** + * Generates an empty world using air blocks + */ +public class EmptyWorldGenerator implements WorldGenerator { + @Override + public void generate(CuboidBlockMaterialBuffer blockData, World world) { + blockData.flood(BlockMaterial.AIR); + } + + @Override + public Populator[] getPopulators() { + return new Populator[0]; + } + + @Override + public String getName() { + return "EmptyWorld"; + } +} diff --git a/src/main/java/org/spout/api/generator/FlatWorldGenerator.java b/src/main/java/org/spout/api/generator/FlatWorldGenerator.java new file mode 100644 index 0000000..f3ddbd4 --- /dev/null +++ b/src/main/java/org/spout/api/generator/FlatWorldGenerator.java @@ -0,0 +1,64 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.generator; + +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.material.BlockMaterial; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; + +/** + * Generates a flat world of a Spout-colored material + */ +public class FlatWorldGenerator implements WorldGenerator { + private final BlockMaterial material; + + public FlatWorldGenerator() { + material = BlockMaterial.SOLID_BLUE; + } + + public FlatWorldGenerator(BlockMaterial material) { + this.material = material; + } + + @Override + public void generate(CuboidBlockMaterialBuffer blockData, World world) { + if (blockData.getBase().getY() < 0) { + blockData.flood(material); + } + } + + @Override + public Populator[] getPopulators() { + return new Populator[0]; + } + + @Override + public String getName() { + return "FlatWorld"; + } +} diff --git a/src/main/java/org/spout/api/generator/GeneratorPopulator.java b/src/main/java/org/spout/api/generator/GeneratorPopulator.java new file mode 100644 index 0000000..d5d12d7 --- /dev/null +++ b/src/main/java/org/spout/api/generator/GeneratorPopulator.java @@ -0,0 +1,36 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.generator; + +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; + +/** + * Represents a populator for a generator which should operate on a material buffer. + */ +public interface GeneratorPopulator { + public void populate(CuboidBlockMaterialBuffer blockData, int x, int y, int z, long seed); +} diff --git a/src/main/java/org/spout/api/generator/LayeredWorldGenerator.java b/src/main/java/org/spout/api/generator/LayeredWorldGenerator.java new file mode 100644 index 0000000..71caeac --- /dev/null +++ b/src/main/java/org/spout/api/generator/LayeredWorldGenerator.java @@ -0,0 +1,194 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.generator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.material.BlockMaterial; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; + +/** + * A world generator that generates using previously-specified layers of blocks + */ +public class LayeredWorldGenerator implements WorldGenerator { + private List layers = new ArrayList<>(); + private int minimum = Integer.MAX_VALUE; + private int height = Integer.MIN_VALUE; + private short floorid = 0, floordata = 0; + + @Override + public void generate(CuboidBlockMaterialBuffer blockData, World world) { + final int startY = blockData.getBase().getFloorY(); + final int endY = blockData.getTop().getFloorY(); + int y, height; + for (Layer layer : this.layers) { + if (layer.getTop() > startY && layer.getY() < endY) { + y = Math.max(startY, layer.getY()); + height = Math.min(endY, layer.getTop()) - y; + blockData.setHorizontalLayer(y, height, layer.getId(), layer.getData()); + } + } + // Floor layer + if (startY < this.minimum) { + height = Math.min(endY, this.minimum) - startY; + blockData.setHorizontalLayer(startY, height, this.floorid, this.floordata); + } + } + + @Override + public Populator[] getPopulators() { + return new Populator[0]; + } + + @Override + public String getName() { + return "LayeredWorld"; + } + + /** + * Gets the total height of all layers + * + * @return Layer height + */ + public int getHeight() { + return this.height; + } + + /** + * Gets an immutable list of layers specified for this layered World Generator + * + * @return List of layers + */ + public List getLayers() { + return Collections.unmodifiableList(this.layers); + } + + /** + * Sets the floor layer material, the material for below the lowest layer
By default this layer is full of empty material (air) + * + * @param material of the layer + */ + protected void setFloorLayer(BlockMaterial material) { + this.setFloorLayer(material.getId(), material.getData()); + } + + /** + * Sets the floor layer material, the material for below the lowest layer
By default this layer is full of empty material (air) + * + * @param id of the material of the layer + * @param data of the layer + */ + protected void setFloorLayer(short id, short data) { + this.floorid = id; + this.floordata = data; + } + + /** + * Stacks a new layer on top of a previous one
At least one layer added using addLayer should be defined before calling this method
Otherwise the y-coordinate of this layer will be incorrect + * + * @param height of the new layer + * @param material of the layer + */ + protected void stackLayer(int height, BlockMaterial material) { + this.addLayer(this.height, height, material); + } + + /** + * Stacks a new layer on top of a previous one
At least one layer added using addLayer should be defined before calling this method
Otherwise the y-coordinate of this layer will be incorrect + * + * @param height of the new layer + * @param id of the material of the layer + * @param data of the layer + */ + protected void stackLayer(int height, short id, short data) { + this.addLayer(this.height, height, id, data); + } + + /** + * Adds a single layer + * + * @param y - coordinate of the start of the layer + * @param height of the layer + * @param material of the layer + */ + protected void addLayer(int y, int height, BlockMaterial material) { + this.addLayer(y, height, material.getId(), material.getData()); + } + + /** + * Adds a single layer + * + * @param y - coordinate of the start of the layer + * @param height of the layer + * @param id of the material of the layer + * @param data of the layer + */ + protected void addLayer(int y, int height, short id, short data) { + final Layer layer = new Layer(y, height, id, data); + this.layers.add(layer); + this.height = Math.max(this.height, layer.getTop()); + this.minimum = Math.min(this.minimum, layer.getY()); + } + + public static class Layer { + private final short id, data; + private final int y, height, topy; + + public Layer(int y, int height, short id, short data) { + this.y = y; + this.height = height; + this.topy = y + height; + this.id = id; + this.data = data; + } + + public int getY() { + return y; + } + + public int getHeight() { + return height; + } + + public int getTop() { + return topy; + } + + public short getId() { + return id; + } + + public short getData() { + return data; + } + } +} diff --git a/src/main/java/org/spout/api/generator/Populator.java b/src/main/java/org/spout/api/generator/Populator.java new file mode 100644 index 0000000..63d7c05 --- /dev/null +++ b/src/main/java/org/spout/api/generator/Populator.java @@ -0,0 +1,66 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.generator; + +import java.util.Random; + +import org.spout.api.geo.cuboid.Chunk; + +/** + * Represents a populator for a chunk + */ +public abstract class Populator { + private boolean needsClearance; + + public Populator() { + this(false); + } + + public Populator(boolean needsClearance) { + this.needsClearance = needsClearance; + } + + public boolean needsClearance() { + return needsClearance; + } + + /** + * Populates the chunk. + * + * This method may make full use of the block modifying methods of the API. + * + * This method will be called once per chunk and it is guaranteed that a 2x2x2 cube of chunks containing the chunk will be loaded. + * + * The chunk to populate is the chunk with the lowest x, y and z coordinates of the cube. + * + * This allows the populator to create features that cross chunk boundaries. + * + * @param chunk the chunk to populate + * @param random The RNG for this chunk + */ + public abstract void populate(Chunk chunk, Random random); +} diff --git a/src/main/java/org/spout/api/generator/WorldGenerator.java b/src/main/java/org/spout/api/generator/WorldGenerator.java new file mode 100644 index 0000000..e2083e4 --- /dev/null +++ b/src/main/java/org/spout/api/generator/WorldGenerator.java @@ -0,0 +1,65 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.generator; + +import com.flowpowered.commons.Named; + +import org.spout.api.geo.World; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; + +/** + * Represents a World generator. + * + * WorldGenerators are used to generate {@link World}s (surprise surprise) + */ +public interface WorldGenerator extends Named { + /** + * Gets the block structure for a Chunk. + * + * The CuboidBuffer will always be chunk-aligned, and could be of a variable (chunk) size.

Use {@link CuboidBlockMaterialBuffer#getBase()} and {@link CuboidBlockMaterialBuffer#getTop()} to + * obtain the Block bounds in which can be generated. + * + * It is recommended that seeded random number generators from WorldGeneratorUtils are used. + * + * @param blockData a zeroed CuboidBuffer which has to be fully generated + * @param world in which is generated + */ + public void generate(CuboidBlockMaterialBuffer blockData, World world); + + /** + * Gets an array of Populators for the world generator + * + * @return the Populator array + */ + public Populator[] getPopulators(); + + /** + * Gets the name of the generator. This name should be unique to prevent two generators overwriting the same world + */ + @Override + public String getName(); +} diff --git a/src/main/java/org/spout/api/generator/WorldGeneratorObject.java b/src/main/java/org/spout/api/generator/WorldGeneratorObject.java new file mode 100644 index 0000000..5b2066e --- /dev/null +++ b/src/main/java/org/spout/api/generator/WorldGeneratorObject.java @@ -0,0 +1,72 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.generator; + +import org.spout.api.geo.World; + +/** + * Represents an Object for a WorldGenerator + */ +public abstract class WorldGeneratorObject { + /** + * Verify if the object can be placed at the given coordinates. + * + * @param w The world w. + * @param x The x coordinate. + * @param y The y coordinate. + * @param z The z coordinate. + * @return true if the object can be placed, false if it can't. + */ + public abstract boolean canPlaceObject(World w, int x, int y, int z); + + /** + * Place this object into the world at the given coordinates. + * + * @param w The world w. + * @param x The x coordinate. + * @param y The y coordinate. + * @param z The z coordinate. + */ + public abstract void placeObject(World w, int x, int y, int z); + + /** + * Attempts placement of this object into the world at the given coordinates. The attempt will fail if {@link #canPlaceObject(org.spout.api.geo.World, int, int, int)} returns false. + * + * @param w The world w. + * @param x The x coordinate. + * @param y The y coordinate. + * @param z The z coordinate. + * @return True if the object was placed, false if otherwise. + */ + public boolean tryPlaceObject(World w, int x, int y, int z) { + if (canPlaceObject(w, x, y, z)) { + placeObject(w, x, y, z); + return true; + } + return false; + } +} diff --git a/src/main/java/org/spout/api/geo/AreaBlockAccess.java b/src/main/java/org/spout/api/geo/AreaBlockAccess.java new file mode 100644 index 0000000..413cdc1 --- /dev/null +++ b/src/main/java/org/spout/api/geo/AreaBlockAccess.java @@ -0,0 +1,275 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +import com.flowpowered.events.Cause; + +import org.spout.api.geo.cuboid.Block; +import org.spout.api.material.BlockMaterial; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; +import org.spout.math.vector.Vector3f; + +public interface AreaBlockAccess extends AreaBlockSource { + /** + * Sets the data for the block at (x, y, z) to the given data. + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param data to set to + * @param cause of the change, or null if non-specific cause + */ + public boolean setBlockData(int x, int y, int z, short data, Cause cause); + + /** + * Adds a value to the data for the block at (x, y, z) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param data to add + * @param cause of the change, or null if non-specific cause + */ + public boolean addBlockData(int x, int y, int z, short data, Cause cause); + + /** + * Sets the material and data for the block at (x, y, z) to the given material and data. + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param data value to set to + * @param material to set to + * @param cause of the change, or null if non-specific cause + */ + public boolean setBlockMaterial(int x, int y, int z, BlockMaterial material, short data, Cause cause); + + /** + * Sets the data of the block at (x, y, z) if the expected state matches + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param expect is the state of the block it expects + * @param data to set to if it matches + * @param cause of the change, or null if non-specific cause + * @return whether setting was successful + */ + public boolean compareAndSetData(int x, int y, int z, int expect, short data, Cause cause); + + /** + * Sets the given bits in the data for the block at (x, y, z)

newData = oldData | (bits) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param bits the bits to set + * @return the old data for the block + */ + public short setBlockDataBits(int x, int y, int z, int bits, Cause cause); + + /** + * Sets the given bits in the data for the block at (x, y, z)

newData = oldData | (bits)
or
newData = oldData & ~(bits) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param bits the bits to set or clear + * @param set true to set, false to clear + * @return the old data for the block + */ + public short setBlockDataBits(int x, int y, int z, int bits, boolean set, Cause source); + + /** + * Clears the given bits in the data for the block at (x, y, z)

newData = oldData & (~bits) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param bits the bits to clear + * @param cause of the change, or null if non-specific cause + * @return the old data for the block + */ + public short clearBlockDataBits(int x, int y, int z, int bits, Cause source); + + /** + * Gets the data field from the block at (x, y, z)

field = (data & bits) >> (shift)

The shift value used shifts the least significant non-zero bit of bits to the LSB position + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param bits the bits of the field + * @return the field value + */ + public int getBlockDataField(int x, int y, int z, int bits); + + /** + * Gets if any of the indicated bits are set. + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param bits the bits of the field + * @return true if any of the given bits are set + */ + public boolean isBlockDataBitSet(int x, int y, int z, int bits); + + /** + * Sets the data field for the block at (x, y, z). This is the reverse operation to the getBlockDataField method.

newData = ((value << shift) & bits) | (oldData & (~bits))

The + * shift value used shifts the least significant non-zero bit of bits to the LSB position + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param bits the bits of the field + * @param value the new value of the field + * @param cause of the change, or null if non-specific cause + * @return the old value of the field + */ + public int setBlockDataField(int x, int y, int z, int bits, int value, Cause source); + + /** + * Adds a value to the data field for the block at (x, y, z). This is the reverse operation to the getBlockDataField method.

newData = (((oldData + (value << shift)) & bits) | (oldData & + * ~bits))

The shift value used shifts the least significant non-zero bit of bits to the LSB position + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param bits the bits of the field + * @param value to add to the value of the field + * @return the old value of the field + */ + public int addBlockDataField(int x, int y, int z, int bits, int value, Cause source); + + /** + * Gets if a block is contained in this area + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @return true if it is contained, false if not + */ + public boolean containsBlock(int x, int y, int z); + + /** + * Gets a {@link Block} representing the block at (x, y, z) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @return the Block + */ + public Block getBlock(int x, int y, int z); + + /** + * Gets a {@link Block} representing the block at (x, y, z) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @return the Block + */ + public Block getBlock(float x, float y, float z); + + /** + * Gets a {@link Block} representing the block at the position given + * + * @param position of the block + * @return the Block + */ + public Block getBlock(Vector3f position); + + /** + * Atomically sets the cuboid volume to the values inside of the cuboid buffer, if the contents of the buffer's backbuffer matches the world. + * + * @param cause that is setting the cuboid volume + */ + public boolean commitCuboid(CuboidBlockMaterialBuffer buffer, Cause cause); + + /** + * Atomically sets the cuboid volume to the values inside of the cuboid buffer. + * + * @param cause that is setting the cuboid volume + */ + public void setCuboid(CuboidBlockMaterialBuffer buffer, Cause cause); + + /** + * Atomically sets the cuboid volume to the values inside of the cuboid buffer with the base located at the given coords + * + * @param cause that is setting the cuboid volume + */ + public void setCuboid(int x, int y, int z, CuboidBlockMaterialBuffer buffer, Cause cause); + + /** + * Atomically gets the cuboid volume + * + * @param backBuffer true for a buffer with a back buffer + */ + public CuboidBlockMaterialBuffer getCuboid(boolean backBuffer); + + /** + * Atomically gets the cuboid volume with the base located at the given coords of the given size.
The buffer returned contains a back buffer
Note: The block at the base coordinate is inside + * the buffer + * + * @param bx base x-coordinate + * @param by base y-coordinate + * @param bz base z-coordinate + * @param sx size x-coordinate + * @param sy size y-coordinate + * @param sz size z-coordinate + */ + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz); + + /** + * Atomically gets the cuboid volume with the base located at the given coords of the given size.

Note: The block at the base coordinate is inside the buffer + * + * @param bx base x-coordinate + * @param by base y-coordinate + * @param bz base z-coordinate + * @param sx size x-coordinate + * @param sy size y-coordinate + * @param sz size z-coordinate + * @param backBuffer true for a buffer with a back buffer + */ + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz, boolean backBuffer); + + /** + * Atomically gets the cuboid volume with the base located at the given coords and the size of the given buffer.

Note: The block at the base coordinate is inside the + * + * @param bx base x-coordinate + * @param by base y-coordinate + * @param bz base z-coordinate + */ + public void getCuboid(int bx, int by, int bz, CuboidBlockMaterialBuffer buffer); + + /** + * Atomically gets the cuboid volume contained within the given buffer + * + * @param buffer the buffer + */ + public void getCuboid(CuboidBlockMaterialBuffer buffer); +} diff --git a/src/main/java/org/spout/api/geo/AreaBlockSource.java b/src/main/java/org/spout/api/geo/AreaBlockSource.java new file mode 100644 index 0000000..2e1145e --- /dev/null +++ b/src/main/java/org/spout/api/geo/AreaBlockSource.java @@ -0,0 +1,61 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +import org.spout.api.material.BlockMaterial; + +public interface AreaBlockSource { + /** + * Gets the material for the block at (x, y, z) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @return the block's material from the snapshot + */ + public BlockMaterial getBlockMaterial(int x, int y, int z); + + /** + * Gets the packed BlockFullData for the block at (x, y, z). Handler methods are provided by the BlockFullState class. + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @return the block's full state from the snapshot + */ + public int getBlockFullState(int x, int y, int z); + + /** + * Gets the data for the block at (x, y, z) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @return the block's data from the snapshot + */ + public short getBlockData(int x, int y, int z); +} \ No newline at end of file diff --git a/src/main/java/org/spout/api/geo/AreaChunkAccess.java b/src/main/java/org/spout/api/geo/AreaChunkAccess.java new file mode 100644 index 0000000..eb10e68 --- /dev/null +++ b/src/main/java/org/spout/api/geo/AreaChunkAccess.java @@ -0,0 +1,120 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +import java.util.List; + +import org.spout.api.geo.cuboid.Chunk; +import org.spout.math.vector.Vector3f; + +public interface AreaChunkAccess extends AreaBlockAccess { + /** + * Gets the {@link Chunk} at chunk coordinates (x, y, z) + * + * @param x coordinate of the chunk + * @param y coordinate of the chunk + * @param z coordinate of the chunk + * @param loadopt to control whether to load and/or generate the chunk, if needed + * @return the chunk + */ + public Chunk getChunk(int x, int y, int z, LoadOption loadopt); + + /** + * Gets if a chunk is contained in this area + * + * @param x coordinate of the chunk + * @param y coordinate of the chunk + * @param z coordinate of the chunk + * @return True if it is contained, False if not + */ + public boolean containsChunk(int x, int y, int z); + + /** + * Gets the {@link Chunk} at block coordinates (x, y, z) + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @param loadopt to control whether to load and/or generate the chunk, if needed + * @return the chunk + */ + public Chunk getChunkFromBlock(int x, int y, int z, LoadOption loadopt); + + /** + * Gets the {@link Chunk} at the given position + * + * @param position of the block + * @param loadopt to control whether to load and/or generate the chunk, if needed + * @return the chunk + */ + public Chunk getChunkFromBlock(Vector3f position, LoadOption loadopt); + + /** + * True if the region has a loaded chunk at the (x, y, z). + * + * @param x coordinate of the chunk + * @param y coordinate of the chunk + * @param z coordinate of the chunk + * @return true if chunk exists + */ + public boolean hasChunk(int x, int y, int z); + + /** + * True if the region has a loaded chunk at the block coordinates (x, y, z). + * + * @param x coordinate of the block + * @param y coordinate of the block + * @param z coordinate of the block + * @return true if chunk exists + */ + public boolean hasChunkAtBlock(int x, int y, int z); + + /** + * Queues a chunk for saving at the next available opportunity. + * + * @param x coordinate of the chunk + * @param y coordinate of the chunk + * @param z coordinate of the chunk + */ + public void saveChunk(int x, int y, int z); + + /** + * Unloads a chunk, and queues it for saving, if requested. + * + * @param x coordinate of the chunk + * @param y coordinate of the chunk + * @param z coordinate of the chunk + */ + public void unloadChunk(int x, int y, int z, boolean save); + + /** + * Gets the number of currently loaded chunks + * + * @return number of loaded chunks + */ + public int getNumLoadedChunks(); +} diff --git a/src/main/java/org/spout/api/geo/AreaRegionAccess.java b/src/main/java/org/spout/api/geo/AreaRegionAccess.java new file mode 100644 index 0000000..a0bd931 --- /dev/null +++ b/src/main/java/org/spout/api/geo/AreaRegionAccess.java @@ -0,0 +1,121 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +import java.util.Collection; + +import org.spout.api.geo.cuboid.Region; +import org.spout.math.vector.Vector3f; + +public interface AreaRegionAccess extends AreaChunkAccess { + /** + * Gets an unmodifiable collection of all loaded regions + * + * @return all loaded regions + */ + public Collection getRegions(); + + /** + * Gets the {@link Region} at region coordinates (x, y, z) + * + * @param x the region x coordinate + * @param y the region y coordinate + * @param z the region z coordinate + * @return the region + */ + public Region getRegion(int x, int y, int z); + + /** + * Gets the {@link Region} at region coordinates (x, y, z) + * + * @param x the region x coordinate + * @param y the region y coordinate + * @param z the region z coordinate + * @param loadopt to control whether to load and/or generate the region, if needed + * @return the region + */ + public Region getRegion(int x, int y, int z, LoadOption loadopt); + + /** + * Gets the {@link Region} at chunk coordinates (x, y, z) + * + * @param x the chunk x coordinate + * @param y the chunk y coordinate + * @param z the chunk z coordinate + * @return the region + */ + public Region getRegionFromChunk(int x, int y, int z); + + /** + * Gets the {@link Region} at chunk coordinates (x, y, z) + * + * @param x the chunk x coordinate + * @param y the chunk y coordinate + * @param z the chunk z coordinate + * @param loadopt to control whether to load and/or generate the region, if needed + * @return the region + */ + public Region getRegionFromChunk(int x, int y, int z, LoadOption loadopt); + + /** + * Gets the {@link Region} at block coordinates (x, y, z) + * + * @param x the block x coordinate + * @param y the block y coordinate + * @param z the block z coordinate + * @return the region + */ + public Region getRegionFromBlock(int x, int y, int z); + + /** + * Gets the {@link Region} at block coordinates (x, y, z) + * + * @param x the block x coordinate + * @param y the block y coordinate + * @param z the block z coordinate + * @param loadopt to control whether to load and/or generate the region, if needed + * @return the region + */ + public Region getRegionFromBlock(int x, int y, int z, LoadOption loadopt); + + /** + * Gets the {@link Region} at block coordinates (x, y, z) + * + * @param position of the block + * @return the region + */ + public Region getRegionFromBlock(Vector3f position); + + /** + * Gets the {@link Region} at block coordinates (x, y, z) + * + * @param position of the block + * @param loadopt to control whether to load and/or generate the region, if needed + * @return the region + */ + public Region getRegionFromBlock(Vector3f position, LoadOption loadopt); +} diff --git a/src/main/java/org/spout/api/geo/LoadOption.java b/src/main/java/org/spout/api/geo/LoadOption.java new file mode 100644 index 0000000..27d5b87 --- /dev/null +++ b/src/main/java/org/spout/api/geo/LoadOption.java @@ -0,0 +1,79 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +public class LoadOption { + /** + * Do not load or generate chunk/region if not currently loaded + */ + public static final LoadOption NO_LOAD = new LoadOption(false, false, true); + /** + * Load chunk/region if not currently loaded, but do not generate it if it does not yet exist + */ + public static final LoadOption LOAD_ONLY = new LoadOption(true, false, true); + /** + * Load chunk/region if not currently loaded, and generate it if it does not yet exist + */ + public static final LoadOption LOAD_GEN = new LoadOption(true, true, true); + /** + * Don't load the chunk if it has already been generated, only generate if it does not yet exist + */ + public static final LoadOption GEN_ONLY = new LoadOption(false, true, true); + + private final boolean load; + private final boolean generate; + private final boolean wait; + + private LoadOption(boolean load, boolean generate, boolean wait) { + this.load = load; + this.generate = generate; + this.wait = wait; + } + + /** + * Test if chunk/region should be loaded if not currently loaded + * + * @return true if yes, false if no + */ + public final boolean loadIfNeeded() { + return load; + } + + /** + * Test if chunk/region should be generated if it does not exist + * + * @return true if yes, false if no + */ + public final boolean generateIfNeeded() { + return generate; + } + + public boolean isWait() { + return wait; + } + +} diff --git a/src/main/java/org/spout/api/geo/LocalAreaAccess.java b/src/main/java/org/spout/api/geo/LocalAreaAccess.java new file mode 100644 index 0000000..f510753 --- /dev/null +++ b/src/main/java/org/spout/api/geo/LocalAreaAccess.java @@ -0,0 +1,66 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.geo.cuboid.Region; +import org.spout.api.material.block.BlockFace; + +public interface LocalAreaAccess { + /** + * Gets a neighbouring region. Only the 3x3x3 cube of regions centered on this region can be obtained by this method. + */ + public Region getLocalRegion(BlockFace face, LoadOption loadopt); + + /** + * Gets a neighbouring region. The coordinates provided range from 0 to 2, rather than -1 to +1. If all 3 coordinates are 1, then this region is returned. + */ + public Region getLocalRegion(int dx, int dy, int dz, LoadOption loadopt); + + /** + * Gets a chunk relative to a given chunk. The given chunk must be in this region and the requested chunk must be in the 3x3x3 cube of regions centred on this region.
+ */ + public Chunk getLocalChunk(Chunk c, BlockFace face, LoadOption loadopt); + + /** + * Gets a chunk relative to a given chunk. The given chunk must be in this region and the requested chunk must be in the 3x3x3 cube of regions centred on this region.

(ox, oy, oz) is the + * offset to the desired chunk. The coordinates of the offset can not have a magnitude greater than 16. + */ + public Chunk getLocalChunk(Chunk c, int ox, int oy, int oz, LoadOption loadopt); + + /** + * Gets a chunk relative to given chunk coordinates. The given chunk must be in this region and the requested chunk must be in the 3x3x3 cube of regions centred on this region.

(x, y, z) + * are the coordinates of a chunk in this region.

(ox, oy, oz) is the offset to the desired chunk. The coordinates of the offset can not have a magnitude greater than 16. + */ + public Chunk getLocalChunk(int x, int y, int z, int ox, int oy, int oz, LoadOption loadopt); + + /** + * Gets a chunk in the 3x3x3 cube of regions centered on this region.

The valid range for the (x, y, z) coordinates is -16 to 31.

To request a chunk in this region, all three + * coordinates must be in the range of 0 to 15. + */ + public Chunk getLocalChunk(int x, int y, int z, LoadOption loadopt); +} diff --git a/src/main/java/org/spout/api/geo/ServerWorld.java b/src/main/java/org/spout/api/geo/ServerWorld.java new file mode 100644 index 0000000..550c5b2 --- /dev/null +++ b/src/main/java/org/spout/api/geo/ServerWorld.java @@ -0,0 +1,66 @@ +package org.spout.api.geo; + +import java.io.File; +import java.util.List; +import org.spout.api.generator.WorldGenerator; +import org.spout.api.geo.discrete.Transform; +import org.spout.math.vector.Vector3f; + +public interface ServerWorld extends World { + + /** + * Gets the {@link WorldGenerator} responsible for generating new chunks for this world + * + * @return generator + */ + WorldGenerator getGenerator(); + + /** + * Gets the world's seed. This value is immutable and set at world creation + * + * @return the seed + */ + long getSeed(); + + // Techinically server-only + /** + * Gets the world's spawn point + * + * @return the spawn point + */ + Transform getSpawnPoint(); + + /** + * Sets the world's spawn point + * + * @param transform the Transform of the spawn point + */ + void setSpawnPoint(Transform transform); + + /** + * Unloads the world from the server. Undefined behavior will occur if any players are currently alive on the world while it is being unloaded. + */ + void unload(boolean save); + + /** + * Saves all world data to world data file.

Note: World data does not include chunks, regions, or other data. World data pertains to world age, world name, and world data maps.

+ */ + public void save(); + + public File getDirectory(); + + /** + * Queues a list of chunks for generation. The Vector3 values are in chunk coords. + * + * @param chunks a list of chunk coordinates + */ + public void queueChunksForGeneration(List chunks); + + /** + * Queues a chunk for generation. The Vector3 value is in chunk coords. + * + * @param chunk chunk coordinates + */ + public void queueChunkForGeneration(Vector3f chunk); + +} diff --git a/src/main/java/org/spout/api/geo/ServerWorldManager.java b/src/main/java/org/spout/api/geo/ServerWorldManager.java new file mode 100644 index 0000000..c07deb2 --- /dev/null +++ b/src/main/java/org/spout/api/geo/ServerWorldManager.java @@ -0,0 +1,75 @@ +package org.spout.api.geo; + +import java.io.File; +import java.util.Collection; +import java.util.List; + +import org.spout.api.generator.WorldGenerator; + +public interface ServerWorldManager extends WorldManager { + + /** + * Gets the default world generator for this game. Specific generators can be specified when loading new worlds. + * + * @return default world generator. + */ + public WorldGenerator getDefaultGenerator(); + + /** + * Loads a {@link World} with the given name and {@link WorldGenerator}
If the world doesn't exist on disk, it creates it.

if the world is already loaded, this functions the same as + * {@link #getWorld(String)} + * + * @param name Name of the world + * @param generator World Generator + * @return {@link World} loaded or created. + */ + public World loadWorld(String name, WorldGenerator generator); + + /** + * Unloads this world from memory.

Note: Worlds can not be unloaded if players are currently on them. + * + * @param name of the world to unload + * @param save whether or not to save the world state to file + * @return true if the world was unloaded, false if not + */ + public boolean unloadWorld(String name, boolean save); + + /** + * Unloads this world from memory.

Note: Worlds can not be unloaded if players are currently on them. + * + * @param world to unload + * @param save whether or not to save the world state to file + * @return true if the world was unloaded, false if not + */ + public boolean unloadWorld(ServerWorld world, boolean save); + + /** + * Initiates a save of the server state, including configuration files.

It will save the state of the world, if specificed, and the state of players, if specified. + * + * @param worlds true to save the state of all active worlds + * @param players true to save the state of all active players + */ + public void save(boolean worlds, boolean players); + + /** + * Gets the world folders which match the world name. + * + * @param worldName to match the world folders with + * @return the world folders that match the world name + */ + public Collection matchWorldFolder(String worldName); + + /** + * Gets all the individual world folders where world data is stored.

This includes offline worlds. + * + * @return a list of available world folders + */ + public List getWorldFolders(); + + /** + * Gets the folder that contains the world save data.

If the folder is unusued, the file path will be '.' + * + * @return world folder + */ + public File getWorldFolder(); +} diff --git a/src/main/java/org/spout/api/geo/World.java b/src/main/java/org/spout/api/geo/World.java new file mode 100644 index 0000000..0656f86 --- /dev/null +++ b/src/main/java/org/spout/api/geo/World.java @@ -0,0 +1,352 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +import java.io.File; +import java.util.List; +import java.util.UUID; + +import com.flowpowered.commons.Named; +import com.flowpowered.commons.datatable.ManagedMap; +import com.flowpowered.events.Cause; + +import org.spout.api.Engine; +import org.spout.api.component.Component; +import org.spout.api.component.ComponentOwner; +import org.spout.api.entity.Entity; +import org.spout.api.entity.EntityPrefab; +import org.spout.api.entity.Player; +import org.spout.api.generator.WorldGenerator; +import org.spout.api.geo.discrete.Point; +import org.spout.api.geo.discrete.Transform; +import org.spout.api.scheduler.TaskManager; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; + +/** + * Represents a World. + */ +public interface World extends AreaRegionAccess, Named, ComponentOwner { + /** + * Gets the name of the world + * + * @return the name of the world + */ + @Override + public String getName(); + + /** + * Gets the age of the world in ms. This count cannot be modified, and increments on every tick. + * + * @return the world's age in ms + */ + public long getAge(); + + /** + * Gets the UID representing the world. With extremely high probability the UID is unique to each world. + * + * @return the name of the world + */ + public UUID getUID(); + + /** + * Gets the entity with the matching unique id

Performs a search on each region for the entity, stopping when it is found, or after all the worlds have been searched upon failure. + * + * @param uid to search and match + * @return entity that matched the uid, or null if none was found + */ + public Entity getEntity(UUID uid); + + /** + * Creates a new {@link Entity} at the {@link Point} with the {@link Component} classes attached. + * + * @param point The area in space where spawn will occur + * @param classes The classes to attach + * @return The entity set to spawn at the point provided with components attached + */ + public Entity createEntity(Point point, Class... classes); + + /** + * Creates a new {@link Entity} at the {@link Point} blueprinted with the {@link EntityPrefab} provided. + * + * @param point The area in space where spawn will occur + * @param prefab The blueprint + * @return The entity set to spawn at the point provided with the prefab applied + */ + public Entity createEntity(Point point, EntityPrefab prefab); + + /** + * Spawns the {@link Entity}. + * + * @param e Entity to spawn + */ + public void spawnEntity(Entity e); + + /** + * Creates and spawns an {@link Entity} at the {@link Point} blueprinted with the {@link EntityPrefab} provided.

The {@link LoadOption} parameter is used to tell Spout if it should load, create + * and load, or not load the chunk for the point provided. Great caution should be used; only load (and more so create) if absolutely necessary. + * + * @param point The area in space to spawn + * @param option Whether to not load, load, or load and create the chunk + * @param prefab The blueprint + * @return The spawned entity at the point with the prefab applied + */ + public Entity createAndSpawnEntity(Point point, LoadOption option, EntityPrefab prefab); + + /** + * Creates and spawns an {@link Entity} at the {@link Point} with the {@link Component} classes attached.

The {@link LoadOption} parameter is used to tell Spout if it should load, create and + * load, or not load the chunk for the point provided. Great caution should be used; only load (and more so create) if absolutely necessary. + * + * @param point The area in space to spawn + * @param option Whether to not load, load, or load and create the chunk + * @param classes The classes to attach + * @return The spawned entity at the point with the components attached + */ + public Entity createAndSpawnEntity(Point point, LoadOption option, Class... classes); + + /** + * Creates and spawns multiple {@link Entity} at the {@link Point}s with the {@link Component} classes attached.

The {@link LoadOption} parameter is used to tell Spout if it should load, create + * and load, or not load the chunk for the points provided. Great caution should be used; only load (and more so create) if absolutely necessary. + * + * @param points The areas in space to spawn + * @param option Whether to not load, load, or load and create the chunk + * @param classes The classes to attach + * @return The spawned entities at the points with the components attached + */ + public Entity[] createAndSpawnEntity(Point[] points, LoadOption option, Class... classes); + + /** + * Gets the engine associated with this world + * + * @return the engine + */ + public Engine getEngine(); + + /** + * Gets all entities with the specified type. + * + * @return A collection of entities with the specified type. + */ + public List getAll(); + + /** + * Gets an entity by its id. + * + * @param id The id. + * @return The entity, or {@code null} if it could not be found. + */ + public Entity getEntity(int id); + + /** + * Gets the task manager responsible for parallel region tasks.

All tasks are submitted to all loaded regions at the start of the next tick.

Repeating tasks are also submitted to + * all new regions when they are created.
Repeated tasks are NOT guaranteed to happen in the same tick for all regions, as each task is submitted individually to each region.

This task + * manager does not support async tasks.
If the Runnable for the task is a ParallelRunnable, then a new instance of the Runnable will be created for each region. + * + * @return the parallel task manager for the engine + */ + public TaskManager getParallelTaskManager(); + + /** + * Gets the TaskManager associated with this world + * + * @return task manager + */ + public abstract TaskManager getTaskManager(); + + /** + * Gets a list of nearby entities of the point, inside of the range + * + * @param position of the center + * @param ignore Entity to ignore + * @param range to look for + * @return the list of nearby entities (or empty if none) + */ + public List getNearbyEntities(Point position, Entity ignore, int range); + + /** + * Gets a set of nearby players to the point, inside of the range + * + * @param position of the center + * @param range to look for + * @return A set of nearby Players + */ + public List getNearbyEntities(Point position, int range); + + /** + * Gets a set of nearby players to the entity, inside of the range + * + * @param entity marking the center and which is ignored + * @param range to look for + * @return A set of nearby Players + */ + public List getNearbyEntities(Entity entity, int range); + + /** + * Gets the absolute closest player from the specified point within a specified range. + * + * @param position to search from + * @param ignore to ignore while searching + * @param range to search + * @return nearest player + */ + public Entity getNearestEntity(Point position, Entity ignore, int range); + + /** + * Gets the absolute closest player from the specified point within a specified range. + * + * @param position center of search + * @param range to search + * @return nearest player + */ + public Entity getNearestEntity(Point position, int range); + + /** + * Gets the absolute closest player from the specified point within a specified range. + * + * @param entity to search from + * @param range to search + * @return nearest player + */ + public Entity getNearestEntity(Entity entity, int range); + + /** + * Gets a set of nearby players to the point, inside of the range. The search will ignore the specified entity. + * + * @param position of the center + * @param ignore Entity to ignore + * @param range to look for + * @return A set of nearby Players + */ + public List getNearbyPlayers(Point position, Player ignore, int range); + + /** + * Gets a set of nearby players to the point, inside of the range + * + * @param position of the center + * @param range to look for + * @return A set of nearby Players + */ + public List getNearbyPlayers(Point position, int range); + + /** + * Gets a set of nearby players to the entity, inside of the range + * + * @param entity marking the center and which is ignored + * @param range to look for + * @return A set of nearby Players + */ + public List getNearbyPlayers(Entity entity, int range); + + /** + * Gets the absolute closest player from the specified point within a specified range. + * + * @param position to search from + * @param ignore to ignore while searching + * @param range to search + * @return nearest player + */ + public Player getNearestPlayer(Point position, Player ignore, int range); + + /** + * Gets the absolute closest player from the specified point within a specified range. + * + * @param position center of search + * @param range to search + * @return nearest player + */ + public Player getNearestPlayer(Point position, int range); + + /** + * Gets the absolute closest player from the specified point within a specified range. + * + * @param entity to search from + * @param range to search + * @return nearest player + */ + public Player getNearestPlayer(Entity entity, int range); + + /** + * Atomically sets the cuboid volume to the values inside of the cuboid buffer. + * + * @param cause that is setting the cuboid volume + */ + @Override + public void setCuboid(CuboidBlockMaterialBuffer buffer, Cause cause); + + /** + * Atomically sets the cuboid volume to the values inside of the cuboid buffer with the base located at the given coords + * + * @param cause that is setting the cuboid volume + */ + @Override + public void setCuboid(int x, int y, int z, CuboidBlockMaterialBuffer buffer, Cause cause); + + /** + * Atomically gets the cuboid volume with the base located at the given coords of the given size.

Note: The block at the base coordinate is inside the + * + * @param bx base x-coordinate + * @param by base y-coordinate + * @param bz base z-coordinate + * @param sx size x-coordinate + * @param sy size y-coordinate + * @param sz size z-coordinate + */ + @Override + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz); + + /** + * Atomically gets the cuboid volume with the base located at the given coords and the size of the given buffer.

Note: The block at the base coordinate is inside the + * + * @param bx base x-coordinate + * @param by base y-coordinate + * @param bz base z-coordinate + */ + @Override + public void getCuboid(int bx, int by, int bz, CuboidBlockMaterialBuffer buffer); + + /** + * Atomically gets the cuboid volume contained within the given buffer + * + * @param buffer the buffer + */ + @Override + public void getCuboid(CuboidBlockMaterialBuffer buffer); + + /** + * Gets the {@link ManagedMap} which a world always has. + * + * @return ManagedMap + */ + @Override + public ManagedMap getData(); + + /** + * Gets a set of all players on active on this world + * + * @return all players on this world + */ + public List getPlayers(); +} diff --git a/src/main/java/org/spout/api/geo/WorldManager.java b/src/main/java/org/spout/api/geo/WorldManager.java new file mode 100644 index 0000000..6879a7e --- /dev/null +++ b/src/main/java/org/spout/api/geo/WorldManager.java @@ -0,0 +1,59 @@ +package org.spout.api.geo; + +import java.util.Collection; +import java.util.UUID; + +public interface WorldManager { + + /** + * Searches for an actively loaded world that exactly matches the given name.

The implementation is identical to iterating over {@link #getWorlds()} and checking for a world that matches + * {@link World#getName()}.

+ * + * Worlds are added to the list immediately, but removed at the end of a tick. + * + * @param name of the world to search for + * @return {@link World} if found, else null + */ + public World getWorld(String name); + + /** + * Searches for an actively loaded world that exactly matches the given name.

If searching for the exact name, this method will iterate and check for exact matches.

Otherwise, + * this method will iterate over over all worlds and find the closest match to the given name, by comparing the length of other player names that start with the given parameter.

+ * + * Worlds are added to the list immediately, but removed at the end of a tick. + * + * @param name of the world to search for + * @param exact Whether to use exact lookup + * @return world if found, else null + */ + public World getWorld(String name, boolean exact); + + /** + * Searches for actively loaded worlds that matches the given name.

The implementation is identical to iterating over {@link #getWorlds()} and checking for a world that matches {@link + * World#getName()}

+ * + * Worlds are added to the list immediately, but removed at the end of a tick. + * + * @param name of the world to search for, or part of it + * @return a collection of worlds that matched the name + */ + public Collection matchWorld(String name); + + /** + * Searches for an actively loaded world has the given {@link UUID}.

The implementation is identical to iterating over {@link #getWorlds()} and checking for a world that matches {@link + * World#getUID()}.

+ * + * Worlds are added to the list immediately, but removed at the end of a tick. + * + * @param uid of the world to search for + * @return {@link World} if found, else null + */ + public World getWorld(UUID uid); + + /** + * Gets a List of all currently loaded worlds
Worlds are added to the list immediately, but removed at the end of a tick. + * + * @return {@link Collection} of actively loaded worlds + */ + public Collection getWorlds(); +} diff --git a/src/main/java/org/spout/api/geo/WorldSource.java b/src/main/java/org/spout/api/geo/WorldSource.java new file mode 100644 index 0000000..5636e60 --- /dev/null +++ b/src/main/java/org/spout/api/geo/WorldSource.java @@ -0,0 +1,39 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo; + +/** + * Represents an object that can be contained within a {@link World} + */ +public interface WorldSource { + /** + * Gets the World + * + * @return the World + */ + public World getWorld(); +} diff --git a/src/main/java/org/spout/api/geo/cuboid/Block.java b/src/main/java/org/spout/api/geo/cuboid/Block.java new file mode 100644 index 0000000..a570a80 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/Block.java @@ -0,0 +1,280 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import com.flowpowered.events.Cause; + +import org.spout.api.component.ComponentOwner; +import org.spout.api.geo.World; +import org.spout.api.geo.WorldSource; +import org.spout.api.geo.discrete.Point; +import org.spout.api.material.BlockMaterial; +import org.spout.api.material.Material; +import org.spout.api.material.block.BlockFace; +import org.spout.math.vector.Vector3f; +import org.spout.math.vector.Vector3i; + +public interface Block extends WorldSource, ComponentOwner { + /** + * Gets the {@link Point} position of this block in the world + * + * @return the position + */ + public Point getPosition(); + + /** + * Gets the {@link Chunk} this block is in + * + * @return the Chunk + */ + public Chunk getChunk(); + + /** + * Gets the {@link Region} this block is in + * + * @return the Region + */ + public Region getRegion(); + + /** + * Gets the {@link World} this block is in + * + * @return the World + */ + @Override + public World getWorld(); + + /** + * Gets the x-coordinate of this block + * + * @return the x-coordinate + */ + public int getX(); + + /** + * Gets the y-coordinate of this block + * + * @return the y-coordinate + */ + public int getY(); + + /** + * Gets the z-coordinate of this block + * + * @return the z-coordinate + */ + public int getZ(); + + /** + * Translates this block using the offset and distance given + * + * @param offset BlockFace to translate + * @param distance to translate + * @return a new Block instance + */ + public Block translate(BlockFace offset, int distance); + + /** + * Translates this block using the offset given + * + * @param offset BlockFace to translate + * @return a new Block instance + */ + public Block translate(BlockFace offset); + + /** + * Translates this block using the offset given + * + * @param offset Vector to translate + * @return a new Block instance + */ + public Block translate(Vector3f offset); + + /** + * Translates this block using the offset given + * + * @param offset Vector to translate + * @return a new Block instance + */ + public Block translate(Vector3i offset); + + /** + * Translates this block using the offsets given + * + * @param dx offset to translate + * @param dy offset to translate + * @param dz offset to translate + * @return a new Block instance + */ + public Block translate(int dx, int dy, int dz); + + /** + * Gets the block material of this block + * + * @return block material + */ + public BlockMaterial getMaterial(); + + /** + * Gets the block data for this block + * + * @return data + */ + public short getBlockData(); + + /** + * Sets the data of this block to the given material's data + * + * @param data to set to + * @return this Block + */ + public Block setData(BlockMaterial data); + + /** + * Sets the data of this block + * + * @param data to set to + * @return this Block + */ + public Block setData(int data); + + /** + * Sets the data of this block + * + * @param data to set to + * @param cause of the change + * @return this Block + */ + public Block setData(int data, Cause cause); + + /** + * Adds the value to the data of this block + * + * @param data to add + * @return this Block + */ + public Block addData(int data); + + /** + * Sets the material of this block + * + * @param material to set to + * @return whether the material set was successful + */ + public boolean setMaterial(BlockMaterial material); + + /** + * Sets the material of this block + * + * @param material to set to + * @param cause of the change + * @return whether the material set was successful + */ + public boolean setMaterial(BlockMaterial material, Cause cause); + + /** + * Sets the material and data of this block + * + * @param material to set to + * @param data to set to + * @return whether the material set was successful + */ + public boolean setMaterial(BlockMaterial material, int data); + + /** + * Sets the material and data of this block + * + * @param material to set to + * @param data to set to + * @param cause of the change + * @return whether the material set was successful + */ + public boolean setMaterial(BlockMaterial material, int data, Cause cause); + + /** + * Sets the given bits in the data for the block

newData = oldData | (bits) + * + * @param bits the bits to set + * @return the old data for the block + */ + public short setDataBits(int bits); + + /** + * Sets the given bits in the data for the block

newData = oldData | (bits)
or
newData = oldData & ~(bits) + * + * @param bits the bits to set or clear + * @param set True to set the bits, False to clear + * @return the old data for the block + */ + public short setDataBits(int bits, boolean set); + + /** + * Clears the given bits in the data for the block

newData = oldData & (~bits) + * + * @param bits the bits to clear + * @return the old data for the block + */ + public short clearDataBits(int bits); + + /** + * Gets the data field from the block

field = (data & bits) >> (shift)

The shift value used shifts the least significant non-zero bit of bits to the LSB position + * + * @param bits the bits of the field + * @return the field value + */ + public int getDataField(int bits); + + /** + * Gets if any of the indicated bits are set. + * + * @param bits the bits to check + * @return true if any of the given bits are set + */ + public boolean isDataBitSet(int bits); + + /** + * Sets the data field for the block. This is the reverse operation to the getDataField method.

newData = ((value << shift) & bits) | (oldData & (~bits))

The shift value used + * shifts the least significant non-zero bit of bits to the LSB position + * + * @param bits the bits of the field + * @param value the new value of the field + * @return the old value of the field + */ + public int setDataField(int bits, int value); + + /** + * Adds a value to the data field for the block. This is the reverse operation to the getBlockDataField method.

newData = (((oldData + (value << shift)) & bits) | (oldData & ~bits))
+ *
The shift value used shifts the least significant non-zero bit of bits to the LSB position + * + * @param bits the bits of the field + * @param value to add to the value of the field + * @return the old value of the field + */ + public int addDataField(int bits, int value); + + public boolean isMaterial(Material... materials); +} diff --git a/src/main/java/org/spout/api/geo/cuboid/BlockComponentContainer.java b/src/main/java/org/spout/api/geo/cuboid/BlockComponentContainer.java new file mode 100644 index 0000000..756d5f6 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/BlockComponentContainer.java @@ -0,0 +1,46 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import org.spout.api.component.BlockComponentOwner; + +public interface BlockComponentContainer extends CubicContainer { + /** + * Sets the next BlockComponentSnapshot in the sequence + */ + public void setBlockComponent(int x, int y, int z, BlockComponentOwner snapshot); + + /** + * Sets the number of block components. This method is called before the first call to setBlockComponent(); + */ + public void setBlockComponentCount(int count); + + /** + * Sets the number of block components. This method is called before the first call to setBlockComponent(); + */ + public int getBlockComponentCount(); +} diff --git a/src/main/java/org/spout/api/geo/cuboid/BlockContainer.java b/src/main/java/org/spout/api/geo/cuboid/BlockContainer.java new file mode 100644 index 0000000..ebd4e94 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/BlockContainer.java @@ -0,0 +1,34 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +public interface BlockContainer extends CubicContainer { + /** + * Sets the state for the next block in the sequence. + */ + public void setBlockFullState(int state); +} diff --git a/src/main/java/org/spout/api/geo/cuboid/Chunk.java b/src/main/java/org/spout/api/geo/cuboid/Chunk.java new file mode 100644 index 0000000..ad45b28 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/Chunk.java @@ -0,0 +1,379 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.Future; + +import com.flowpowered.commons.BitSize; + +import org.spout.api.entity.Entity; +import org.spout.api.entity.Player; +import org.spout.api.geo.AreaBlockAccess; +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.ChunkSnapshot.EntityType; +import org.spout.api.geo.cuboid.ChunkSnapshot.ExtraData; +import org.spout.api.geo.cuboid.ChunkSnapshot.SnapshotType; +import org.spout.api.geo.discrete.Point; +import org.spout.api.material.block.BlockFace; +import org.spout.math.vector.Vector3f; + +/** + * Represents a cube containing 16x16x16 Blocks + */ +public abstract class Chunk extends Cube implements AreaBlockAccess { + /** + * Stores the size of the amount of blocks in this Chunk + */ + public static final BitSize BLOCKS = new BitSize(4); + /** + * Mask to convert a block integer coordinate into the point base + */ + public final static int POINT_BASE_MASK = -BLOCKS.SIZE; + private final int blockX; + private final int blockY; + private final int blockZ; + + public Chunk(World world, float x, float y, float z) { + super(new Point(world, x, y, z), BLOCKS.SIZE); + blockX = super.getX() << BLOCKS.BITS; + blockY = super.getY() << BLOCKS.BITS; + blockZ = super.getZ() << BLOCKS.BITS; + } + + /** + * Performs the necessary tasks to unload this chunk from the world. + * + * @param save whether the chunk data should be saved. + */ + public abstract void unload(boolean save); + + /** + * Performs the necessary tasks to save this chunk. + */ + public abstract void save(); + + /** + * Gets a snapshot of the data for the chunk.

This process may result in tearing if called during potential updates

This is the same as calling getSnapshot(BOTH, WEAK_ENTITIES, + * NO_EXTRA_DATA) + * + * @return the snapshot + */ + public abstract ChunkSnapshot getSnapshot(); + + /** + * Gets a snapshot of the data for the chunk.

This process may result in tearing if called during potential updates

+ * + * @param type the type of basic snapshot information to be stored + * @param entities whether to include entity data in the snapshot + * @param data the extra data, if any, to be stored + * @return the snapshot + */ + public abstract ChunkSnapshot getSnapshot(SnapshotType type, EntityType entities, ExtraData data); + + /** + * Fills the given block container with the block data for this chunk + */ + public abstract void fillBlockContainer(BlockContainer container); + + /** + * Fills the given block component container with the block components for this chunk + */ + public abstract void fillBlockComponentContainer(BlockComponentContainer container); + + /** + * Gets a snapshot of the data for the chunk. The snapshot will be taken at a stable moment in the tick.

This is the same as calling getFutureSnapshot(BOTH, WEAK_ENTITIES, NO_EXTRA_DATA) + * + * @return the snapshot + */ + public abstract Future getFutureSnapshot(); + + /** + * Gets a snapshot of the data for the chunk. The snapshot will be taken at a stable moment in the tick. + * + * @param type the type of basic snapshot information to be stored + * @param entities whether to include entity data in the snapshot + * @param data the extra data, if any, to be stored + * @return the snapshot + */ + public abstract Future getFutureSnapshot(SnapshotType type, EntityType entities, ExtraData data); + + /** + * Refresh the distance between a player and the chunk, and adds the player as an observer if not previously observing. + * + * @param player the player + * @return false if the player was already observing the chunk + */ + public abstract boolean refreshObserver(Entity player); + + /** + * De-register a player as observing the chunk. + * + * @param player the player + * @return true if the player was observing the chunk + */ + public abstract boolean removeObserver(Entity player); + + /** + * Gets the region that this chunk is located in + * + * @return region + */ + public abstract Region getRegion(); + + /** + * Tests if the chunk is currently loaded + * + * Chunks may be unloaded at the end of each tick + */ + public abstract boolean isLoaded(); + + /** + * Populates the chunk with all the Populators attached to the WorldGenerator of its world. + */ + public abstract boolean populate(); + + /** + * Populates the chunk with all the Populators attached to the WorldGenerator of its world. + * + * @param force forces to populate the chunk even if it already has been populated. + */ + public abstract boolean populate(boolean force); + + /** + * Populates the chunk with all the Populators attached to the WorldGenerator of its world.

Warning: populating with force observer should not be called from within populators as it could + * lead to a population cascade + * + * @param sync queues the population to occur at a later time + * @param observe forces the chunk to be observed for population + */ + public abstract void populate(boolean sync, boolean observe); + + /** + * Populates the chunk with all the Populators attached to the WorldGenerator of its world.

Warning: populating with force observer should not be called from within populators as it could + * lead to a population cascade + * + * @param sync queues the population to occur at a later time + * @param observe forces the chunk to be observed for population + * @param priority adds the chunk to the high priority queue + */ + public abstract void populate(boolean sync, boolean observe, boolean priority); + + /** + * Gets if this chunk already has been populated. + * + * @return if the chunk is populated. + */ + public abstract boolean isPopulated(); + + /** + * Gets the entities in the chunk at the last snapshot + * + * @return the entities + */ + public abstract List getEntities(); + + /** + * Gets the entities currently in the chunk + * + * @return the entities + */ + public abstract List getLiveEntities(); + + /** + * Gets the number of observers viewing this chunk. If the number of observing entities falls to zero, this chunk may be reaped at any time. + * + * @return number of observers + */ + public abstract int getNumObservers(); + + /** + * Gets the observing players of this chunk (done based on the player's view distance). + * + * @return List containing the observing players + */ + public abstract Set getObservingPlayers(); + + /** + * Gets the observers of this chunk + * + * @return Set containing the observing players + */ + public abstract Set getObservers(); + + @Override + public boolean containsBlock(int x, int y, int z) { + return x >> BLOCKS.BITS == this.getX() && y >> BLOCKS.BITS == this.getY() && z >> BLOCKS.BITS == this.getZ(); + } + + /** + * Gets the x-coordinate of this chunk as a Block coordinate + * + * @return the x-coordinate of the first block in this chunk + */ + public int getBlockX() { + return blockX; + } + + /** + * Gets the y-coordinate of this chunk as a Block coordinate + * + * @return the y-coordinate of the first block in this chunk + */ + public int getBlockY() { + return blockY; + } + + /** + * Gets the z-coordinate of this chunk as a Block coordinate + * + * @return the z-coordinate of the first block in this chunk + */ + public int getBlockZ() { + return blockZ; + } + + /** + * Gets the Block x-coordinate in the world + * + * @param x-coordinate within this Chunk + * @return x-coordinate within the World + */ + public int getBlockX(int x) { + return this.blockX + (x & BLOCKS.MASK); + } + + /** + * Gets the Block x-coordinate in the world + * + * @param y y-coordinate within this Chunk + * @return y-coordinate within the World + */ + public int getBlockY(int y) { + return this.blockY + (y & BLOCKS.MASK); + } + + /** + * Gets the Block x-coordinate in the world + * + * @param z z-coordinate within this Chunk + * @return z-coordinate within the World + */ + public int getBlockZ(int z) { + return this.blockZ + (z & BLOCKS.MASK); + } + + /** + * Gets a random Block x-coordinate using a Random + * + * @param random to use + * @return x-coordinate within the World in this Chunk + */ + public int getBlockX(Random random) { + return this.blockX + random.nextInt(BLOCKS.SIZE); + } + + /** + * Gets a random Block y-coordinate using a Random + * + * @param random to use + * @return y-coordinate within the World in this Chunk + */ + public int getBlockY(Random random) { + return this.blockY + random.nextInt(BLOCKS.SIZE); + } + + /** + * Gets a random Block z-coordinate using a Random + * + * @param random to use + * @return z-coordinate within the World in this Chunk + */ + public int getBlockZ(Random random) { + return this.blockZ + random.nextInt(BLOCKS.SIZE); + } + + /** + * Gets a chunk relative to this chunk + * + * @param offset of the chunk relative to this chunk + * @param opt True to load the chunk if it is not yet loaded + * @return The Chunk, or null if not loaded and load is False + */ + public Chunk getRelative(Vector3f offset, LoadOption opt) { + return this.getWorld().getChunk(this.getX() + (int) offset.getX(), this.getY() + (int) offset.getY(), this.getZ() + (int) offset.getZ(), opt); + } + + /** + * Gets a chunk relative to this chunk, loads if needed + * + * @param offset of the chunk relative to this chunk + * @return The Chunk + */ + public Chunk getRelative(Vector3f offset) { + return this.getRelative(offset, LoadOption.LOAD_GEN); + } + + /** + * Gets a chunk relative to this chunk + * + * @param offset of the chunk relative to this chunk + * @param opt True to load the chunk if it is not yet loaded + * @return The Chunk, or null if not loaded and load is False + */ + public Chunk getRelative(BlockFace offset, LoadOption opt) { + return this.getRelative(offset.getOffset(), opt); + } + + /** + * Gets a chunk relative to this chunk, loads if needed + * + * @param offset of the chunk relative to this chunk + * @return The Chunk + */ + public Chunk getRelative(BlockFace offset) { + return this.getRelative(offset.getOffset()); + } + + /** + * Gets the generation index for this chunk. Only chunks generated as part of the same bulk initialize have the same index. + * + * @return a unique generation id, or -1 if the chunk was loaded from disk + */ + public abstract int getGenerationIndex(); + + /** + * Converts a point in such a way that it points to the first block (the base block) of the chunk
This is similar to performing the following operation on the x, y and z coordinate:
- Convert + * to the chunk coordinate
- Multiply by chunk size + */ + public static Point pointToBase(Point p) { + return new Point(p.getWorld(), (int) p.getX() & POINT_BASE_MASK, (int) p.getY() & POINT_BASE_MASK, (int) p.getZ() & POINT_BASE_MASK); + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/ChunkSnapshot.java b/src/main/java/org/spout/api/geo/cuboid/ChunkSnapshot.java new file mode 100644 index 0000000..f61af0c --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/ChunkSnapshot.java @@ -0,0 +1,201 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import java.util.List; +import java.util.Set; + +import com.flowpowered.commons.datatable.SerializableMap; +import org.spout.api.component.block.BlockComponent; +import org.spout.api.entity.EntitySnapshot; +import org.spout.api.geo.AreaBlockSource; +import org.spout.api.geo.World; +import org.spout.api.geo.discrete.Point; + +public abstract class ChunkSnapshot extends Cube implements AreaBlockSource { + /** + * Internal size of a side of a chunk + */ + public final static int CHUNK_SIZE = 16; + /** + * Number of bits on the side of a chunk + */ + public final static int CHUNK_SIZE_BITS = 4; + /** + * Mask to convert a block integer coordinate into the chunk's base + */ + public final static int BASE_MASK = -CHUNK_SIZE; + + public ChunkSnapshot(World world, float x, float y, float z) { + super(new Point(world, x, y, z), CHUNK_SIZE); + } + + /** + * Gets a copy of the raw block ids. + * + * @return raw block ids + */ + public abstract short[] getBlockIds(); + + /** + * Gets a copy of the raw block data. + * + * @return block data + */ + public abstract short[] getBlockData(); + + /** + * Gets the region that this chunk is located in. + */ + public abstract Region getRegion(); + + /** + * Gets an unmodifiable list of all of the entity snapshots associated with this chunk. + * + * @return the entities + */ + public abstract List getEntities(); + + /** + * Gets if this chunk snapshot had already been populated. + * + * @return if the chunk snapshot was populated. + */ + public abstract boolean isPopulated(); + + /** + * A thread-safe copy of the map of data attached to the chunk. + * + * @return data map + */ + public abstract SerializableMap getDataMap(); + + /** + * Gets a unmodifiable list of all of the block component snapshots associated with this chunk. + * + * @return list of block component snapshots + */ + public abstract List getBlockComponents(); + + public static enum SnapshotType { + /** + * Loads no block data (ids, data, skylight, blocklight) in the snapshot + */ + NO_BLOCK_DATA, + /** + * Loads only block ids, no block data, skylight, or blocklight + */ + BLOCK_IDS_ONLY, + /** + * Loads only block ids and block data, no skylight or blocklight + */ + BLOCKS_ONLY, + /** + * Loads only skylight and blocklight, no block ids or block data + */ + LIGHT_ONLY, + /** + * Loads block ids, block data, skylight, and blocklight + */ + BOTH, + } + + public static enum EntityType { + /** + * Saves no entity information in the snapshot + */ + NO_ENTITIES, + /** + * Saves class and datatable information regarding block components in the snapshot + */ + BLOCK_COMPONENTS, + /** + * Saves entity snapshot information + */ + ENTITIES, + /** + * Saves both entity and block component information in the snapshot + */ + BOTH + } + + public static enum ExtraData { + /** + * Loads no extra data in the snapshot + */ + NO_EXTRA_DATA, + /** + * Loads only extra biome data in the snapshot + */ + BIOME_DATA, + /** + * Loads only extra datatable information in the snapshot + */ + DATATABLE, + /** + * Loads both biome and datatable information in the snapshot + */ + BOTH; + } + + public static interface BlockComponentSnapshot { + /** + * Gets the world block x-coordinate for this snapshot + * + * @return x-coordinate + */ + public int getX(); + + /** + * Gets the world block x-coordinate for this snapshot + * + * @return y-coordinate + */ + public int getY(); + + /** + * Gets the world block x-coordinate for this snapshot + * + * @return z-coordinate + */ + public int getZ(); + + /** + * Gets the block component class + * + * @return block component class + */ + public Set> getComponents(); + + /** + * Gets a copy of the block component data + * + * @return data + */ + public SerializableMap getData(); + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/ChunkSnapshotGroup.java b/src/main/java/org/spout/api/geo/cuboid/ChunkSnapshotGroup.java new file mode 100644 index 0000000..1f1a61a --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/ChunkSnapshotGroup.java @@ -0,0 +1,82 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import org.spout.api.material.BlockMaterial; + +public interface ChunkSnapshotGroup { + public int getX(); + + public int getY(); + + public int getZ(); + + /** + * Gets if the chunk was unloaded. Unload models only indicate an unload occurred and contain no data. + */ + public boolean isUnload(); + + /** + * Gets the current center chunk of this model + */ + public ChunkSnapshot getCenter(); + + /** + * Clears all references to live chunks and regions + */ + public void cleanUp(); + + /** + * Gets the chunk at world chunk coordinates
Note: Coordinates must be within this model, or index out of bounds will be thrown. + * + * @param cx coordinate of the chunk + * @param cy coordinate of the chunk + * @param cz coordinate of the chunk + * @return The chunk, or null if not available + */ + public ChunkSnapshot getChunk(int cx, int cy, int cz); + + /** + * Gets the chunk at world block coordinates
Note: Coordinates must be within this model, or index out of bounds will be thrown. + * + * @param bx coordinate of the block + * @param by coordinate of the block + * @param bz coordinate of the block + * @return The chunk, or null if not available + */ + public ChunkSnapshot getChunkFromBlock(int bx, int by, int bz); + + /** + * Gets the block material at the world block coordinates.
Note: Coordinates must be within this model, or index out of bounds will be thrown. + * + * @param bx coordinate of the block + * @param by coordinate of the block + * @param bz coordinate of the block + * @return The block, or null if not available + */ + public BlockMaterial getBlock(int bx, int by, int bz); +} diff --git a/src/main/java/org/spout/api/geo/cuboid/ContainerFillOrder.java b/src/main/java/org/spout/api/geo/cuboid/ContainerFillOrder.java new file mode 100644 index 0000000..0dc0780 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/ContainerFillOrder.java @@ -0,0 +1,112 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +/** + * \ Indicates fill orders for fill containers

The intended usages is of the form

int sourceIndex = 0;
int targetIndex = 0;

int thirdStep = + * targetFillOrder.thirdStep(source, sizeX, sizeY, sizeZ);
int secondStep = targetFillOrder.secondStep(source, sizeX, sizeY, sizeZ);
int firstStep = targetFillOrder.firstStep(source, sizeX, + * sizeY, sizeZ);

int thirdMax = target.getThirdSize(sizeX, sizeY, sizeZ);
int secondMax = target.getSecondSize(sizeX, sizeY, sizeZ);
int firstMax = target.getFirstSize(sizeX, sizeY, + * sizeZ);

 for (int third = 0; third < thirdMax; third++) {
    int secondStart = sourceIndex;
    for (int second = 0; second < + * secondMax; second++) {
      int firstStart = sourceIndex;
      for (int first = 0; first < firstMax; first++) {
+ *         target[targetIndex++] = source[sourceIndex += firstStep];
      }
+ *       sourceIndex = firstStart + secondStep;
    }
    sourceIndex = secondStart + thirdStep;
  } + *
+ */ +public enum ContainerFillOrder { + + YXZ(Coord.Y, Coord.X, Coord.Z), + YZX(Coord.Y, Coord.Z, Coord.X), + ZXY(Coord.Z, Coord.X, Coord.Y), + ZYX(Coord.Z, Coord.Y, Coord.X), + XYZ(Coord.X, Coord.Y, Coord.Z), + XZY(Coord.X, Coord.Z, Coord.Y); + private final Coord first; + private final Coord second; + private final Coord third; + + ContainerFillOrder(Coord first, Coord second, Coord third) { + this.first = first; + this.second = second; + this.third = third; + } + + public int firstStep(ContainerFillOrder source, int sizeX, int sizeY, int sizeZ) { + return step(source, first, sizeX, sizeY, sizeZ); + } + + public int getFirstSize(int sizeX, int sizeY, int sizeZ) { + return first.getSize(sizeX, sizeY, sizeZ); + } + + public int secondStep(ContainerFillOrder source, int sizeX, int sizeY, int sizeZ) { + return step(source, second, sizeX, sizeY, sizeZ); + } + + public int getSecondSize(int sizeX, int sizeY, int sizeZ) { + return second.getSize(sizeX, sizeY, sizeZ); + } + + public int thirdStep(ContainerFillOrder source, int sizeX, int sizeY, int sizeZ) { + return step(source, third, sizeX, sizeY, sizeZ); + } + + public int getThirdSize(int sizeX, int sizeY, int sizeZ) { + return third.getSize(sizeX, sizeY, sizeZ); + } + + private int step(ContainerFillOrder source, Coord coord, int sizeX, int sizeY, int sizeZ) { + if (coord == source.first) { + return 1; + } else if (coord == source.second) { + return source.first.getSize(sizeX, sizeY, sizeZ); + } else if (coord == source.third) { + return source.first.getSize(sizeX, sizeY, sizeZ) * source.second.getSize(sizeX, sizeY, sizeZ); + } else { + throw new IllegalStateException("At least one coord must match"); + } + } + + private enum Coord { + X, + Y, + Z; + + public int getSize(int sizeX, int sizeY, int sizeZ) { + switch (this) { + case X: + return sizeX; + case Y: + return sizeY; + case Z: + return sizeZ; + default: + throw new IllegalStateException("Unknown coord: " + this); + } + } + + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/Cube.java b/src/main/java/org/spout/api/geo/cuboid/Cube.java new file mode 100644 index 0000000..2350755 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/Cube.java @@ -0,0 +1,39 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import org.spout.api.geo.discrete.Point; +import org.spout.math.vector.Vector3f; + +/** + * Represents a Cube that is located somewhere in a world. + */ +public class Cube extends Cuboid { + public Cube(Point base, float size) { + super(base, new Vector3f(size, size, size)); + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/CubicContainer.java b/src/main/java/org/spout/api/geo/cuboid/CubicContainer.java new file mode 100644 index 0000000..032f45f --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/CubicContainer.java @@ -0,0 +1,34 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +public interface CubicContainer { + /** + * Gets the fill order for the container + */ + public ContainerFillOrder getOrder(); +} diff --git a/src/main/java/org/spout/api/geo/cuboid/Cuboid.java b/src/main/java/org/spout/api/geo/cuboid/Cuboid.java new file mode 100644 index 0000000..7181f0d --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/Cuboid.java @@ -0,0 +1,147 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import org.spout.api.geo.World; +import org.spout.api.geo.WorldSource; +import org.spout.api.geo.discrete.Point; +import org.spout.math.vector.Vector3f; + +/** + * Represents a Cuboid shaped volume that is located somewhere in a world. + */ +public class Cuboid implements WorldSource { + protected final Point base; + protected final Vector3f size; + private final int x; + private final int y; + private final int z; + /** + * Hashcode caching + */ + private volatile boolean hashed = false; + private volatile int hashcode = 0; + /** + * Vertex cache + */ + private Vector3f[] vertices = null; + + /** + * Constructs a cubiod with the point as the base point, and + */ + public Cuboid(Point base, Vector3f size) { + this.base = base; + this.size = size; + this.x = (int) (base.getX() / size.getX()); + this.y = (int) (base.getY() / size.getY()); + this.z = (int) (base.getZ() / size.getZ()); + } + + public Point getBase() { + return base; + } + + public Vector3f getSize() { + return size; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + @Override + public World getWorld() { + return base.getWorld(); + } + + /** + * Returns the vertices of this Cuboid. + * + * @return The vertices + */ + public Vector3f[] getVertices() { + if (vertices == null) { + vertices = new Vector3f[8]; + + // Front + vertices[0] = new Vector3f(base.getX(), base.getY(), base.getZ() + size.getZ()); + vertices[1] = new Vector3f(base.getX() + size.getX(), base.getY(), base.getZ() + size.getZ()); + vertices[2] = new Vector3f(base.getX() + size.getX(), base.getY() + size.getY(), base.getZ() + size.getZ()); + vertices[3] = new Vector3f(base.getX(), base.getY() + size.getY(), base.getZ() + size.getZ()); + // Back + vertices[4] = new Vector3f(base.getX(), base.getY(), base.getZ()); + vertices[5] = new Vector3f(base.getX() + size.getX(), base.getY(), base.getZ()); + vertices[6] = new Vector3f(base.getX() + size.getX(), base.getY() + size.getY(), base.getZ()); + vertices[7] = new Vector3f(base.getX(), base.getY() + size.getY(), base.getZ()); + } + + return vertices; + } + + public boolean contains(Vector3f vec) { + Vector3f max = base.add(size); + return (base.getX() <= vec.getX() && vec.getX() < max.getX()) && (base.getY() <= vec.getY() && vec.getY() < max.getY()) && (base.getZ() <= vec.getZ() && vec.getZ() < max.getZ()); + } + + @Override + public int hashCode() { + if (!hashed) { + hashcode = new HashCodeBuilder(563, 21).append(base).append(size).toHashCode(); + hashed = true; + } + return hashcode; + } + + @Override + public boolean equals(Object obj) { + + if (obj == null) { + return false; + } else if (!(obj instanceof Cuboid)) { + return false; + } else { + Cuboid cuboid = (Cuboid) obj; + + return cuboid.size.getX() == size.getX() && cuboid.size.getY() == size.getY() && cuboid.size.getZ() == size.getZ() && cuboid.getWorld().equals(getWorld()) && cuboid.getX() == getX() && cuboid.getY() == getY() && cuboid.getZ() == getZ(); + } + } + + @Override + public String toString() { + return "Cuboid[" + size.getX() + ", " + size.getY() + ", " + size.getZ() + "]@[" + getX() + ", " + getY() + ", " + getZ() + "]"; + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/Region.java b/src/main/java/org/spout/api/geo/cuboid/Region.java new file mode 100644 index 0000000..6fde011 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/Region.java @@ -0,0 +1,283 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import java.util.Iterator; +import java.util.List; + +import com.flowpowered.commons.BitSize; +import org.spout.api.entity.Entity; +import org.spout.api.entity.Player; +import org.spout.api.geo.AreaChunkAccess; +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.LocalAreaAccess; +import org.spout.api.geo.World; +import org.spout.api.geo.discrete.Point; +import org.spout.api.scheduler.TaskManager; + +/** + * Represents a cube containing 16x16x16 Chunks (256x256x256 Blocks) + */ +public abstract class Region extends Cube implements AreaChunkAccess, LocalAreaAccess, Iterable { + /** + * Stores the size of the amount of chunks in this Region + */ + public static final BitSize CHUNKS = new BitSize(4); + /** + * Stores the size of the amount of blocks in this Region + */ + public static final BitSize BLOCKS = new BitSize(CHUNKS.BITS + Chunk.BLOCKS.BITS); + private final int blockX; + private final int blockY; + private final int blockZ; + private final int chunkX; + private final int chunkY; + private final int chunkZ; + + public Region(World world, float x, float y, float z) { + super(new Point(world, x, y, z), BLOCKS.SIZE); + this.blockX = super.getX() << BLOCKS.BITS; + this.blockY = super.getY() << BLOCKS.BITS; + this.blockZ = super.getZ() << BLOCKS.BITS; + this.chunkX = super.getX() << CHUNKS.BITS; + this.chunkY = super.getY() << CHUNKS.BITS; + this.chunkZ = super.getZ() << CHUNKS.BITS; + } + + /** + * Gets the x-coordinate of this region as a Block coordinate + * + * @return the x-coordinate of the first block in this region + */ + public int getBlockX() { + return this.blockX; + } + + /** + * Gets the y-coordinate of this region as a Block coordinate + * + * @return the y-coordinate of the first block in this region + */ + public int getBlockY() { + return this.blockY; + } + + /** + * Gets the z-coordinate of this region as a Block coordinate + * + * @return the z-coordinate of the first block in this region + */ + public int getBlockZ() { + return this.blockZ; + } + + /** + * Gets the x-coordinate of this region as a Chunk coordinate + * + * @return the x-coordinate of the first chunk in this region + */ + public int getChunkX() { + return this.chunkX; + } + + /** + * Gets the y-coordinate of this region as a Chunk coordinate + * + * @return the y-coordinate of the first chunk in this region + */ + public int getChunkY() { + return this.chunkY; + } + + /** + * Gets the z-coordinate of this region as a Chunk coordinate + * + * @return the z-coordinate of the first chunk in this region + */ + public int getChunkZ() { + return this.chunkZ; + } + + /** + * Gets the Block x-coordinate in the world + * + * @param x-coordinate within this Region + * @return x-coordinate within the World + */ + public int getBlockX(int x) { + return this.blockX + (x & BLOCKS.MASK); + } + + /** + * Gets the Block y-coordinate in the world + * + * @param y-coordinate within this Region + * @return y-coordinate within the World + */ + public int getBlockY(int y) { + return this.blockY + (y & BLOCKS.MASK); + } + + /** + * Gets the Block z-coordinate in the world + * + * @param z-coordinate within this Region + * @return z-coordinate within the World + */ + public int getBlockZ(int z) { + return this.blockZ + (z & BLOCKS.MASK); + } + + /** + * Gets the Chunk x-coordinate in the world + * + * @param x-coordinate within this Region + * @return x-coordinate within the World + */ + public int getChunkX(int x) { + return this.chunkX + (x & CHUNKS.MASK); + } + + /** + * Gets the Chunk y-coordinate in the world + * + * @param y-coordinate within this Region + * @return y-coordinate within the World + */ + public int getChunkY(int y) { + return this.chunkY + (y & CHUNKS.MASK); + } + + /** + * Gets the Chunk z-coordinate in the world + * + * @param z-coordinate within this Region + * @return z-coordinate within the World + */ + public int getChunkZ(int z) { + return this.chunkZ + (z & CHUNKS.MASK); + } + + @Override + public boolean containsBlock(int x, int y, int z) { + return x >> BLOCKS.BITS == this.getX() && y >> BLOCKS.BITS == this.getY() && z >> BLOCKS.BITS == this.getZ(); + } + + @Override + public boolean containsChunk(int x, int y, int z) { + return x >> CHUNKS.BITS == this.getX() && y >> CHUNKS.BITS == this.getY() && z >> CHUNKS.BITS == this.getZ(); + } + + /** + * Queues all chunks for saving at the next available opportunity. + */ + public abstract void save(); + + /** + * Performs the nessecary tasks to unload this region from the world, and all associated chunks. + * + * @param save whether to save the region and associated data. + */ + public abstract void unload(boolean save); + + /** + * Gets all entities with the specified type. + * + * @return A set of entities with the specified type. + */ + public abstract List getAll(); + + /** + * Gets an entity by its id. + * + * @param id The id. + * @return The entity, or {@code null} if it could not be found. + */ + public abstract Entity getEntity(int id); + + public abstract List getPlayers(); + + /** + * Gets the TaskManager associated with this region + */ + public abstract TaskManager getTaskManager(); + + public abstract boolean isLoaded(); + + @Override + public Iterator iterator() { + return new ChunkIterator(); + } + + private class ChunkIterator implements Iterator { + private Chunk next; + + public ChunkIterator() { + loop: + for (int dx = 0; dx < CHUNKS.SIZE; dx++) { + for (int dy = 0; dy < CHUNKS.SIZE; dy++) { + for (int dz = 0; dz < CHUNKS.SIZE; dz++) { + next = getChunk(dx, dy, dz, LoadOption.NO_LOAD); + if (next != null) { + break loop; + } + } + } + } + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Chunk next() { + Chunk current = next; + next = null; + final int cx = current.getX() & CHUNKS.MASK; + final int cy = current.getY() & CHUNKS.MASK; + final int cz = current.getZ() & CHUNKS.MASK; + for (int dx = cx; dx < CHUNKS.SIZE; dx++) { + for (int dy = cy; dy < CHUNKS.SIZE; dy++) { + for (int dz = cz; dz < CHUNKS.SIZE; dz++) { + next = getChunk(dx, dy, dz, LoadOption.NO_LOAD); + if (next != null && next != current) { + return current; + } + } + } + } + return current; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Operation not supported"); + } + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/UpdateOption.java b/src/main/java/org/spout/api/geo/cuboid/UpdateOption.java new file mode 100644 index 0000000..1b0737d --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/UpdateOption.java @@ -0,0 +1,69 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +public enum UpdateOption { + /** + * Updates the block itself + */ + SELF(true, false), + /** + * Updates the blocks surrounding this block + */ + AROUND(false, true), + /** + * Updates the block and the blocks surrounding the blocks + */ + SELF_AROUND(true, true); + + private final boolean self; + private final boolean around; + + private UpdateOption(boolean self, boolean around) { + this.self = self; + this.around = around; + } + + /** + * Test if chunk/region should be loaded if not currently loaded + * + * @return true if yes, false if no + */ + public final boolean updateSelf() { + return self; + } + + /** + * Test if chunk/region should be generated if it does not exist + * + * @return true if yes, false if no + */ + public final boolean updateAround() { + return around; + } + +} diff --git a/src/main/java/org/spout/api/geo/cuboid/Voxel.java b/src/main/java/org/spout/api/geo/cuboid/Voxel.java new file mode 100644 index 0000000..fb8818e --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/Voxel.java @@ -0,0 +1,41 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid; + +import org.spout.api.geo.World; +import org.spout.api.geo.discrete.Point; + +/** + * Represents a cube with an edge length of 1/16 of the edge of a Block. + */ +public abstract class Voxel extends Cube { + protected final static float EDGE = 1.f / 16.0f; + + public Voxel(World world, float x, float y, float z) { + super(new Point(world, x, y, z), EDGE); + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/reference/ChunkReference.java b/src/main/java/org/spout/api/geo/cuboid/reference/ChunkReference.java new file mode 100644 index 0000000..a6949ef --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/reference/ChunkReference.java @@ -0,0 +1,96 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid.reference; + +import java.lang.ref.WeakReference; + +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.geo.cuboid.Region; +import org.spout.api.geo.discrete.Point; + +/** + * This holds a {@code WeakReference} that can be used streamline the get() with a isLoaded check. It also adds a + * store of a {@code Point} representing the base. Because of this, a ChunkReference may contain only base info. + */ +public class ChunkReference { + private final Point base; + private final RegionReference region; + private WeakReference chunk; + + public ChunkReference(Chunk referent) { + this.chunk = new WeakReference<>(referent); + this.region = new RegionReference(referent.getRegion()); + this.base = referent.getBase(); + } + + public ChunkReference(Point base) { + this.chunk = null; + this.region = new RegionReference(new Point(base.getWorld(), base.getFloorX() >> Region.BLOCKS.BITS, base.getFloorY() >> Region.BLOCKS.BITS, base.getFloorZ() >> Region.BLOCKS.BITS)); + this.base = base; + } + + public Chunk get() { + Chunk get = chunk == null ? null : chunk.get(); + if (get != null) { + if (!get.isLoaded()) { + chunk = null; + return null; + } + } + return get; + } + + public Chunk refresh(LoadOption opt) { + Chunk newChunk = get(); + if (newChunk != null) return newChunk; + + Region newRegion = region.refresh(opt); + if (newRegion == null) return null; + + newChunk = newRegion.getChunkFromBlock(base, opt); + this.chunk = newChunk == null ? null : new WeakReference<>(newChunk); + return newChunk; + } + + @Override + public int hashCode() { + return base.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ChunkReference) { + return base.equals(((ChunkReference) obj).base); + } + return false; + } + + public Point getBase() { + return base; + } +} diff --git a/src/main/java/org/spout/api/geo/cuboid/reference/RegionReference.java b/src/main/java/org/spout/api/geo/cuboid/reference/RegionReference.java new file mode 100644 index 0000000..1a28305 --- /dev/null +++ b/src/main/java/org/spout/api/geo/cuboid/reference/RegionReference.java @@ -0,0 +1,87 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.cuboid.reference; + +import java.lang.ref.WeakReference; + +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.cuboid.Region; +import org.spout.api.geo.discrete.Point; + +/** + * This holds a {@code WeakReference} that can be used streamline the get() with a isLoaded check. It also adds a + * store of a {@code Point} representing the base. Because of this, a RegionReference may contain only base info. + */ +public class RegionReference { + private final Point base; + private WeakReference region; + public RegionReference(Region referent) { + this.region = new WeakReference<>(referent); + base = referent.getBase(); + } + + public RegionReference(Point base) { + region = null; + this.base = base; + } + + public Region get() { + Region get = region == null ? null : region.get(); + if (get != null) { + if (!get.isLoaded()) { + region = null; + return null; + } + } + return get; + } + + public Region refresh(LoadOption opt) { + Region newRegion = get(); + if (newRegion != null) return newRegion; + newRegion = base.getRegion(opt); + this.region = newRegion == null ? null : new WeakReference<>(newRegion); + return newRegion; + } + + @Override + public int hashCode() { + return base.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof RegionReference) { + return base.equals(((RegionReference) obj).base); + } + return false; + } + + public Point getBase() { + return base; + } +} diff --git a/src/main/java/org/spout/api/geo/discrete/Point.java b/src/main/java/org/spout/api/geo/discrete/Point.java new file mode 100644 index 0000000..c8cdce0 --- /dev/null +++ b/src/main/java/org/spout/api/geo/discrete/Point.java @@ -0,0 +1,340 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.discrete; + +import java.io.IOException; +import java.lang.reflect.Field; + +import com.flowpowered.commons.StringUtil; + +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.spout.api.Spout; + +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.World; +import org.spout.api.geo.WorldSource; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.math.vector.Vector3f; +import org.spout.api.geo.cuboid.Region; + +/** + * Represents a position in a World + */ +public class Point extends Vector3f implements WorldSource { + private static final long serialVersionUID = 1L; + protected final World world; + public static final Point invalid = new Point(null, 0, 0, 0); + /** + * Hashcode caching + */ + private transient volatile boolean hashed = false; + private transient volatile int hashcode = 0; + + public Point(Point point) { + super(point); + world = point.getWorld(); + } + + public Point(Vector3f vector, World w) { + super(vector); + world = w; + } + + public Point(World world, float x, float y, float z) { + super(x, y, z); + this.world = world; + } + + @Override + public Point div(float val) { + return new Point(super.div(val), world); + } + + @Override + public Point div(double val) { + return new Point(super.div(val), world); + } + + @Override + public Point div(Vector3f other) { + return new Point(super.div(other), world); + } + + @Override + public Point div(double x, double y, double z) { + return new Point(super.div(x, y, z), world); + } + + @Override + public Point div(float x, float y, float z) { + return new Point(super.div(x, y, z), world); + } + + @Override + public Point mul(float val) { + return new Point(super.mul(val), world); + } + + @Override + public Point mul(double val) { + return new Point(super.mul(val), world); + } + + @Override + public Point mul(Vector3f other) { + return new Point(super.mul(other), world); + } + + @Override + public Point mul(double x, double y, double z) { + return new Point(super.mul(x, y, z), world); + } + + @Override + public Point mul(float x, float y, float z) { + return new Point(super.mul(x, y, z), world); + } + + public Point add(Point other) { + if (world != other.world) { + throw new IllegalArgumentException("Cannot add two points in seperate worlds"); + } + return new Point(super.add(other), world); + } + + @Override + public Point add(Vector3f other) { + return new Point(super.add(other), world); + } + + @Override + public Point add(float x, float y, float z) { + return new Point(super.add(x, y, z), world); + } + + @Override + public Point add(double x, double y, double z) { + return new Point(super.add(x, y, z), world); + } + + @Override + public Point sub(Vector3f other) { + return new Point(super.sub(other), world); + } + + @Override + public Point sub(float x, float y, float z) { + return new Point(super.sub(x, y, z), world); + } + + @Override + public Point sub(double x, double y, double z) { + return new Point(super.sub(x, y, z), world); + } + + public int getBlockX() { + return this.getFloorX(); + } + + public int getBlockY() { + return this.getFloorY(); + } + + public int getBlockZ() { + return this.getFloorZ(); + } + + public int getChunkX() { + return this.getFloorX() >> Chunk.BLOCKS.BITS; + } + + public int getChunkY() { + return this.getFloorY() >> Chunk.BLOCKS.BITS; + } + + public int getChunkZ() { + return this.getFloorZ() >> Chunk.BLOCKS.BITS; + } + + public Chunk getChunk(LoadOption loadopt) { + return world.getChunk(getChunkX(), getChunkY(), getChunkZ(), loadopt); + } + + public Region getRegion(LoadOption loadopt) { + return world.getRegionFromChunk(getChunkX(), getChunkY(), getChunkZ(), loadopt); + } + + /** + * Gets the square of the distance between two points. + * + * This will return Double.MAX_VALUE if the other Point is null, either world is null, or the two points are in different worlds. + * + * Otherwise, it returns the Manhattan distance. + */ + public double getSquaredDistance(Point other) { + if (other == null || world == null || other.world == null || !world.equals(other.world)) { + return Double.MAX_VALUE; + } + double dx = getX() - other.getX(); + double dy = getY() - other.getY(); + double dz = getZ() - other.getZ(); + return dx * dx + dy * dy + dz * dz; + } + + /** + * Gets the distance between two points. + * + * This will return Double.MAX_VALUE if the other Point is null, either world is null, or the two points are in different worlds. + * + * Otherwise, it returns the Manhattan distance. + */ + public double getDistance(Point other) { + return Math.sqrt(getSquaredDistance(other)); + } + + /** + * Gets the Manhattan distance between two points. + * + * This will return Double.MAX_VALUE if the other Point is null, either world is null, or the two points are in different worlds. + * + * Otherwise, it returns the Manhattan distance. + */ + public double getManhattanDistance(Point other) { + if (other == null || world == null || other.world == null || !world.equals(other.world)) { + return Double.MAX_VALUE; + } + return Math.abs(getX() - other.getX()) + Math.abs(getY() - other.getY()) + Math.abs(getZ() - other.getZ()); + } + + /** + * Gets the largest distance between two points, when projected onto one of the axes. + * + * This will return Double.MAX_VALUE if the other Point is null, either world is null, or the two points are in different worlds. + * + * Otherwise, it returns the max distance. + */ + public double getMaxDistance(Point other) { + if (other == null || world == null || other.world == null || !world.equals(other.world)) { + return Double.MAX_VALUE; + } + return Math.max(Math.abs(getX() - other.getX()), + Math.max(Math.abs(getY() - other.getY()), + Math.abs(getZ() - other.getZ()))); + } + + /** + * Gets the world this point is locate in + * + * @return the world + */ + @Override + public World getWorld() { + return world; + } + + /** + * Gets the block this point is locate in + * + * @return the world + */ + public Block getBlock() { + return world.getBlock(getX(), getY(), getZ()); + } + + @Override + public int hashCode() { + if (!hashed) { + hashcode = new HashCodeBuilder(5033, 61).appendSuper(super.hashCode()).append(world).toHashCode(); + hashed = true; + } + return hashcode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Point)) { + return false; + } else { + Point point = (Point) obj; + boolean worldEqual = point.world == world || (point.world != null && point.world.equals(world)); + return worldEqual && point.getX() == getX() && point.getY() == getY() && point.getZ() == getZ(); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + StringUtil.toString(world, getX(), getY(), getZ()); + } + + public String toBlockString() { + return "{" + world.getName() + ":" + getBlockX() + ", " + getBlockY() + ", " + getBlockZ() + "}"; + } + + public String toChunkString() { + return "{" + world.getName() + ":" + getChunkX() + ", " + getChunkY() + ", " + getChunkZ() + "}"; + } + + //Custom serialization logic because world can not be made serializable + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.writeFloat(this.getX()); + out.writeFloat(this.getY()); + out.writeFloat(this.getZ()); + out.writeUTF(world != null ? world.getName() : "null"); + } + + private void readObject(java.io.ObjectInputStream in) throws IOException { + float x = in.readFloat(); + float y = in.readFloat(); + float z = in.readFloat(); + String world = in.readUTF(); + World w = Spout.getEngine().getWorldManager().getWorld(world, true); + try { + Field field; + + field = Vector3f.class.getDeclaredField("x"); + field.setAccessible(true); + field.set(this, x); + + field = Vector3f.class.getDeclaredField("y"); + field.setAccessible(true); + field.set(this, y); + + field = Vector3f.class.getDeclaredField("z"); + field.setAccessible(true); + field.set(this, z); + + field = Point.class.getDeclaredField("world"); + field.setAccessible(true); + field.set(this, w); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + if (Spout.debugMode()) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/org/spout/api/geo/discrete/Transform.java b/src/main/java/org/spout/api/geo/discrete/Transform.java new file mode 100644 index 0000000..0ef82a9 --- /dev/null +++ b/src/main/java/org/spout/api/geo/discrete/Transform.java @@ -0,0 +1,324 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.discrete; + +import java.io.Serializable; + +import com.flowpowered.commons.StringUtil; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import org.spout.api.geo.World; +import org.spout.api.util.concurrent.SpinLock; + +import org.spout.math.imaginary.Quaternionf; +import org.spout.math.matrix.Matrix3f; +import org.spout.math.matrix.Matrix4f; +import org.spout.math.vector.Vector3f; + +public final class Transform implements Serializable { + private static final long serialVersionUID = 2L; + private final transient SpinLock lock = new SpinLock(); + private Point position; + private Quaternionf rotation; + private Vector3f scale; + + public Transform() { + this(Point.invalid, Quaternionf.IDENTITY, new Vector3f(1, 1, 1)); + } + + public Transform(Transform transform) { + set(transform); + } + + public Transform(Point position, Quaternionf rotation, Vector3f scale) { + this.position = position; + this.rotation = rotation; + this.scale = scale; + } + + public Point getPosition() { + try { + lock.lock(); + return position; + } finally { + lock.unlock(); + } + } + + public Transform setPosition(Point position) { + try { + lock.lock(); + this.position = position; + } finally { + lock.unlock(); + } + return this; + } + + public Transform translate(float x, float y, float z) { + return translate(new Vector3f(x, y, z)); + } + + public Transform translate(Vector3f offset) { + try { + lock.lock(); + this.position = this.position.add(offset); + } finally { + lock.unlock(); + } + return this; + } + + public Transform rotate(Quaternionf offset) { + try { + lock.lock(); + this.rotation = offset.mul(rotation); + } finally { + lock.unlock(); + } + return this; + } + + public Transform scale(Vector3f offset) { + try { + lock.lock(); + this.scale = this.scale.add(offset); + } finally { + lock.unlock(); + } + return this; + } + + public Transform translateAndSetRotation(Vector3f offset, Quaternionf rotation) { + try { + lock.lock(); + this.position = this.position.add(offset); + this.rotation = rotation; + } finally { + lock.unlock(); + } + return this; + } + + public Quaternionf getRotation() { + try { + lock.lock(); + return rotation; + } finally { + lock.unlock(); + } + } + + public Transform setRotation(Quaternionf rotation) { + try { + lock.lock(); + this.rotation = rotation; + } finally { + lock.unlock(); + } + return this; + } + + public Vector3f getScale() { + try { + lock.lock(); + return scale; + } finally { + lock.unlock(); + } + } + + public Transform setScale(Vector3f scale) { + try { + lock.lock(); + this.scale = scale; + } finally { + lock.unlock(); + } + return this; + } + + /** + * Atomically sets the value of this transform to the value of another transform + * + * @param transform the other transform + * @return this transform + */ + public Transform set(Transform transform) { + if (transform == null) { + throw new NullPointerException("Transform can not be a null argument!"); + } + + try { + SpinLock.dualLock(lock, transform.lock); + setUnsafe(transform.position, transform.rotation, transform.scale); + } finally { + SpinLock.dualUnlock(lock, transform.lock); + } + return this; + } + + /** + * Atomically sets the value of this transform. + * + * @param world the world + * @param px the x coordinate of the position + * @param py the y coordinate of the position + * @param pz the z coordinate of the position + * @param rx the x coordinate of the quaternion + * @param ry the y coordinate of the quaternion + * @param rz the z coordinate of the quaternion + * @param rw the w coordinate of the quaternion + * @param sx the x coordinate of the scale + * @param sy the y coordinate of the scale + * @param sz the z coordinate of the scale + * @return this transform + */ + public Transform set(World world, float px, float py, float pz, float rx, float ry, float rz, float rw, float sx, float sy, float sz) { + return this.set(new Point(world, px, py, pz), new Quaternionf(rx, ry, rz, rw), new Vector3f(sx, sy, sz)); + } + + /** + * Atomically sets this point to the given components + * + * @return this transform + */ + public Transform set(Point p, Quaternionf r, Vector3f s) { + try { + lock.lock(); + setUnsafe(p, r, s); + } finally { + lock.unlock(); + } + return this; + } + + private void setUnsafe(Point p, Quaternionf r, Vector3f s) { + this.position = p; + this.rotation = r; + this.scale = s; + } + + /** + * Creates a Transform that is a copy of this transform + * + * @return the snapshot + */ + public Transform copy() { + try { + lock.lock(); + return new Transform(position, rotation, scale); + } finally { + lock.unlock(); + } + } + + /** + * Gets a String representation of this transform + *

+ * Note: unsafe, could return torn values + * + * @return the string + */ + @Override + public String toString() { + return getClass().getSimpleName() + StringUtil.toString(position, rotation, scale); + } + + @Override + public int hashCode() { + HashCodeBuilder builder = new HashCodeBuilder(41, 63); + try { + lock.lock(); + builder.append(position).append(rotation).append(scale); + } finally { + lock.unlock(); + } + return builder.toHashCode(); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Transform)) { + return false; + } + Transform t = (Transform) other; + try { + SpinLock.dualLock(lock, t.lock); + return position.equals(t.position) && rotation.equals(t.rotation) && scale.equals(t.scale); + } finally { + SpinLock.dualUnlock(lock, t.lock); + } + } + + /** + * Returns the 4x4 matrix that represents this transform object + */ + public Matrix4f toMatrix() { + Matrix4f translate = Matrix4f.createTranslation(getPosition()); + Matrix4f rotate = Matrix4f.createRotation(getRotation()); + Matrix4f scale = Matrix4f.createScaling(getScale().toVector4(1)); + return scale.mul(rotate).mul(translate); + } + + /** + * Returns a unit vector that points in the forward direction of this transform + */ + public Vector3f forwardVector() { + return Matrix3f.createRotation(getRotation()).transform(Vector3f.FORWARD); + } + + /** + * Returns a unit vector that points right in relation to this transform + */ + public Vector3f rightVector() { + return Matrix3f.createRotation(getRotation()).transform(Vector3f.RIGHT); + } + + /** + * Returns a unit vector that points up in relation to this transform + */ + public Vector3f upVector() { + return Matrix3f.createRotation(getRotation()).transform(Vector3f.UP); + } + + /** + * Returns if this Transform is "empty"

Empty is defined by Position, {@link Point}, of the transform equaling {@link Point#invalid}, Rotation, {@link org.spout.math.imaginary.Quaternionf}, of the transform equaling + * {@link org.spout.math.imaginary.Quaternionf#IDENTITY}, and Scale, {@link org.spout.math.vector.Vector3f}, equaling {@link org.spout.math.vector.Vector3f#ONE}. + * + * @return True if empty, false if not + */ + public boolean isEmpty() { + try { + lock.lock(); + return position.equals(Point.invalid) && rotation.equals(Quaternionf.IDENTITY) && scale.equals(Vector3f.ONE); + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/org/spout/api/geo/discrete/Transform2D.java b/src/main/java/org/spout/api/geo/discrete/Transform2D.java new file mode 100644 index 0000000..2f96f20 --- /dev/null +++ b/src/main/java/org/spout/api/geo/discrete/Transform2D.java @@ -0,0 +1,98 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.geo.discrete; + +import org.spout.math.imaginary.Complexf; +import org.spout.math.matrix.Matrix3f; +import org.spout.math.vector.Vector2f; + +public class Transform2D { + private Vector2f position; + private Complexf rotation; + private Vector2f scale; + + public Transform2D() { + this(Vector2f.ZERO, Complexf.IDENTITY, Vector2f.ONE); + } + + public Transform2D(Vector2f position, Complexf rotation, Vector2f scale) { + this.position = position; + this.rotation = rotation; + this.scale = scale; + } + + public void add(float x, float y) { + setPosition(getPosition().add(x, y)); + } + + public void setPosition(float x, float y) { + this.position = new Vector2f(x, y); + } + + public void setPosition(Vector2f position) { + this.position = position; + } + + public Vector2f getPosition() { + return position; + } + + public void setScale(float scale) { + this.scale = new Vector2f(scale, scale); + } + + public void setScale(float scaleX, float scaleY) { + this.scale = new Vector2f(scaleX, scaleY); + } + + public void setScale(Vector2f scale) { + this.scale = scale; + } + + public Vector2f getScale() { + return scale; + } + + public void setRotation(float angle) { + this.rotation = Complexf.fromAngleDeg(angle); + } + + public void setRotation(Complexf rotation) { + this.rotation = rotation; + } + + public Complexf getRotation() { + return rotation; + } + + public Matrix3f toMatrix() { + Matrix3f rotation = Matrix3f.createRotation(this.rotation); + Matrix3f translation = Matrix3f.createTranslation(position); + Matrix3f scale = Matrix3f.createScaling(this.scale.toVector3(1)); + return scale.mul(rotation).mul(translation); + } +} diff --git a/src/main/java/org/spout/api/io/nbt/QuaternionTag.java b/src/main/java/org/spout/api/io/nbt/QuaternionTag.java new file mode 100644 index 0000000..a1dc05b --- /dev/null +++ b/src/main/java/org/spout/api/io/nbt/QuaternionTag.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.io.nbt; + +import java.util.ArrayList; +import java.util.List; + +import org.spout.math.imaginary.Quaternionf; +import org.spout.nbt.FloatTag; +import org.spout.nbt.ListTag; +import org.spout.nbt.Tag; +import org.spout.nbt.util.NBTMapper; + +public class QuaternionTag extends ListTag { + public QuaternionTag(String name, Quaternionf q) { + super(name, FloatTag.class, quaternionToList(q)); + } + + private static List quaternionToList(Quaternionf q) { + List list = new ArrayList<>(4); + list.add(new FloatTag("", q.getX())); + list.add(new FloatTag("", q.getY())); + list.add(new FloatTag("", q.getZ())); + list.add(new FloatTag("", q.getW())); + return list; + } + + @SuppressWarnings ("unchecked") + public static Quaternionf getValue(Tag tag) { + try { + return getValue((ListTag) tag); + } catch (ClassCastException e) { + return null; + } + } + + public static Quaternionf getValue(ListTag list) { + if (list == null) { + return null; + } + return getValue(list.getValue()); + } + + public static Quaternionf getValue(List list) { + if (list == null || list.size() != 4) { + return null; + } + Float x = NBTMapper.toTagValue(list.get(0), Float.class, null); + Float y = NBTMapper.toTagValue(list.get(1), Float.class, null); + Float z = NBTMapper.toTagValue(list.get(2), Float.class, null); + Float w = NBTMapper.toTagValue(list.get(3), Float.class, null); + if (x == null || y == null || z == null || w == null) { + return null; + } + return new Quaternionf(x, y, z, w); + } +} diff --git a/src/main/java/org/spout/api/io/nbt/TransformTag.java b/src/main/java/org/spout/api/io/nbt/TransformTag.java new file mode 100644 index 0000000..e61a3db --- /dev/null +++ b/src/main/java/org/spout/api/io/nbt/TransformTag.java @@ -0,0 +1,92 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.io.nbt; + +import org.spout.api.geo.World; +import org.spout.api.geo.discrete.Point; +import org.spout.api.geo.discrete.Transform; +import org.spout.math.imaginary.Quaternionf; +import org.spout.math.vector.Vector3f; +import org.spout.nbt.CompoundMap; +import org.spout.nbt.CompoundTag; +import org.spout.nbt.Tag; + +public class TransformTag extends CompoundTag { + public TransformTag(String name, Transform t) { + this(name, t.getPosition(), t.getRotation(), t.getScale()); + } + + public TransformTag(String name, float px, float py, float pz, float qx, float qy, float qz, float qw, float sx, float sy, float sz) { + this(name, new Vector3f(px, py, pz), new Quaternionf(qx, qy, qz, qw), new Vector3f(sx, sy, sz)); + } + + public TransformTag(String name, Vector3f p, Quaternionf q, Vector3f s) { + super(name, toMap(p, q, s)); + } + + private static CompoundMap toMap(Vector3f p, Quaternionf q, Vector3f s) { + CompoundMap map = new CompoundMap(); + map.put(new Vector3Tag("pos", p)); + map.put(new QuaternionTag("rot", q)); + map.put(new Vector3Tag("scale", s)); + return map; + } + + public static Transform getValue(World w, Tag tag) { + try { + return getValue(w, (CompoundTag) tag); + } catch (ClassCastException e) { + return null; + } + } + + public static Transform getValue(World w, CompoundTag map) { + if (map == null || w == null) { + return null; + } + return getValue(w, map.getValue()); + } + + public static Transform getValue(World w, CompoundMap map) { + if (map == null || w == null) { + return null; + } + Vector3f pVector = Vector3Tag.getValue(map.get("pos")); + + Quaternionf r = QuaternionTag.getValue(map.get("rot")); + + Vector3f s = Vector3Tag.getValue(map.get("scale")); + + if (pVector == null || r == null || s == null) { + return null; + } + + Point p = new Point(pVector, w); + + return new Transform(p, r, s); + } +} diff --git a/src/main/java/org/spout/api/io/nbt/UUIDTag.java b/src/main/java/org/spout/api/io/nbt/UUIDTag.java new file mode 100644 index 0000000..de06a3d --- /dev/null +++ b/src/main/java/org/spout/api/io/nbt/UUIDTag.java @@ -0,0 +1,78 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.io.nbt; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.spout.nbt.ListTag; +import org.spout.nbt.LongTag; +import org.spout.nbt.Tag; +import org.spout.nbt.util.NBTMapper; + +public class UUIDTag extends ListTag { + public UUIDTag(String name, UUID u) { + super(name, LongTag.class, UUIDToList(u)); + } + + private static List UUIDToList(UUID u) { + List list = new ArrayList<>(2); + list.add(new LongTag("", u.getMostSignificantBits())); + list.add(new LongTag("", u.getLeastSignificantBits())); + return list; + } + + @SuppressWarnings ("unchecked") + public static UUID getValue(Tag tag) { + try { + return getValue((ListTag) tag); + } catch (ClassCastException e) { + return null; + } + } + + public static UUID getValue(ListTag list) { + if (list == null) { + return null; + } + return getValue(list.getValue()); + } + + public static UUID getValue(List list) { + if (list == null || list.size() != 2) { + return null; + } + Long m = NBTMapper.toTagValue(list.get(0), Long.class, null); + Long l = NBTMapper.toTagValue(list.get(1), Long.class, null); + + if (m == null || l == null) { + return null; + } + return new UUID(m, l); + } +} diff --git a/src/main/java/org/spout/api/io/nbt/Vector3Tag.java b/src/main/java/org/spout/api/io/nbt/Vector3Tag.java new file mode 100644 index 0000000..9226112 --- /dev/null +++ b/src/main/java/org/spout/api/io/nbt/Vector3Tag.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.io.nbt; + +import java.util.ArrayList; +import java.util.List; + +import org.spout.math.vector.Vector3f; +import org.spout.nbt.FloatTag; +import org.spout.nbt.ListTag; +import org.spout.nbt.Tag; +import org.spout.nbt.util.NBTMapper; + +public class Vector3Tag extends ListTag { + public Vector3Tag(String name, Vector3f v) { + super(name, FloatTag.class, vector3ToList(v)); + } + + private static List vector3ToList(Vector3f v) { + List list = new ArrayList<>(3); + list.add(new FloatTag("", v.getX())); + list.add(new FloatTag("", v.getY())); + list.add(new FloatTag("", v.getZ())); + return list; + } + + @SuppressWarnings ("unchecked") + public static Vector3f getValue(Tag tag) { + try { + return getValue((ListTag) tag); + } catch (ClassCastException e) { + return null; + } + } + + public static Vector3f getValue(ListTag list) { + if (list == null) { + return null; + } + return getValue(list.getValue()); + } + + public static Vector3f getValue(List list) { + if (list == null || list.size() != 3) { + return null; + } + Float x = NBTMapper.toTagValue(list.get(0), Float.class, null); + Float y = NBTMapper.toTagValue(list.get(1), Float.class, null); + Float z = NBTMapper.toTagValue(list.get(2), Float.class, null); + + if (x == null || y == null || z == null) { + return null; + } + + return new Vector3f(x, y, z); + } +} diff --git a/src/main/java/org/spout/api/material/BlockMaterial.java b/src/main/java/org/spout/api/material/BlockMaterial.java new file mode 100644 index 0000000..11e2998 --- /dev/null +++ b/src/main/java/org/spout/api/material/BlockMaterial.java @@ -0,0 +1,556 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material; + +import java.util.Set; + +import com.flowpowered.commons.bytebit.ByteBitSet; +import com.flowpowered.events.Cause; + +import com.google.common.collect.ImmutableSet; + +import org.spout.api.component.block.BlockComponent; +import org.spout.api.entity.Entity; +import org.spout.api.event.cause.MaterialCause; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.discrete.Point; +import org.spout.api.material.basic.Air; +import org.spout.api.material.basic.Solid; +import org.spout.api.material.block.BlockFace; +import org.spout.api.material.block.BlockFaces; +import org.spout.math.GenericMath; +import org.spout.math.vector.Vector3f; +import org.spout.physics.ReactDefaults; +import org.spout.physics.collision.shape.CollisionShape; + +/** + * Defines the specific characteristics of a Block + */ +@SuppressWarnings ("unchecked") +public class BlockMaterial extends Material implements Placeable { + public static final BlockMaterial AIR = new Air(); + public static final BlockMaterial UNBREAKABLE = new Solid("Unbreakable"); + public static final BlockMaterial UNGENERATED = new Solid("Ungenerated").setInvisible(); + public static final BlockMaterial ERROR = new Solid("Missing Plugin"); + public static final BlockMaterial SOLID_BLUE = new Solid("SolidBlue"); + public static final BlockMaterial SOLID_BROWN = new Solid("SolidBrown"); + public static final BlockMaterial SOLID_GREEN = new Solid("SolidGreen"); + public static final BlockMaterial SOLID_LIGHTGREEN = new Solid("SolidLightGreen"); + public static final BlockMaterial SOLID_RED = new Solid("SolidRed"); + public static final BlockMaterial SOLID_SKYBLUE = new Solid("SolidSkyBlue"); + private final Set> components; + private ByteBitSet occlusion = new ByteBitSet(BlockFaces.NESWBT); + private float hardness = 0F; + private byte opacity = 0xF; + private boolean invisible = false; + //Collision + private CollisionShape shape; + private float mass = 1; + private float friction = ReactDefaults.DEFAULT_FRICTION_COEFFICIENT; + private float restitution = ReactDefaults.DEFAULT_RESTITUTION_COEFFICIENT; + private boolean isGhost = false; + + public BlockMaterial(short dataMask, String name, CollisionShape shape, Class... components) { + super(dataMask, name); + this.components = ImmutableSet.copyOf(components); + this.shape = shape; + } + + public BlockMaterial(String name, int data, Material parent, String model, CollisionShape shape, Class... components) { + super(name, data, parent, model); + this.components = ImmutableSet.copyOf(components); + this.shape = shape; + } + + protected BlockMaterial(String name, short id, CollisionShape shape, Class... components) { + super(name, id); + this.components = ImmutableSet.copyOf(components); + this.shape = shape; + } + + protected BlockMaterial(String name, CollisionShape shape, Class... components) { + super(name); + this.components = ImmutableSet.copyOf(components); + this.shape = shape; + } + + /** + * Gets the block material with the given id, or null if none found + * + * @param id to get + * @return block, or null if none found + */ + public static BlockMaterial get(short id) { + Material mat = Material.get(id); + if (!(mat instanceof BlockMaterial)) { + return null; + } + + return (BlockMaterial) mat; + } + + /** + * Gets the block (sub-)material with the given id and data, or null if none found + * + * @param id to get + * @return block, or null if none found + */ + public static BlockMaterial get(short id, short data) { + Material m = Material.get(id, data); + if (m instanceof BlockMaterial) { + return (BlockMaterial) m; + } + return null; + } + + /** + * Gets the associated block material with it's name. Case-insensitive. + * + * @param name to lookup + * @return material, or null if none found + */ + public static BlockMaterial get(String name) { + Material mat = Material.get(name); + if (!(mat instanceof BlockMaterial)) { + return null; + } + + return (BlockMaterial) mat; + } + + @Override + public BlockMaterial getSubMaterial(short data) { + return (BlockMaterial) super.getSubMaterial(data); + } + + /** + * Gets the hardness of this block + * + * @return hardness value + */ + public float getHardness() { + return this.hardness; + } + + /** + * Sets the hardness of this block + * + * @param hardness hardness value + * @return this material + */ + public BlockMaterial setHardness(float hardness) { + this.hardness = hardness; + return this; + } + + /** + * Gets the amount of light this block emits + * + * @return light level + */ + public byte getLightLevel(short data) { + return 0; + } + + /** + * Gets the amount of light this block emits + * + * @return light level + */ + public byte getLightLevel() { + return getLightLevel(getData()); + } + + /** + * Gets the amount of light blocked by this block. + * + * 0xF (15) represents a fully opaque block. + * + * @return opacity + */ + public byte getOpacity() { + return this.opacity; + } + + /** + * Returns true if the block is opaque, false if not. + * + * @return True if opacity is 15, false if less than. + */ + public boolean isOpaque() { + return this.opacity == 0xF; + } + + /** + * Sets the amount of light blocked by this block. + * + * 0xF (15) represents a fully opaque block. + * + * @param level of opacity, a value from 0 to 15 + * @return this material + */ + public BlockMaterial setOpacity(int level) { + this.opacity = (byte) GenericMath.clamp(level, 0, 15); + return this; + } + + /** + * Turns this Block Material in a fully opaque block, not letting light through from any side
Sets opacity to 15 and sets occlusion to all faces + * + * @return this Block Material + */ + public BlockMaterial setOpaque() { + this.occlusion.set(BlockFaces.NESWBT); + return this.setOpacity(15); + } + + /** + * Turns this Block Material in a fully transparent block, letting light through from all sides
Sets the opacity to 0 and sets occlusion to none + * + * @return this Block Material + */ + public BlockMaterial setTransparent() { + this.occlusion.set(BlockFaces.NONE); + return this.setOpacity(0); + } + + /** + * True if this block acts as an obstacle when placing a block on it false if not. + * + * If the block is not an obstacle, placement will replace this block. + * + * @return if this block acts as a placement obstacle + */ + public boolean isPlacementObstacle() { + return true; + } + + /** + * True if this block requires physic updates when a neighbor block changes, false if not. + * + * @return if this block requires physics updates + */ + public boolean hasPhysics() { + return false; + } + + /** + * Called when a block near to this material is changed.
+ * + * @param oldMaterial the previous material, or null if the update was not due to a material change + * @param block that got updated + * @return true if the block was updated + */ + public void onUpdate(BlockMaterial oldMaterial, Block block) { + } + + /** + * Performs the block destroy procedure + * + * @param block to destroy + * @return True if destroying was successful + */ + public boolean destroy(Block block, Cause cause) { + if (this.onDestroy(block, cause)) { + this.onPostDestroy(block); + return true; + } + return false; + } + + /** + * Called when this block has to be destroyed.
This function performs the actual destruction of the block. + * + * @param block that got destroyed + * @return true if the destruction occurred + */ + public boolean onDestroy(Block block, Cause cause) { + return block.setMaterial(AIR, cause); + } + + /** + * Called after this block has been destroyed.
This function performs possible post-destroy operations, such as effects. + * + * @param block of the material that got destroyed + */ + public void onPostDestroy(Block block) { + } + + /** + * Gets the occluded faces of this Block Material for the data value specified
Occluded faces do not let light though and require rendering behind it at those faces + * + * @param data value of the material + * @return the occluded faces + */ + public ByteBitSet getOcclusion(short data) { + return this.occlusion; + } + + /** + * Sets the occludes faces of this Block Material
Occluded faces do not let light though and require rendering behind it at those faces + * + * @param data of this Block Material + * @param faces to make this Block Material occlude + * @return this Block Material + */ + public BlockMaterial setOcclusion(short data, BlockFaces faces) { + this.getOcclusion(data).set(faces); + return this; + } + + /** + * Sets the occludes face of this Block Material
Occluded faces do not let light though and require rendering behind it at those faces + * + * @param data of this Block Material + * @param face to make this Block Material occlude + * @return this Block Material + */ + public BlockMaterial setOcclusion(short data, BlockFace face) { + this.getOcclusion(data).set(face); + return this; + } + + /** + * Gets if the a face should be rendered + * + * @param face the fact to render + * @param material the material of the neighbouring block + */ + public boolean isFaceRendered(BlockFace face, BlockMaterial material) { + return true; + } + + @Override + public boolean canPlace(Block block, short data, BlockFace against, Vector3f clickedPos, boolean isClickedBlock, Cause cause) { + return canCreate(block, data, cause); + } + + @Override + public void onPlacement(Block block, short data, BlockFace against, Vector3f clickedPos, boolean isClickedBlock, Cause cause) { + this.onCreate(block, data, cause); + } + + /** + * Checks the block to see if it can be created at that position
Orientation-specific checks are performed in the {@link #canPlace(org.spout.api.geo.cuboid.Block, short, org.spout.api.material.block.BlockFace, org.spout.math.vector.Vector3f, boolean, org.spout.api.event.Cause)} method
Use this method to see if creation is possible at a + * given position when not placed + * + * @param block this Block Material should be created in + * @param data for the material + * @param cause of this creation + * @return True if creation is possible, False if not + */ + public boolean canCreate(Block block, short data, Cause cause) { + return true; + } + + /** + * Creates this Block Material at a block in the world
Orientation-specific changes are performed in the {@link #onPlacement(org.spout.api.geo.cuboid.Block, short, org.spout.api.material.block.BlockFace, org.spout.math.vector.Vector3f, boolean, org.spout.api.event.Cause)} method
Use this method to create the block at a given position + * when not placed + * + * @param block to create this Block Material in + * @param data for the material + * @param cause of this creation + */ + public void onCreate(Block block, short data, Cause cause) { + block.setMaterial(this, data, cause); + } + + /** + * Returns true if the block is completely invisible + * + * @return True if the block should never be rendered + */ + public boolean isInvisible() { + return this.invisible; + } + + /** + * Turns this material invisible and sets it as non-occluding. Invisible blocks are not rendered. + */ + public BlockMaterial setInvisible() { + this.invisible = true; + this.occlusion.set(BlockFaces.NONE); + return this; + } + + /** + * Returns true if the block is transparent, false if not. + * + * @return True if opacity is 0, false if more than. + */ + public boolean isTransparent() { + return this.opacity == 0; + } + + /** + * Called by the dynamic block update system. If a material is changed into a material that it is not compatible with, then this will automatically trigger a block reset. + * + * @param m the other material + * @return true if the two materials are compatible + */ + public boolean isCompatibleWith(BlockMaterial m) { + return (m.getId() == getId() && ((m.getData() ^ getData()) & getDataMask()) == 0); + } + + /** + * Helper method to create a MaterialCause. + * + * Same as using new MaterialCause(material, block) + * + * @param block location of the event + * @return cause + */ + public Cause toCause(Block block) { + return new MaterialCause<>(this, block); + } + + /** + * Helper method to create a MaterialCause. + * + * Same as using new MaterialCause(material, block) + * + * @param p location of the event + * @return cause + */ + public Cause toCause(Point p) { + return new MaterialCause<>(this, p.getWorld().getBlock(p)); + } + + public Set> getComponents() { + return components; + } + + /** + * Returns if this BlockMaterial is a ghost object. + * + * @return True if ghost, false if not + */ + public boolean isGhost() { + return isGhost; + } + + /** + * Sets if this BlockMaterial should be a detector "ghost" material.

This means any collisions with this BlockMaterial will not incur adjustments for the {@link Entity} which collided: instead + * callbacks will be alerted and the Entity will be able to move freely through this BlockMaterial (by default).

If this BlockMaterial has a null {@link CollisionShape}, this setting will have no + * effect until a reference is set. + */ + public void setGhost(final boolean isGhost) { + this.isGhost = isGhost; + } + + /** + * Get the mass of this BlockMaterial + * + * @return The mass + */ + public float getMass() { + return mass; + } + + /** + * Sets the mass of this BlockMaterial + * + * @param mass The new mass + * @return This material, for chaining + * @throws IllegalArgumentException If provided mass is < 1f + */ + public BlockMaterial setMass(final float mass) { + if (mass < 1) { + throw new IllegalArgumentException("Mass must be greater than or equal to 1f"); + } + this.mass = mass; + return this; + } + + /** + * Get the friction of this BlockMaterial + * + * @return The friction + */ + public float getFriction() { + return friction; + } + + /** + * Sets the friction of this BlockMaterial + * + * @param friction The new friction + * @return This material, for chaining + * @throws IllegalArgumentException If provided friction is < 0f + */ + public BlockMaterial setFriction(final float friction) { + if (friction < 0 || friction > 1) { + throw new IllegalArgumentException("Friction must be between 0 and 1 (inclusive)"); + } + this.friction = friction; + return this; + } + + /** + * Get the restitution of this BlockMaterial + * + * @return The restitution + */ + public float getRestitution() { + return restitution; + } + + /** + * Sets the restitution of this BlockMaterial + * + * @param restitution The new restitution + * @return This material, for chaining + * @throws IllegalArgumentException If provided restitution is < 0f + */ + public BlockMaterial setRestitution(final float restitution) { + if (restitution < 0 || restitution > 1) { + throw new IllegalArgumentException("Restitution must be between 0 and 1 (inclusive)"); + } + this.restitution = restitution; + return this; + } + + /** + * Gets the {@link CollisionShape} this BlockMaterial has. + * + * @return the collision shape + */ + public CollisionShape getShape() { + return shape; + } + + /** + * Sets the {@link CollisionShape} this BlockMaterial has/ + * + * @param shape The new collision shape + * @return This material, for chaining + */ + public BlockMaterial setShape(final CollisionShape shape) { + this.shape = shape; + return this; + } +} diff --git a/src/main/java/org/spout/api/material/Material.java b/src/main/java/org/spout/api/material/Material.java new file mode 100644 index 0000000..93d48b0 --- /dev/null +++ b/src/main/java/org/spout/api/material/Material.java @@ -0,0 +1,356 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material; + +/** + * Defines the characteristics of Blocks or Items. + */ + +import com.flowpowered.commons.LogicUtil; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +import org.spout.math.GenericMath; + +public abstract class Material extends MaterialRegistry { + private final short id; + private final short data; + private final String name; + private final boolean isSubMaterial; + private final Material parent; + private final Material root; + private String displayName; + private int maxStackSize = 64; + private short maxData = Short.MAX_VALUE; + private final AtomicReference subMaterials; + private Material[] submaterialsContiguous = null; + private volatile boolean submaterialsDirty = true; + private final short dataMask; + + /** + * Creates a material with a dataMask, name + */ + public Material(short dataMask, String name) { + this.isSubMaterial = false; + this.displayName = name; + this.name = getClass().getCanonicalName() + "_" + name.replace(' ', '_'); + this.parent = this; + this.data = 0; + this.id = (short) MaterialRegistry.register(this); + this.subMaterials = MaterialRegistry.getSubMaterialReference(this.id); + this.dataMask = dataMask; + this.root = this; + } + + /** + * Creates and registers a material + * + * @param name of the material + */ + public Material(String name) { + this((short) 0, name); + } + + /** + * Creates and registers a sub material + * + * @param name of the material + * @param parent material + */ + public Material(String name, int data, Material parent) { + this(name, data, parent, null); + } + + /** + * Creates and registers a sub material + * + * @param name of the material + * @param parent material + */ + public Material(String name, int data, Material parent, String model) { + this.isSubMaterial = true; + this.displayName = name; + this.name = name.replace(' ', '_'); + this.parent = parent; + this.data = (short) data; + this.id = (short) MaterialRegistry.register(this); + this.subMaterials = MaterialRegistry.getSubMaterialReference(this.id); + this.dataMask = parent.getDataMask(); + this.root = parent.getRoot(); + } + + /** + * Creates a material with a reserved id + * + * @param name of the material + * @param id to reserve + */ + protected Material(String name, short id) { + this.isSubMaterial = false; + this.displayName = name; + this.name = name.replace(' ', '_'); + this.parent = this; + this.data = 0; + this.id = (short) MaterialRegistry.register(this, id); + this.subMaterials = MaterialRegistry.getSubMaterialReference(this.id); + this.dataMask = 0; + this.root = this; + } + + public final short getId() { + return this.id; + } + + /** + * Gets the data value associated with this material. if this material does not have or is not a sub material, then (getData() & getDataMask()) is equal to zero. + * + * @return data value + */ + public final short getData() { + return this.data; + } + + /** + * Gets the data mask for this material, and sub-materials. When determining sub-material, this mask is applied to the data before the comparison is performed. + * + * @return data mask + */ + public final short getDataMask() { + return this.dataMask; + } + + /** + * Checks if this material is a sub material or not + * + * @return true if it is a sub material + */ + public final boolean isSubMaterial() { + return isSubMaterial; + } + + /** + * Checks if this material has other materials mapped by data + * + * @return true if this material has sub materials + */ + public final boolean hasSubMaterials() { + return this.subMaterials.get().length > 1; + } + + /** + * Gets all sub materials of this material + * + * @return an array of sub materials + */ + public final Material[] getSubMaterials() { + if (submaterialsDirty) { + int materialCount = 0; + Material[] sm = subMaterials.get(); + for (int i = 0; i < sm.length; i++) { + if (sm[i] != null) { + materialCount++; + } + } + Material[] newSubmaterials = new Material[materialCount]; + materialCount = 0; + for (int i = 0; i < sm.length; i++) { + if (sm[i] != null) { + newSubmaterials[materialCount++] = sm[i]; + } + } + this.submaterialsContiguous = newSubmaterials; + submaterialsDirty = false; + } + Material[] sm = submaterialsContiguous; + return Arrays.copyOf(sm, sm.length); + } + + /** + * Recursively gets the sub material mapped to the data value specified + * + * @param data to search for + * @return the sub material, or this material if not found + */ + public Material getSubMaterial(short data) { + short maskedData = (short) (data & dataMask); + return subMaterials.get()[maskedData]; + } + + /** + * Registers the sub material for this material + * + * @param material to register + */ + public final void registerSubMaterial(Material material) { + submaterialsDirty = true; + try { + int data = material.data & 0xFFFF; + if ((data & dataMask) != data) { + throw new IllegalArgumentException("Sub material of: " + material.getId() + " with data value: " + data + " is outside data mask: " + Integer.toHexString(dataMask)); + } + if (material.isSubMaterial) { + if (material.getParentMaterial() == this) { + boolean success = false; + while (!success) { + Material[] sm = subMaterials.get(); + if (data >= sm.length) { + int newSize = GenericMath.roundUpPow2(data + (data >> 1) + 1); + Material[] newSubmaterials = new Material[newSize]; + System.arraycopy(sm, 0, newSubmaterials, 0, sm.length); + success = subMaterials.compareAndSet(sm, newSubmaterials); + } else { + success = true; + } + } + Material[] sm = subMaterials.get(); + if (sm[data] == null) { + sm[data] = material; + } else { + throw new IllegalArgumentException("Two sub material registered for the same data value"); + } + } else { + throw new IllegalArgumentException("Sub Material is registered to a material different than the parent!"); + } + } else { + throw new IllegalArgumentException("Material is not a valid sub material!"); + } + } finally { + submaterialsDirty = true; + } + } + + /** + * Gets the parent of this sub material + * + * @return the material of the parent + */ + public Material getParentMaterial() { + return this.parent; + } + + /** + * Gets the root parent of this sub material + * + * @return the material root + */ + public Material getRoot() { + return this.root; + } + + /** + * Gets the name of this material + * + * @return the name + */ + public final String getName() { + return this.name; + } + + /** + * Gets the display name of this material + * + * @return the display name + */ + public final String getDisplayName() { + return this.displayName; + } + + /** + * Sets the display name of this material + * + * @param name the new display name + */ + public final void setDisplayName(String name) { + this.displayName = name; + } + + /** + * Gets the maximum size of a stack of this material + * + * @return the current max size + */ + public final int getMaxStackSize() { + return this.maxStackSize; + } + + /** + * Sets the maximum size of a stack of this material + * + * @param newValue the new maximum stack size + */ + public final void setMaxStackSize(int newValue) { + this.maxStackSize = newValue; + } + + /** + * Gets the maximum data a stack of this material can have + */ + public final short getMaxData() { + return this.maxData; + } + + /** + * Sets the maximum of the data value this material can have + * + * @param newValue the new maximum data + */ + public final void setMaxData(short newValue) { + this.maxData = newValue; + } + + public boolean isMaterial(Material... materials) { + if (LogicUtil.equalsAny(this, materials)) { + return true; + } + if (this.getRoot() != this && LogicUtil.equalsAny(this.getRoot(), materials)) { + return true; + } + return false; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Material) { + return other == this; + } else { + return false; + } + } + + @Override + public String toString() { + return "Material {" + getName() + "}"; + } + + /** + * Indicates that the dataMask covers the least significant bits.

This method is used when verifying that the dataMask is set correctly + */ + public boolean hasLSBDataMask() { + return true; + } +} diff --git a/src/main/java/org/spout/api/material/MaterialRegistry.java b/src/main/java/org/spout/api/material/MaterialRegistry.java new file mode 100644 index 0000000..0b6d6c8 --- /dev/null +++ b/src/main/java/org/spout/api/material/MaterialRegistry.java @@ -0,0 +1,267 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material; + +import com.flowpowered.commons.store.MemoryStore; + +import java.io.File; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +import org.spout.api.Server; +import org.spout.api.Spout; +import org.spout.api.material.block.BlockFullState; +import org.spout.api.store.BinaryFileStore; +import org.spout.api.util.SyncedStringMap; +import org.spout.math.GenericMath; + +/** + * Handles all registered materials on the server statically. + */ +public abstract class MaterialRegistry { + private final static ConcurrentHashMap nameLookup = new ConcurrentHashMap<>(1000); + private final static int MAX_SIZE = 1 << 16; + @SuppressWarnings ("unchecked") + private final static AtomicReference[] materialLookup = new AtomicReference[MAX_SIZE]; + private static boolean setup = false; + private static SyncedStringMap materialRegistry; + private final static Material[] NULL_MATERIAL_ARRAY = new Material[] {null}; + + static { + for (int i = 0; i < materialLookup.length; i++) { + materialLookup[i] = new AtomicReference<>(); + materialLookup[i].set(NULL_MATERIAL_ARRAY); + } + } + + /** + * Sets up the material registry for its first use. May not be called more than once.
This attempts to load the materials.dat file from the 'worlds' directory into memory.
+ * + * Can throw an {@link IllegalStateException} if the material registry has already been setup. + * + * @return StringToUniqueIntegerMap of registered materials + */ + public static SyncedStringMap setupRegistry() { + if (setup) { + throw new IllegalStateException("Can not setup material registry twice!"); + } + if (Spout.getPlatform().isServer()) { + setupServer(); + } else { + setupClient(); + } + + setup = true; + return materialRegistry; + } + + private static void setupServer() { + File serverItemMap = new File(((Server) Spout.getEngine()).getWorldManager().getWorldFolder(), "materials.dat"); + BinaryFileStore store = new BinaryFileStore(serverItemMap); + materialRegistry = SyncedStringMap.create(null, store, 1, Short.MAX_VALUE, Material.class.getName()); + if (serverItemMap.exists()) { + store.load(); + } + } + + private static void setupClient() { + materialRegistry = SyncedStringMap.create(null, new MemoryStore(), 1, Short.MAX_VALUE, Material.class.getName()); + } + + /** + * Registers the material in the material lookup service + * + * @param material to register + * @return id of the material registered + */ + protected static int register(Material material) { + if (material.isSubMaterial()) { + material.getParentMaterial().registerSubMaterial(material); + nameLookup.put(formatName(material.getDisplayName()), material); + return material.getParentMaterial().getId(); + } else { + int id = materialRegistry.register(material.getName()); + Material[] subArray = new Material[] {material}; + if (!materialLookup[id].compareAndSet(NULL_MATERIAL_ARRAY, subArray)) { + throw new IllegalArgumentException(materialLookup[id].get() + " is already mapped to id: " + material.getId() + "!"); + } + + nameLookup.put(formatName(material.getDisplayName()), material); + return id; + } + } + + protected static AtomicReference getSubMaterialReference(short id) { + return materialLookup[id]; + } + + /** + * Registers the material in the material lookup service + * + * @param material to register + * @return id of the material registered. + */ + protected static int register(Material material, int id) { + materialRegistry.register(material.getName(), id); + Material[] subArray = new Material[] {material}; + if (!materialLookup[id].compareAndSet(NULL_MATERIAL_ARRAY, subArray)) { + throw new IllegalArgumentException(materialLookup[id].get()[0] + " is already mapped to id: " + material.getId() + "!"); + } + + nameLookup.put(formatName(material.getName()), material); + return id; + } + + /** + * Gets the material from the given id + * + * @param id to get + * @return material or null if none found + */ + public static Material get(short id) { + if (id < 0 || id >= materialLookup.length) { + return null; + } + return materialLookup[id].get()[0]; + } + + /** + * Gets the material from the given id and data + * + * @param id to get + * @param data to get + * @return material or null if none found + */ + public static Material get(short id, short data) { + if (id < 0 || id >= materialLookup.length) { + return null; + } + Material[] parent = materialLookup[id].get(); + if (parent[0] == null) { + return null; + } + + data &= parent[0].getDataMask(); + return materialLookup[id].get()[data]; + } + + /** + * Gets the material for the given BlockFullState + * + * @param state the full state of the block + * @return Material of the BlockFullState + */ + public static Material get(BlockFullState state) { + return get(state.getPacked()); + } + + /** + * Gets the material for the given packed full state + * + * @param state the full state of the block + * @return Material of the id + */ + public static BlockMaterial get(int packedState) { + short id = BlockFullState.getId(packedState); + if (id < 0 || id >= materialLookup.length) { + return null; + } + Material[] material = materialLookup[id].get(); + if (material[0] == null) { + return null; + } + return (BlockMaterial) material[BlockFullState.getData(packedState) & (material[0].getDataMask())]; + } + + /** + * Returns all current materials in the game + * + * @return an array of all materials + */ + public static Material[] values() { + //TODO: This is wrong, need to count # of registered materials + HashSet set = new HashSet<>(1000); + for (int i = 0; i < materialLookup.length; i++) { + if (materialLookup[i].get() != null) { + set.add(materialLookup[i].get()[0]); + } + } + return set.toArray(new Material[0]); + } + + /** + * Gets the associated material with its name. Case-insensitive. + * + * @param name to lookup + * @return material, or null if none found + */ + public static Material get(String name) { + return nameLookup.get(formatName(name)); + } + + /** + * Returns a human legible material name from the full material. + * + * This will strip any '_' and replace with spaces, strip out extra whitespace, and lowercase the material name. + * + * @return human legible name of the material. + */ + private static String formatName(String matName) { + return matName.trim().replaceAll(" ", "_").toLowerCase(); + } + + /** + * Gets the minimum data mask required to account for all sub-materials of the material + * + * @param m the material + * @return the minimum data mask + */ + public static short getMinimumDatamask(Material m) { + Material root = m; + while (root.isSubMaterial()) { + root = m.getParentMaterial(); + } + + if (root.getData() != 0) { + throw new IllegalStateException("Root materials must have data set to zero"); + } + Material[] subMaterials = root.getSubMaterials(); + + short minimumMask = 0; + + for (Material sm : subMaterials) { + minimumMask |= sm.getData() & 0xFFFF; + } + + if (m.hasLSBDataMask()) { + minimumMask = (short) (GenericMath.roundUpPow2(minimumMask + 1) - 1); + } + + return minimumMask; + } +} diff --git a/src/main/java/org/spout/api/material/Placeable.java b/src/main/java/org/spout/api/material/Placeable.java new file mode 100644 index 0000000..09d6936 --- /dev/null +++ b/src/main/java/org/spout/api/material/Placeable.java @@ -0,0 +1,63 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material; + +import com.flowpowered.events.Cause; + +import org.spout.api.geo.cuboid.Block; +import org.spout.api.material.block.BlockFace; +import org.spout.math.vector.Vector3f; + +/** + * An interface defining a {@link Material} that can be placed + */ +public interface Placeable { + /** + * Called when this block is about to be placed (before {@link #onPlacement(Block, short, BlockFace, boolean)}), checking if placement is allowed or not. + * + * @param block to place + * @param data block data to use during placement + * @param against face against the block is placed + * @param isClickedBlock whether the block is to be placed at the clicked block + * @param cause the cause of the placement + * @return true if placement is allowed + */ + public boolean canPlace(Block block, short data, BlockFace against, Vector3f clickedPos, boolean isClickedBlock, Cause cause); + + /** + * Called when this block is placed, handling the actual placement
This method should only change properties that rely on the face it is placed against, or in what way it is placed. All other + * logic should be performed in onCreate. + * + * @param block to affect + * @param data block data to use during placement + * @param against face against the block is placed + * @param clickedPos relative position the block was clicked to place this block + * @param isClickedBlock whether the block is being placed at the clicked block + * @param cause the cause of the placement + */ + public void onPlacement(Block block, short data, BlockFace against, Vector3f clickedPos, boolean isClickedBlock, Cause cause); +} diff --git a/src/main/java/org/spout/api/material/basic/Air.java b/src/main/java/org/spout/api/material/basic/Air.java new file mode 100644 index 0000000..11ebab1 --- /dev/null +++ b/src/main/java/org/spout/api/material/basic/Air.java @@ -0,0 +1,50 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material.basic; + +import com.flowpowered.events.Cause; +import org.spout.api.geo.cuboid.Block; + +import org.spout.api.material.BlockMaterial; + +public final class Air extends BlockMaterial { + @SuppressWarnings ("unchecked") + public Air() { + super("Air", (short) 0, null); + this.setTransparent().setInvisible(); + } + + @Override + public boolean isPlacementObstacle() { + return false; + } + + @Override + public boolean onDestroy(Block block, Cause cause) { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/api/material/basic/Solid.java b/src/main/java/org/spout/api/material/basic/Solid.java new file mode 100644 index 0000000..201391f --- /dev/null +++ b/src/main/java/org/spout/api/material/basic/Solid.java @@ -0,0 +1,39 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material.basic; + +import org.spout.api.material.BlockMaterial; + +import org.spout.physics.collision.shape.BoxShape; +import org.spout.physics.math.Vector3; + +public class Solid extends BlockMaterial { + public Solid(String name) { + super((short) 0, name, new BoxShape(new Vector3(1f, 1f, 1f))); + setHardness(100); + } +} diff --git a/src/main/java/org/spout/api/material/block/BlockFace.java b/src/main/java/org/spout/api/material/block/BlockFace.java new file mode 100644 index 0000000..77f3c6b --- /dev/null +++ b/src/main/java/org/spout/api/material/block/BlockFace.java @@ -0,0 +1,142 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material.block; + +import com.flowpowered.commons.bytebit.ByteBitMask; + +import java.io.Serializable; + +import gnu.trove.map.hash.TIntObjectHashMap; + +import org.spout.math.imaginary.Quaternionf; +import org.spout.math.vector.Vector3f; +import org.spout.math.vector.Vector3i; + +/** + * Indicates the facing of a Block + */ +public enum BlockFace implements ByteBitMask, Serializable { + TOP(0x1, 0, 1, 0, Quaternionf.fromAngleDegAxis(-90, 1, 0, 0)), + BOTTOM(0x2, 0, -1, 0, Quaternionf.fromAngleDegAxis(90, 1, 0, 0), TOP), + NORTH(0x4, -1, 0, 0, Quaternionf.fromAngleDegAxis(-90, 0, 1, 0)), + SOUTH(0x8, 1, 0, 0, Quaternionf.fromAngleDegAxis(90, 0, 1, 0), NORTH), + EAST(0x10, 0, 0, -1, Quaternionf.fromAngleDegAxis(180, 0, 1, 0)), + WEST(0x20, 0, 0, 1, Quaternionf.fromAngleDegAxis(0, 0, 1, 0), EAST), + THIS(0x40, 0, 0, 0, Quaternionf.IDENTITY); + private final byte mask; + private final Vector3f offset; + private final Vector3i intOffset; + private final Quaternionf direction; + private BlockFace opposite = this; + private static final TIntObjectHashMap OFFSET_MAP = new TIntObjectHashMap<>(7); + private static final long serialVersionUID = 1L; + + static { + for (BlockFace face : values()) { + OFFSET_MAP.put(getOffsetHash(face.getOffset()), face); + } + } + + private BlockFace(int mask, int dx, int dy, int dz, Quaternionf direction, BlockFace opposite) { + this(mask, dx, dy, dz, direction); + this.opposite = opposite; + opposite.opposite = this; + } + + private BlockFace(int mask, int dx, int dy, int dz, Quaternionf direction) { + this.offset = new Vector3f(dx, dy, dz); + this.intOffset = new Vector3i(dx, dy, dz); + this.direction = direction; + this.mask = (byte) mask; + } + + private static int getOffsetHash(Vector3f offset) { + int x = offset.getFloorX(); + int y = offset.getFloorY(); + int z = offset.getFloorZ(); + x += 1; + y += 1; + z += 1; + return x | y << 2 | z << 4; + } + + /** + * Represents the rotation of the BlockFace in the world as a Quaternion. This is the rotation form the west face to this face. + * + * @return the direction of the blockface. + */ + public Quaternionf getDirection() { + return this.direction; + } + + /** + * Represents the directional offset of this Blockface as a Vector3. + * + * @return the offset of this directional. + */ + public Vector3f getOffset() { + return this.offset; + } + + /** + * Represents the directional offset of this Blockface as an IntVector3. + * + * @return the offset of this directional. + */ + public Vector3i getIntOffset() { + return this.intOffset; + } + + /** + * Gets the opposite BlockFace. If this BlockFace has no opposite the method will return itself. + * + * @return the opposite BlockFace, or this if it has no opposite. + */ + public BlockFace getOpposite() { + return this.opposite; + } + + @Override + public byte getMask() { + return this.mask; + } + + /** + * Uses a yaw angle to get the north, east, west or south face which points into the same direction. + * + * @param yaw to use + * @return the block face + */ + public static BlockFace fromYaw(float yaw) { + return BlockFaces.WSEN.get(Math.round(yaw / 90f) & 0x3); + } + + public static BlockFace fromOffset(Vector3f offset) { + offset = offset.normalize().round(); + return OFFSET_MAP.get(getOffsetHash(offset)); + } +} diff --git a/src/main/java/org/spout/api/material/block/BlockFaces.java b/src/main/java/org/spout/api/material/block/BlockFaces.java new file mode 100644 index 0000000..2185e7e --- /dev/null +++ b/src/main/java/org/spout/api/material/block/BlockFaces.java @@ -0,0 +1,378 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material.block; + +import com.flowpowered.commons.bytebit.ByteBitMask; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Random; + +import gnu.trove.map.hash.TByteObjectHashMap; + +import org.spout.math.vector.Vector3f; + +/** + * Contains several BlockFace array constants and functions to operate on them + */ +public class BlockFaces implements Iterable, ByteBitMask { + private static final TByteObjectHashMap offsetHash = new TByteObjectHashMap<>(); + + static { + for (BlockFace face1 : new BlockFace[] {BlockFace.THIS, BlockFace.TOP, + BlockFace.BOTTOM}) { + for (BlockFace face2 : new BlockFace[] {BlockFace.THIS, + BlockFace.WEST, BlockFace.EAST}) { + for (BlockFace face3 : new BlockFace[] {BlockFace.THIS, + BlockFace.NORTH, BlockFace.SOUTH}) { + BlockFaces faces = new BlockFaces(face1, face2, face3); + Vector3f offset = faces.getOffset(); + byte hash = getOffsetHash(offset); + offsetHash.put(hash, faces); + } + } + } + } + + /** + * The [top-bottom] faces + */ + public static final BlockFaces TB = new BlockFaces(BlockFace.TOP, BlockFace.BOTTOM); + /** + * The [bottom-top] faces + */ + public static final BlockFaces BT = new BlockFaces(BlockFace.BOTTOM, BlockFace.TOP); + /** + * The [north-south] faces + */ + public static final BlockFaces NS = new BlockFaces(BlockFace.NORTH, BlockFace.SOUTH); + /** + * The [east-west] faces + */ + public static final BlockFaces EW = new BlockFaces(BlockFace.EAST, BlockFace.WEST); + /** + * The [north-east-south-west] faces + */ + public static final BlockFaces NESW = new BlockFaces(BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST); + /** + * The [north-south-east-west] faces + */ + public static final BlockFaces NSEW = new BlockFaces(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST); + /** + * The [east-west-south-north] faces + */ + public static final BlockFaces EWSN = new BlockFaces(BlockFace.EAST, BlockFace.WEST, BlockFace.SOUTH, BlockFace.NORTH); + /** + * The [north-south-west-east] faces + */ + public static final BlockFaces NSWE = new BlockFaces(BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST); + /** + * The [south-west-north-east] faces + */ + public static final BlockFaces SWNE = new BlockFaces(BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST); + /** + * The [west-north-east-south] faces + */ + public static final BlockFaces WNES = new BlockFaces(BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH); + /** + * The [west-south-east-north] faces + */ + public static final BlockFaces WSEN = new BlockFaces(BlockFace.WEST, BlockFace.SOUTH, BlockFace.EAST, BlockFace.NORTH); + /** + * The [south-north-east-west] faces + */ + public static final BlockFaces SNEW = new BlockFaces(BlockFace.SOUTH, BlockFace.NORTH, BlockFace.EAST, BlockFace.WEST); + /** + * The [west-east-south-north] faces + */ + public static final BlockFaces WESN = new BlockFaces(BlockFace.WEST, BlockFace.EAST, BlockFace.SOUTH, BlockFace.NORTH); + /** + * The [south-north-west-east] faces + */ + public static final BlockFaces SNWE = new BlockFaces(BlockFace.SOUTH, BlockFace.NORTH, BlockFace.WEST, BlockFace.EAST); + /** + * The [east-south-west-north] faces + */ + public static final BlockFaces ESWN = new BlockFaces(BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH); + /** + * The [east-west-north-south] faces + */ + public static final BlockFaces EWNS = new BlockFaces(BlockFace.EAST, BlockFace.WEST, BlockFace.NORTH, BlockFace.SOUTH); + /** + * The [north-east-south-west-bottom] faces + */ + public static final BlockFaces NESWB = NESW.append(BlockFace.BOTTOM); + /** + * The [east-west-south-north-bottom] faces + */ + public static final BlockFaces EWSNB = EWSN.append(BlockFace.BOTTOM); + /** + * The [north-east-south-west-top] faces + */ + public static final BlockFaces NESWT = NESW.append(BlockFace.TOP); + /** + * The [north-east-south-west-bottom-top] faces + */ + public static final BlockFaces NESWBT = NESW.append(BT); + /** + * The [bottom-top-east-west-north-south] faces + */ + public static final BlockFaces BTEWNS = BT.append(EWNS); + /** + * The [bottom-top-north-south-west-east] faces + */ + public static final BlockFaces BTNSWE = BT.append(NSWE); + /** + * The [north-south-east-west-bottom] faces + */ + public static final BlockFaces NSEWB = NSEW.append(BlockFace.BOTTOM); + /** + * The [north-south-west-east-bottom] faces + */ + public static final BlockFaces NSWEB = NSWE.append(BlockFace.BOTTOM); + /** + * The [north-east-south-west-bottom-this] faces + */ + public static final BlockFaces NESWBTHIS = NESWB.append(BlockFace.THIS); + /** + * The [top-bottom-north-south-east-west-this] faces + */ + public static final BlockFaces ALL = new BlockFaces(BlockFace.values()); + /** + * A constant containing no faces at all + */ + public static final BlockFaces NONE = new BlockFaces(); + private final byte mask; + private final BlockFace[] faces; + private final Vector3f offset; + + public BlockFaces(BlockFace... blockfaces) { + this.faces = blockfaces; + byte mask = 0; + Vector3f offsetc = Vector3f.ZERO; + for (BlockFace face : this.faces) { + offsetc = offsetc.add(face.getOffset()); + mask |= face.getMask(); + } + offset = offsetc; + this.mask = mask; + } + + @Override + public byte getMask() { + return this.mask; + } + + public Vector3f getOffset() { + return offset; + } + + @Override + public Iterator iterator() { + return Arrays.asList(this.faces).iterator(); + } + + /** + * Gets the total amount of BlockFace objects contained in this constant + * + * @return the amount of BlockFace objects + */ + public int size() { + return this.faces.length; + } + + /** + * Appends another array of block faces to this BlockFaces object + * + * @param blockFaces to append + * @return a new BlockFaces object with the faces appended + */ + public BlockFaces append(BlockFaces blockFaces) { + return this.append(blockFaces.faces); + } + + /** + * Appends another array of block faces to this BlockFaces object + * + * @param blockFaces to append + * @return a new BlockFaces object with the faces appended + */ + public BlockFaces append(BlockFace... blockFaces) { + BlockFace[] faces = new BlockFace[this.faces.length + blockFaces.length]; + System.arraycopy(this.faces, 0, faces, 0, this.faces.length); + System.arraycopy(blockFaces, 0, faces, this.faces.length, blockFaces.length); + return new BlockFaces(faces); + } + + /** + * Checks if this block face constant contains the face given + * + * @param face to look for + * @return True if found, False if not + */ + public boolean contains(BlockFace face) { + for (BlockFace bface : this.faces) { + if (bface == face) { + return true; + } + } + return false; + } + + /** + * Gets the index of a face in this constant + * + * @param face to get the index of + * @param def value to return if not found + * @return the index in the constant, or the def value if not found + */ + public int indexOf(BlockFace face, int def) { + for (int i = 0; i < this.faces.length; i++) { + if (this.faces[i] == face) { + return i; + } + } + return def; + } + + /** + * Gets the previous BlockFace in this circular BlockFaces constant
This function calls previous using an offset of 1 + * + * @param from the BlockFace to count + * @return the face at the offset + * @see BlockFaces.previous(BlockFace from, int offset); + */ + public BlockFace previous(BlockFace from) { + return previous(from, 1); + } + + /** + * Gets the previous BlockFace in this circular BlockFaces constant
This function calls next using a negative offset + * + * @param from the BlockFace to count + * @param offset index in this range + * @return the face at the offset + * @see BlockFaces.next(BlockFace from, int offset); + */ + public BlockFace previous(BlockFace from, int offset) { + return this.next(from, -offset); + } + + /** + * Gets the next BlockFace in this circular BlockFaces constant
This function calls next using an offset of 1 + * + * @param from the BlockFace to count + * @return the face at the offset + * @see BlockFaces.next(BlockFace from, int offset); + */ + public BlockFace next(BlockFace from) { + return next(from, 1); + } + + /** + * Gets the next BlockFace in this circular BlockFaces constant

+ * + * For example:
BlockFaces.NESW.next(BlockFace.EAST, 2) == BlockFace.WEST
BlockFaces.NESW.next(BlockFace.WEST, 1) == BlockFace.NORTH
BlockFaces.NESW.next(BlockFace.SOUTH, -3) == + * BlockFace.WEST
+ * + * @param from the BlockFace to count + * @param offset index in this range + * @return the face at the offset + */ + public BlockFace next(BlockFace from, int offset) { + int index = this.indexOf(from, -1); + if (index == -1) { + throw new IllegalArgumentException("This BlockFaces constant does not contain the face specified"); + } + index = (index + offset) % this.faces.length; + if (index < 0) { + index += this.faces.length; + } + return this.faces[index]; + } + + /** + * Gets a random BlockFace from this BlockFaces constant + * + * @param random to use + * @return a random BlockFace + */ + public BlockFace random(Random random) { + return this.faces[random.nextInt(this.faces.length)]; + } + + /** + * Gets the face from this constant at the index given
If the index is out of range, the first or last element is returned + * + * @param index to get at + * @return the BlockFace + */ + public BlockFace get(int index) { + if (index < 0) { + return this.faces[0]; + } else if (index >= this.faces.length) { + return this.faces[this.faces.length - 1]; + } else { + return this.faces[index]; + } + } + + /** + * Gets the face from this constant at the index given
If the index is out of range, the default is returned + * + * @param index to get at + * @param def if the index is out of range + * @return the BlockFace + */ + public BlockFace get(int index, BlockFace def) { + if (index < 0 || index >= this.faces.length) { + return def; + } + + return this.faces[index]; + } + + public BlockFace[] toArray() { + return Arrays.copyOf(this.faces, this.faces.length); + } + + private static byte getOffsetHash(Vector3f offset) { + offset = offset.normalize(); + offset = offset.round(); + int x = offset.getFloorX(); + int y = offset.getFloorY(); + int z = offset.getFloorZ(); + x += 1; + y += 1; + z += 1; + return (byte) (x | y << 2 | z << 4); + } + + public static BlockFaces fromOffset(Vector3f offset) { + return offsetHash.get(getOffsetHash(offset)); + } +} diff --git a/src/main/java/org/spout/api/material/block/BlockFullState.java b/src/main/java/org/spout/api/material/block/BlockFullState.java new file mode 100644 index 0000000..ff8dd2d --- /dev/null +++ b/src/main/java/org/spout/api/material/block/BlockFullState.java @@ -0,0 +1,160 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material.block; + +import com.flowpowered.commons.StringUtil; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import org.spout.api.material.BlockMaterial; + +/** + * Represents a {@link Block}'s ID and Data values, but contains no location-specific information. + */ +public class BlockFullState implements Cloneable { + private final short id; + private final short data; + + public BlockFullState(int idAndData) { + id = (short) (idAndData >> 16); + data = (short) (idAndData); + } + + public BlockFullState(short id, short data) { + this.id = id; + this.data = data; + } + + /** + * Id of the Block + * + * @return id + */ + public final short getId() { + return id; + } + + /** + * Data value of the Block + * + * @return data + */ + public final short getData() { + return data; + } + + /** + * Returns an Integer representation of the merged ID and data for this BlockFullState.
The id will be contained in the upper 16-bits. The data will be contained in the lower 16-bits.
+ * + * @return integer representation of ID and Data. + */ + public int getPacked() { + return getPacked(id, data); + } + + /** + * Returns an Integer representation of the merged ID and data.
The id will be contained in the upper 16-bits. The data will be contained in the lower 16-bits.
+ * + * @param id to pack. + * @param data to pack. + * @return integer representation of ID and Data. + */ + public static int getPacked(short id, short data) { + return id << 16 | (data & 0xFFFF); + } + + /** + * Returns an Integer representation of the ID and Data from a {@link BlockMaterial}.
The id will be contained in the upper 16-bits. The data will be contained in the lower 16-bits.
+ */ + public static int getPacked(BlockMaterial m) { + return getPacked(m.getId(), m.getData()); + } + + /** + * Unpacks the ID of a Material or Block from a packed integer.
The integer being passed in must have the ID of the Material or Block contained in the upper 16-bits.
+ * + * @param packed integer + * @return id of the material or block + */ + public static short getId(int packed) { + return (short) (packed >> 16); + } + + /** + * Unpacks the Data of a material or block from a packed integer.
The integer being passed in must have the data of the Material or Block contained in the lower 16-bits.
+ * + * @param packed integer + * @return data of the material or block. + */ + public static short getData(int packed) { + return (short) packed; + } + + /** + * Looks up the BlockMaterial from a packed integer.
If the material does not exist in the {@link BlockMaterialRegistry} then {@link BasicAir} will be returned. If the material does exist, and + * it contains data, the Sub-Material will be returned. + * + * @return the material found. + */ + public static BlockMaterial getMaterial(int packed) { + short id = getId(packed); + short data = getData(packed); + BlockMaterial mat = BlockMaterial.get(id); + if (mat == null) { + return BlockMaterial.AIR; + } + return mat.getSubMaterial(data); + } + + @Override + public String toString() { + return StringUtil.toNamedString(this, this.id, this.data); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(77, 81).append(id).append(data).toHashCode(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (!(o instanceof BlockFullState)) { + return false; + } else { + BlockFullState fullState = (BlockFullState) o; + + return fullState.id == id && fullState.data == data; + } + } + + @Override + public BlockFullState clone() { + return new BlockFullState(id, data); + } +} diff --git a/src/main/java/org/spout/api/material/block/BlockSnapshot.java b/src/main/java/org/spout/api/material/block/BlockSnapshot.java new file mode 100644 index 0000000..7a051bd --- /dev/null +++ b/src/main/java/org/spout/api/material/block/BlockSnapshot.java @@ -0,0 +1,150 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.material.block; + +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.material.BlockMaterial; +import org.spout.api.material.Material; +import org.spout.api.util.hashing.ShortPairHashed; + +/** + * Represents an immutable snapshot of the state of a block + */ +public class BlockSnapshot { + private final BlockMaterial material; + private final short data; + private final int x, y, z; + private final World world; + + public BlockSnapshot(Block block) { + this(block, block.getMaterial(), block.getBlockData()); + } + + public BlockSnapshot(Block block, BlockMaterial material, short data) { + this(block.getWorld(), block.getX(), block.getY(), block.getZ(), material, data); + } + + public BlockSnapshot(World world, int x, int y, int z, BlockMaterial material, short data) { + this.material = material; + this.data = data; + this.x = x; + this.y = y; + this.z = z; + this.world = world; + } + + /** + * Gets the x-coordinate of this Block snapshot + * + * @return the x-coordinate + */ + public int getX() { + return this.x; + } + + /** + * Gets the y-coordinate of this Block snapshot + * + * @return the y-coordinate + */ + public int getY() { + return this.y; + } + + /** + * Gets the z-coordinate of this Block snapshot + * + * @return the z-coordinate + */ + public int getZ() { + return this.z; + } + + /** + * Gets the world this Block snapshot is in + * + * @return the World + */ + public World getWorld() { + return this.world; + } + + /** + * Gets which block corresponding to the snapshot + * + * @return the block + */ + public Block getBlock() { + return this.world.getBlock(this.x, this.y, this.z); + } + + /** + * Gets the block's material at the time of the snapshot + * + * @return the material + */ + public BlockMaterial getMaterial() { + return this.material; + } + + public short getData() { + return this.data; + } + + @Override + public String toString() { + return "BlockSnapshot { material: " + this.material + " , data:" + this.data + ", x: " + x + ", y: " + y + ", z: " + z + "}"; + } + + @Override + public int hashCode() { + return ShortPairHashed.key(this.material.getId(), this.data); + } + + @Override + public boolean equals(Object o) { + if (o instanceof BlockSnapshot) { + BlockSnapshot other = (BlockSnapshot) o; + return other.x == x && other.y == y && other.z == z && other.material.equals(material) && other.data == data; + } + return false; + } + + public boolean isMaterial(Material... materials) { + if (this.material == null) { + for (Material material : materials) { + if (material == null) { + return true; + } + } + return false; + } else { + return this.material.isMaterial(materials); + } + } +} diff --git a/src/main/java/org/spout/api/model/mesh/CubeMeshFactory.java b/src/main/java/org/spout/api/model/mesh/CubeMeshFactory.java new file mode 100644 index 0000000..c5383dc --- /dev/null +++ b/src/main/java/org/spout/api/model/mesh/CubeMeshFactory.java @@ -0,0 +1,111 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.model.mesh; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import org.spout.api.material.block.BlockFace; +import org.spout.math.vector.Vector2f; +import org.spout.math.vector.Vector3f; + +public class CubeMeshFactory { + public static OrientedMesh generateCubeMesh(Vector2f[][] uvs) { + ArrayList list = new ArrayList<>(12); + + Vector3f vertex0 = new Vector3f(0, 0, 0); + Vector3f vertex1 = new Vector3f(0, 1, 0); + Vector3f vertex2 = new Vector3f(1, 1, 0); + Vector3f vertex3 = new Vector3f(1, 0, 0); + Vector3f vertex4 = new Vector3f(0, 0, 1); + Vector3f vertex5 = new Vector3f(0, 1, 1); + Vector3f vertex6 = new Vector3f(1, 1, 1); + Vector3f vertex7 = new Vector3f(1, 0, 1); + + Vertex v1 = null, v2 = null, v3 = null, v4 = null; + + /* 1--2 + * /| /| + * 5--6 | + * | 0|-3 Y - Bottom < TOP + * |/ |/ | + * 4--7 O-- X - North < SOUTH + * / + * Z - East < WEST + */ + + v1 = Vertex.createVertexPositionNormaTexture0(vertex1, BlockFace.TOP.getOffset(), getUV(uvs, 0, 3)); + v2 = Vertex.createVertexPositionNormaTexture0(vertex2, BlockFace.TOP.getOffset(), getUV(uvs, 0, 2)); + v3 = Vertex.createVertexPositionNormaTexture0(vertex6, BlockFace.TOP.getOffset(), getUV(uvs, 0, 1)); + v4 = Vertex.createVertexPositionNormaTexture0(vertex5, BlockFace.TOP.getOffset(), getUV(uvs, 0, 0)); + list.add(new OrientedMeshFace(v1, v2, v3, new HashSet<>(Arrays.asList(BlockFace.TOP)))); + list.add(new OrientedMeshFace(v3, v4, v1, new HashSet<>(Arrays.asList(BlockFace.TOP)))); + + v1 = Vertex.createVertexPositionNormaTexture0(vertex0, BlockFace.BOTTOM.getOffset(), getUV(uvs, 1, 0)); + v2 = Vertex.createVertexPositionNormaTexture0(vertex4, BlockFace.BOTTOM.getOffset(), getUV(uvs, 1, 3)); + v3 = Vertex.createVertexPositionNormaTexture0(vertex7, BlockFace.BOTTOM.getOffset(), getUV(uvs, 1, 2)); + v4 = Vertex.createVertexPositionNormaTexture0(vertex3, BlockFace.BOTTOM.getOffset(), getUV(uvs, 1, 1)); + list.add(new OrientedMeshFace(v1, v2, v3, new HashSet<>(Arrays.asList(BlockFace.BOTTOM)))); + list.add(new OrientedMeshFace(v3, v4, v1, new HashSet<>(Arrays.asList(BlockFace.BOTTOM)))); + + v1 = Vertex.createVertexPositionNormaTexture0(vertex0, BlockFace.NORTH.getOffset(), getUV(uvs, 2, 1)); + v2 = Vertex.createVertexPositionNormaTexture0(vertex1, BlockFace.NORTH.getOffset(), getUV(uvs, 2, 0)); + v3 = Vertex.createVertexPositionNormaTexture0(vertex5, BlockFace.NORTH.getOffset(), getUV(uvs, 2, 3)); + v4 = Vertex.createVertexPositionNormaTexture0(vertex4, BlockFace.NORTH.getOffset(), getUV(uvs, 2, 2)); + list.add(new OrientedMeshFace(v1, v2, v3, new HashSet<>(Arrays.asList(BlockFace.NORTH)))); + list.add(new OrientedMeshFace(v3, v4, v1, new HashSet<>(Arrays.asList(BlockFace.NORTH)))); + + v1 = Vertex.createVertexPositionNormaTexture0(vertex7, BlockFace.SOUTH.getOffset(), getUV(uvs, 3, 1)); + v2 = Vertex.createVertexPositionNormaTexture0(vertex6, BlockFace.SOUTH.getOffset(), getUV(uvs, 3, 0)); + v3 = Vertex.createVertexPositionNormaTexture0(vertex2, BlockFace.SOUTH.getOffset(), getUV(uvs, 3, 3)); + v4 = Vertex.createVertexPositionNormaTexture0(vertex3, BlockFace.SOUTH.getOffset(), getUV(uvs, 3, 2)); + list.add(new OrientedMeshFace(v1, v2, v3, new HashSet<>(Arrays.asList(BlockFace.SOUTH)))); + list.add(new OrientedMeshFace(v3, v4, v1, new HashSet<>(Arrays.asList(BlockFace.SOUTH)))); + + v1 = Vertex.createVertexPositionNormaTexture0(vertex0, BlockFace.EAST.getOffset(), getUV(uvs, 4, 2)); + v2 = Vertex.createVertexPositionNormaTexture0(vertex3, BlockFace.EAST.getOffset(), getUV(uvs, 4, 1)); + v3 = Vertex.createVertexPositionNormaTexture0(vertex2, BlockFace.EAST.getOffset(), getUV(uvs, 4, 0)); + v4 = Vertex.createVertexPositionNormaTexture0(vertex1, BlockFace.EAST.getOffset(), getUV(uvs, 4, 3)); + list.add(new OrientedMeshFace(v1, v2, v3, new HashSet<>(Arrays.asList(BlockFace.EAST)))); + list.add(new OrientedMeshFace(v3, v4, v1, new HashSet<>(Arrays.asList(BlockFace.EAST)))); + + v1 = Vertex.createVertexPositionNormaTexture0(vertex5, BlockFace.WEST.getOffset(), getUV(uvs, 5, 0)); + v2 = Vertex.createVertexPositionNormaTexture0(vertex6, BlockFace.WEST.getOffset(), getUV(uvs, 5, 3)); + v3 = Vertex.createVertexPositionNormaTexture0(vertex7, BlockFace.WEST.getOffset(), getUV(uvs, 5, 2)); + v4 = Vertex.createVertexPositionNormaTexture0(vertex4, BlockFace.WEST.getOffset(), getUV(uvs, 5, 1)); + list.add(new OrientedMeshFace(v1, v2, v3, new HashSet<>(Arrays.asList(BlockFace.WEST)))); + list.add(new OrientedMeshFace(v3, v4, v1, new HashSet<>(Arrays.asList(BlockFace.WEST)))); + + return new OrientedMesh(list); + } + + private static Vector2f getUV(Vector2f[][] uvs, int face, int vertex) { + int i = face % uvs.length; //Allow to render all face of a cube with a one face specified + return uvs[i][vertex % uvs[i].length]; + } +} diff --git a/src/main/java/org/spout/api/model/mesh/Mesh.java b/src/main/java/org/spout/api/model/mesh/Mesh.java new file mode 100644 index 0000000..5512954 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesh/Mesh.java @@ -0,0 +1,30 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.model.mesh; + +public interface Mesh { +} diff --git a/src/main/java/org/spout/api/model/mesh/MeshFace.java b/src/main/java/org/spout/api/model/mesh/MeshFace.java new file mode 100644 index 0000000..276d011 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesh/MeshFace.java @@ -0,0 +1,83 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.model.mesh; + +import com.flowpowered.commons.StringUtil; + +import java.io.Serializable; +import java.util.Iterator; + +import org.apache.commons.collections.iterators.ArrayIterator; +import org.spout.math.vector.Vector2f; +import org.spout.math.vector.Vector3f; + +/** + * Represents a Triangle for a model face + */ +public class MeshFace implements Iterable, Serializable { + private static final long serialVersionUID = 1L; + Vertex[] verts = new Vertex[3]; + + public MeshFace(Vertex v1, Vertex v2, Vertex v3) { + verts[0] = v1; + verts[1] = v2; + verts[2] = v3; + } + + /** + * Recalculates the normals for this triangle. All points must be 0'd before this. + */ + protected void doRecalculateNormals() { + Vector3f trinormal = verts[0].position.sub(verts[1].position).cross(verts[1].position.sub(verts[2].position)).normalize(); + verts[0].normal = verts[0].normal.add(trinormal).normalize(); + verts[1].normal = verts[1].normal.add(trinormal).normalize(); + verts[2].normal = verts[2].normal.add(trinormal).normalize(); + } + + Vector3f[] getPositions() { + return new Vector3f[] {verts[0].position, verts[1].position, verts[2].position}; + } + + Vector3f[] getNormals() { + return new Vector3f[] {verts[0].normal, verts[1].normal, verts[2].normal}; + } + + Vector2f[] getUVs() { + return new Vector2f[] {verts[0].texCoord0, verts[1].texCoord0, verts[2].texCoord0}; + } + + @Override + public String toString() { + return StringUtil.toNamedString(this, verts[0], verts[1], verts[2]); + } + + @SuppressWarnings ("unchecked") + @Override + public Iterator iterator() { + return new ArrayIterator(verts); + } +} diff --git a/src/main/java/org/spout/api/model/mesh/OrientedMesh.java b/src/main/java/org/spout/api/model/mesh/OrientedMesh.java new file mode 100644 index 0000000..83418d3 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesh/OrientedMesh.java @@ -0,0 +1,57 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.model.mesh; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Iterator; +import java.util.List; + +public class OrientedMesh implements Mesh, Iterable { + private static final long serialVersionUID = 1L; + protected final List meshFace; + + public & Serializable> OrientedMesh(T meshFace) { + this.meshFace = meshFace; + } + + @Override + public Iterator iterator() { + return meshFace.iterator(); + } + + // This is a workaround for SerializableTest. We know at compile-time, thanks to generics, that meshFace will be Serializable + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + } +} diff --git a/src/main/java/org/spout/api/model/mesh/OrientedMeshFace.java b/src/main/java/org/spout/api/model/mesh/OrientedMeshFace.java new file mode 100644 index 0000000..0a64733 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesh/OrientedMeshFace.java @@ -0,0 +1,114 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.model.mesh; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.spout.api.material.block.BlockFace; +import org.spout.math.vector.Vector3f; + +/** + * Represents a Triangle for a model face + */ +public class OrientedMeshFace extends MeshFace { + public final static BlockFace[] shouldRender = new BlockFace[] {BlockFace.TOP, BlockFace.BOTTOM, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST}; + private final static Map> faceMap = new HashMap<>(); + private static final long serialVersionUID = 1L; + + static { + /* 1--2 + * /| /| + * 5--6 | + * | 0|-3 Y - Bottom < TOP + * |/ |/ | + * 4--7 O-- X - North < SOUTH + * / + * Z - East < WEST + */ + + //TODO : extract vector in variable + + faceMap.put(BlockFace.TOP, new ArrayList<>(Arrays.asList(new Vector3f(-1, 1, -1).normalize(), new Vector3f(1, 1, -1).normalize(), new Vector3f(-1, 1, 1).normalize(), new Vector3f(1, 1, 1).normalize()))); + faceMap.put(BlockFace.BOTTOM, new ArrayList<>(Arrays.asList(new Vector3f(-1, -1, -1).normalize(), new Vector3f(1, -1, -1).normalize(), new Vector3f(-1, -1, 1).normalize(), new Vector3f(1, -1, 1).normalize()))); + faceMap.put(BlockFace.NORTH, new ArrayList<>(Arrays.asList(new Vector3f(-1, -1, -1).normalize(), new Vector3f(-1, 1, -1).normalize(), new Vector3f(-1, 1, 1).normalize(), new Vector3f(-1, -1, 1).normalize()))); + faceMap.put(BlockFace.SOUTH, new ArrayList<>(Arrays.asList(new Vector3f(1, -1, -1).normalize(), new Vector3f(1, -1, 1).normalize(), new Vector3f(1, 1, -1).normalize(), new Vector3f(1, 1, 1).normalize()))); + faceMap.put(BlockFace.WEST, new ArrayList<>(Arrays.asList(new Vector3f(-1, 1, 1).normalize(), new Vector3f(-1, -1, 1).normalize(), new Vector3f(1, -1, 1).normalize(), new Vector3f(1, 1, 1).normalize()))); + faceMap.put(BlockFace.EAST, new ArrayList<>(Arrays.asList(new Vector3f(-1, -1, -1).normalize(), new Vector3f(-1, 1, -1).normalize(), new Vector3f(1, -1, -1).normalize(), new Vector3f(1, 1, -1).normalize()))); + } + + private boolean[] seeFromFace = new boolean[shouldRender.length]; + + public OrientedMeshFace(Vertex v1, Vertex v2, Vertex v3) { + super(v1, v2, v3); + + // Calculate two vectors from the three points + + Vector3f vector1 = verts[0].position.sub(verts[1].position); + Vector3f vector2 = verts[1].position.sub(verts[2].position); + + // Take the cross product of the two vectors to get + // the normal vector which will be stored in out + + Vector3f norm = vector1.cross(vector2).normalize(); + + for (int i = 0; i < shouldRender.length; i++) { + for (Vector3f edge : faceMap.get(shouldRender[i])) { + if (norm.distance(edge) < 1) { + seeFromFace[i] = true; + } + } + } + } + + public OrientedMeshFace(Vertex v1, Vertex v2, Vertex v3, Set requiredFace) { + super(v1, v2, v3); + + for (int i = 0; i < shouldRender.length; i++) { + seeFromFace[i] = requiredFace.contains(shouldRender[i]); + } + } + + public boolean canRender(boolean toRender[]) { + /** + * For each face : + * - Look if a face is see by a block face and this block face isn't occluded + * - If this face is the face what you want to draw, send yes + * - If it's not the face that you want to drawn, send no, because this face will be rended by a other blockface + */ + for (int i = 0; i < shouldRender.length; i++) { + if (seeFromFace[i] && toRender[i]) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/org/spout/api/model/mesh/Vertex.java b/src/main/java/org/spout/api/model/mesh/Vertex.java new file mode 100644 index 0000000..a91bea2 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesh/Vertex.java @@ -0,0 +1,162 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.model.mesh; + +import java.awt.Color; +import java.io.Serializable; + +import com.flowpowered.commons.StringUtil; + +import org.spout.math.vector.Vector2f; +import org.spout.math.vector.Vector3f; + +public class Vertex implements Serializable { + private static final long serialVersionUID = 1L; + public static final int SIZE_FLOAT = 4; + public static final int VERTEX_LAYER = 0; + public static final int COLOR_LAYER = 1; + public static final int NORMAL_LAYER = 2; + public static final int TEXTURE0_LAYER = 3; + public static final int TEXTURE1_LAYER = 4; + public Vector3f position; + public Color color; + public Vector3f normal; + public Vector2f texCoord0; + public Vector2f texCoord1; + public int id; + + /** + * Create a vertex with a position + */ + public static Vertex createVertexPosition(Vector3f position) { + return new Vertex(position, Vector3f.ZERO, Vector2f.ZERO, null, 0); + } + + /** + * Create a vertex with a position and a normal + */ + public static Vertex createVertexPositionNormal(Vector3f position, Vector3f normal) { + return new Vertex(position, normal, Vector2f.ZERO, null, 0); + } + + /** + * Create a vertex with a position and a texture + */ + public static Vertex createVertexPositionTexture0(Vector3f position, Vector2f texture) { + return new Vertex(position, Vector3f.ZERO, texture, null, 0); + } + + /** + * Create a vertex with a position, a normal and a color + */ + public static Vertex createVertexPositionNormalColor(Vector3f position, Vector3f normal, Color color) { + return new Vertex(position, normal, Vector2f.ZERO, color, 0); + } + + /** + * Create a vertex with a position, a normal and a texture + */ + public static Vertex createVertexPositionNormaTexture0(Vector3f position, Vector3f normal, Vector2f texture) { + return new Vertex(position, normal, texture, null, 0); + } + + /** + * Create a vertex with a position, a normal, a texture and a color + */ + public static Vertex createVertexPositionNormalTexture0Color(Vector3f position, Vector3f normal, Vector2f texture, Color color) { + return new Vertex(position, normal, texture, color, 0); + } + + /** + * Create a vertex with a position, a normal, a texture and a vertice index + */ + public static Vertex createVertexPositionNormalTexture0Index(Vector3f position, Vector3f normal, Vector2f texture, int id) { + return new Vertex(position, normal, texture, null, id); + } + + /** + * Create a vertex with a position, a normal and a vertice index + */ + public static Vertex createVertexPositionNormalIndex(Vector3f position, Vector3f normal, int id) { + return new Vertex(position, normal, Vector2f.ZERO, null, id); + } + + /** + * Create a vertex with a position and a vertice index + */ + public static Vertex createVertexPositionIndex(Vector3f position, int id) { + return new Vertex(position, Vector3f.ZERO, Vector2f.ZERO, null, id); + } + + /** + * Create a vertex with a position, a texture and a vertice index + */ + public static Vertex createVertexPositionTexture0Index(Vector3f position, Vector2f texture, int id) { + return new Vertex(position, Vector3f.ZERO, texture, null, id); + } + + public Vertex(Vector3f position, Vector3f normal, Vector2f texture, Color color, int id) { + this.position = position; + this.normal = normal; + this.texCoord0 = texture; + this.color = color == null ? null : new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); + this.id = id; + } + + public Vertex(Vertex v) { + this.color = v.color == null ? null : new Color(v.color.getRed(), v.color.getGreen(), v.color.getBlue(), v.color.getAlpha()); + this.position = v.position == null ? null : new Vector3f(v.position); + this.normal = v.normal == null ? null : new Vector3f(v.normal); + this.texCoord0 = v.texCoord0 == null ? null : new Vector2f(v.texCoord0); + this.texCoord1 = v.texCoord1 == null ? null : new Vector2f(v.texCoord1); + this.id = v.id; + } + + public float[] toArray() { + return new float[] {position.getX(), position.getY(), position.getZ(), 1.0f, + color.getRed() / 255.0f, color.getBlue() / 255.0f, color.getGreen() / 255.0f, color.getAlpha() / 255.0f, + normal.getX(), normal.getY(), normal.getZ(), + texCoord0.getX(), texCoord0.getY(), + texCoord1.getX(), texCoord1.getY() + }; + } + + public int getStride() { + int stride = 0; + stride += SIZE_FLOAT * 4; //number of floats in a vector4 + stride += SIZE_FLOAT * 4; //number of floats in a Color + stride += SIZE_FLOAT * 3; //number of floats in a normal + stride += SIZE_FLOAT * 2; //number of floats in a texcoord + stride += SIZE_FLOAT * 2; //number of floats in a texcoord + return stride; + } + + @Override + public String toString() { + return StringUtil.toNamedString(this, position, normal, texCoord0); + } +} diff --git a/src/main/java/org/spout/api/model/mesher/ChunkMesher.java b/src/main/java/org/spout/api/model/mesher/ChunkMesher.java new file mode 100644 index 0000000..5d02b86 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesher/ChunkMesher.java @@ -0,0 +1,39 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.api.model.mesher; + +import org.spout.api.geo.cuboid.ChunkSnapshotGroup; + +/** + * Converts chunk snapshot groups to meshes for rendering. + */ +public interface ChunkMesher { + /** + * Converts the chunk snapshot group to a mesh. + * + * @param chunk The chunk snapshot group + * @return The mesh + */ + public Mesh mesh(ChunkSnapshotGroup chunk); +} diff --git a/src/main/java/org/spout/api/model/mesher/Mesh.java b/src/main/java/org/spout/api/model/mesher/Mesh.java new file mode 100644 index 0000000..6b55db3 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesher/Mesh.java @@ -0,0 +1,212 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.api.model.mesher; + +import java.util.EnumMap; +import java.util.Map; +import java.util.Map.Entry; + +import gnu.trove.list.TFloatList; +import gnu.trove.list.TIntList; +import gnu.trove.list.array.TFloatArrayList; +import gnu.trove.list.array.TIntArrayList; + +import org.spout.renderer.data.VertexAttribute; +import org.spout.renderer.data.VertexAttribute.DataType; +import org.spout.renderer.data.VertexData; +import org.spout.renderer.util.CausticUtil; + +/** + * Represents a standard mesh, with various attributes (positions, normals, texture coordinates and/or tangents). This mesh can be converted into {@link org.spout.renderer.data.VertexData for + * rendering}. + * + * @see org.spoutcraft.client.nterface.mesh.Mesh.MeshAttribute + */ +public class Mesh { + private final Map attributes = new EnumMap<>(MeshAttribute.class); + private final TIntList indices = new TIntArrayList(); + + /** + * Constructs a new mesh with the desired attributes. + * + * @param attributes The attributes + */ + public Mesh(MeshAttribute... attributes) { + for (MeshAttribute attribute : attributes) { + this.attributes.put(attribute, new TFloatArrayList()); + } + } + + /** + * Returns true if the mesh has the attribute, false if not. + * + * @param attribute Whether or not the mesh has the attribute + * @return The attribute to check for + */ + public boolean hasAttribute(MeshAttribute attribute) { + return attributes.containsKey(attribute); + } + + /** + * Adds an attribute to the mesh, if not already present. + * + * @param attribute The attribute to add + */ + public void addAttribute(MeshAttribute attribute) { + if (!hasAttribute(attribute)) { + attributes.put(attribute, new TFloatArrayList()); + } + } + + /** + * Returns the float list associated to the attribute in which to store the data. The components for each individual attribute point should be stored in their natural order inside the list, and + * each point after the other. Actual order of the points for rendering is decided by the indices list. + * + * @param attribute The attribute to get the float list for + * @return The float list for the attribute + */ + public TFloatList getAttribute(MeshAttribute attribute) { + return attributes.get(attribute); + } + + /** + * Removes the attributes from the mesh, deleting its data. + * + * @param attribute The attribute to remove + */ + public void removeAttribute(MeshAttribute attribute) { + attributes.remove(attribute); + } + + /** + * Returns the index list for the mesh, in which to store the indices that declares the triangle faces by winding order. + * + * @return The index list + */ + public TIntList getIndices() { + return indices; + } + + /** + * Returns true if all the attribute data lists and the indices list are empty. + * + * @return Whether or not this mesh is empty (no data for the attributes or indices) + */ + public boolean isEmpty() { + for (TFloatList list : attributes.values()) { + if (!list.isEmpty()) { + return false; + } + } + return indices.isEmpty(); + } + + /** + * Builds the mesh into a {@link org.spout.renderer.data.VertexData} to be ready for rendering. If an attribute has no data, but can be automatically generated (see {@link + * org.spoutcraft.client.nterface.mesh.Mesh.MeshAttribute#generateDataIfMissing()}, it will be generated for the build. The generated data will be stored in the attribute float list. + * + * @return The vertex data for the built mesh + */ + public VertexData build() { + final VertexData vertexData = new VertexData(); + int i = 0; + for (Entry entry : attributes.entrySet()) { + MeshAttribute attribute = entry.getKey(); + final VertexAttribute vertexAttribute = new VertexAttribute(attribute.getName(), DataType.FLOAT, attribute.getComponentCount()); + final TFloatList data = entry.getValue(); + if (data.isEmpty() && attribute.generateDataIfMissing()) { + switch (attribute) { + case NORMALS: + CausticUtil.generateNormals(attributes.get(MeshAttribute.POSITIONS), indices, data); + break; + case TANGENTS: + CausticUtil.generateTangents(attributes.get(MeshAttribute.POSITIONS), attributes.get(MeshAttribute.NORMALS), attributes.get(MeshAttribute.TEXTURE_COORDS), indices, data); + } + } + vertexAttribute.setData(data); + vertexData.addAttribute(i++, vertexAttribute); + } + vertexData.getIndices().addAll(indices); + return vertexData; + } + + /** + * An enum of the various mesh attributes. + */ + public static enum MeshAttribute { + // Enum ordering is important here, don't change + /** + * The positions attribute, has 3 components and cannot be automatically generated. + */ + POSITIONS("positions", 3, false), + /** + * The normals attribute, has 3 components and can be automatically generated if the position data exists. + */ + NORMALS("normals", 3, true), + /** + * The texture coordinates attribute, has 2 components and cannot be automatically generated. + */ + TEXTURE_COORDS("textureCoords", 2, false), + /** + * The tangents attribute, has 4 components and can be automatically generated if the positions, normals and texture coordinates exist. + */ + TANGENTS("tangents", 4, true); + private final String name; + private final int componentCount; + private final boolean generateIfDataMissing; + + private MeshAttribute(String name, int componentCount, boolean generateIfDataMissing) { + this.name = name; + this.componentCount = componentCount; + this.generateIfDataMissing = generateIfDataMissing; + } + + /** + * Returns the name of the attribute. + * + * @return The attribute name + */ + public String getName() { + return name; + } + + /** + * Returns the component count of the attribute. + * + * @return The component count + */ + public int getComponentCount() { + return componentCount; + } + + /** + * Returns true if the attribute data can be automatically generated when the required attributes are present. + * + * @return Whether or not the attribute data can be automatically generated + */ + public boolean generateDataIfMissing() { + return generateIfDataMissing; + } + } +} diff --git a/src/main/java/org/spout/api/model/mesher/ParallelChunkMesher.java b/src/main/java/org/spout/api/model/mesher/ParallelChunkMesher.java new file mode 100644 index 0000000..f365910 --- /dev/null +++ b/src/main/java/org/spout/api/model/mesher/ParallelChunkMesher.java @@ -0,0 +1,200 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.api.model.mesher; + +import java.util.concurrent.Callable; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.spout.api.geo.cuboid.ChunkSnapshotGroup; +import org.spout.engine.render.SpoutRenderer; +import org.spout.renderer.data.VertexData; +import org.spout.renderer.gl.VertexArray; +import org.spout.renderer.model.Model; + +/** + * Meshes chunks in parallel. Returns chunk models which may not be rendered when {@link org.spoutcraft.client.nterface.mesh.ParallelChunkMesher.ChunkModel#render()} is called, this is happens when + * the meshing is in progress. Parallelism is achieved using a {@link java.util.concurrent.ForkJoinPool} with default thread count. Chunks are meshed using the provided {@link + * org.spoutcraft.client.nterface.mesh.ChunkMesher}. An optional {@link org.spoutcraft.client.nterface.Interface} can be passed to the constructor for chunk culling. + * + * @see org.spoutcraft.client.nterface.mesh.ParallelChunkMesher.ChunkModel + */ +public class ParallelChunkMesher { + private final SpoutRenderer renderer; + private final ChunkMesher mesher; + private final ThreadPoolExecutor executor; + + /** + * Constructs a new parallel chunk mesher from the actual mesher. + * + * @param mesher The chunk mesher + */ + public ParallelChunkMesher(SpoutRenderer renderer, ChunkMesher mesher) { + this.renderer = renderer; + this.mesher = mesher; + this.executor = new ThreadPoolExecutor(4, 4, + 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue()); + executor.allowCoreThreadTimeOut(true); + } + + /** + * Queues a chunk to be meshed, returning a chunk model which can be used normally. The chunk model will actually only renderer the chunk once meshing it complete. + * + * @param chunk The chunk to mesh + * @return The chunk's model + */ + public ChunkModel queue(ChunkSnapshotGroup chunk) { + return new ChunkModel(executor.submit(new ChunkMeshTask(chunk))); + } + + /** + * Shuts down the executor used for meshing, cancelling any meshing pending or active. + */ + public void shutdown() { + executor.shutdownNow(); + } + + private class ChunkMeshTask implements Callable { + private final ChunkSnapshotGroup toMesh; + + private ChunkMeshTask(ChunkSnapshotGroup toMesh) { + this.toMesh = toMesh; + } + + @Override + public VertexData call() { + final Mesh mesh = mesher.mesh(toMesh); + if (mesh.isEmpty()) { + return null; + } + return mesh.build(); + } + } + + /** + * In the case that meshing is occurring and that the chunk is not renderable, a previous model can be rendered instead. To use this feature, set the previous model using {@link + * org.spoutcraft.client.nterface.mesh.ParallelChunkMesher.ChunkModel#setPrevious(org.spoutcraft.client.nterface.mesh.ParallelChunkMesher.ChunkModel)}. This previous model will be used until the + * mesh becomes available. At this point, the previous model will be destroyed, and the new one rendered. When a model isn't needed anymore, you must call {@link + * org.spoutcraft.client.nterface.mesh.ParallelChunkMesher.ChunkModel#destroy()} to dispose of it completely. This will also cancel the meshing if it's in progress, and destroy the previous model. + * The chunk can also be automatically culled by passing an optional {@link org.spoutcraft.client.nterface.Interface} to the {@link org.spoutcraft.client.nterface.mesh.ParallelChunkMesher} + * constructor. + */ + public class ChunkModel extends Model { + private Future mesh; + private boolean complete = false; + private ChunkModel previous; + + private ChunkModel(Future mesh) { + this.mesh = mesh; + } + + @Override + public void render() { + // If we have not received the mesh and it's done + if (!complete && mesh.isDone()) { + // Get the mesh + final VertexData vertexData; + try { + vertexData = mesh.get(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + mesh = null; + // If the chunk mesher returned a mesh. It may not return one if the chunk has no mesh (completely invisible) + if (vertexData != null) { + // Create the vertex array from the mesh + final VertexArray vertexArray = renderer.getGLFactory().createVertexArray(); + vertexArray.setData(vertexData); + vertexArray.create(); + // Set it for rendering + setVertexArray(vertexArray); + } + // Destroy and discard the previous model (if any), as it is now obsolete + if (previous != null) { + previous.destroy(); + previous = null; + } + // Set the model as complete + complete = true; + } + if (!isVisible()) { + return; + } + // If we have a vertex array, we can render + if (complete) { + // Only render if the model has a vertex array and we're visible + if (getVertexArray() != null && isVisible()) { + super.render(); + } + } else if (previous != null && isVisible()) { + // Else, fall back on the previous model if we have one and we're visible + previous.render(); + } + } + + private boolean isVisible() { + // It's hard to look right + // at the world baby + // But here's my frustum + // so cull me maybe? + return true; + // TODO frustrum + } + + /** + * Sets the previous model to renderer until the updated one is ready. + * + * @param previous The previous model + */ + public void setPrevious(ChunkModel previous) { + this.previous = previous; + } + + /** + * Destroys the models, cancelling the meshing task if in progress, and the previous model (if any). + */ + public void destroy() { + // If we have a vertex array, destroy it + if (complete) { + if (getVertexArray() != null) { + getVertexArray().destroy(); + } + complete = false; + } else { + // Else, the mesh is still in progress, cancel that + mesh.cancel(false); + mesh = null; + // Also destroy and discard the previous model if we have one + if (previous != null) { + previous.destroy(); + previous = null; + } + } + } + } +} diff --git a/src/main/java/org/spout/api/model/mesher/StandardChunkMesher.java b/src/main/java/org/spout/api/model/mesher/StandardChunkMesher.java new file mode 100644 index 0000000..402c60c --- /dev/null +++ b/src/main/java/org/spout/api/model/mesher/StandardChunkMesher.java @@ -0,0 +1,147 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.api.model.mesher; + +import gnu.trove.list.TFloatList; +import gnu.trove.list.TIntList; +import org.spout.api.geo.cuboid.Chunk; + +import org.spout.api.geo.cuboid.ChunkSnapshotGroup; +import org.spout.api.material.BlockMaterial; +import org.spout.api.material.block.BlockFace; +import org.spout.api.material.block.BlockFaces; +import org.spout.api.model.mesher.Mesh.MeshAttribute; + +/** + * The standard chunk mesher. Voxels are meshed as blocks. Occludes any block not visible, including the edge blocks. Can mesh a chunk with 3n^2(n+2) block access operations, n being the size of the + * chunk. + */ +public class StandardChunkMesher implements ChunkMesher { + @Override + public Mesh mesh(ChunkSnapshotGroup chunk) { + // TODO: add textures + final Mesh mesh = new Mesh(MeshAttribute.POSITIONS, MeshAttribute.NORMALS); + final TFloatList positions = mesh.getAttribute(MeshAttribute.POSITIONS); + final TIntList indices = mesh.getIndices(); + int index = 0; + // Mesh the faces on the x axis + for (int zz = 0; zz < Chunk.BLOCKS.SIZE; zz++) { + for (int yy = 0; yy < Chunk.BLOCKS.SIZE; yy++) { + BlockMaterial backMaterial = chunk.getBlock(-1, yy, zz); + for (int xx = 0; xx < Chunk.BLOCKS.SIZE + 1; xx++) { + final BlockMaterial frontMaterial = chunk.getBlock(xx, yy, zz); + final BlockFace face = getFace(backMaterial, frontMaterial, BlockFaces.NS); + if (face == BlockFace.NORTH) { + add(indices, index + 3, index + 2, index + 1, index + 2, index, index + 1); + } else if (face == BlockFace.SOUTH) { + add(indices, index + 3, index + 1, index + 2, index + 2, index + 1, index); + } else { + backMaterial = frontMaterial; + continue; + } + add(positions, xx, yy + 1, zz + 1); + add(positions, xx, yy + 1, zz); + add(positions, xx, yy, zz + 1); + add(positions, xx, yy, zz); + index += 4; + backMaterial = frontMaterial; + } + } + } + // Mesh the faces on the y axis + for (int xx = 0; xx < Chunk.BLOCKS.SIZE; xx++) { + for (int zz = 0; zz < Chunk.BLOCKS.SIZE; zz++) { + BlockMaterial backMaterial = chunk.getBlock(xx, -1, zz); + for (int yy = 0; yy < Chunk.BLOCKS.SIZE + 1; yy++) { + final BlockMaterial frontMaterial = chunk.getBlock(xx, yy, zz); + final BlockFace face = getFace(backMaterial, frontMaterial, BlockFaces.BT); + if (face == BlockFace.BOTTOM) { + add(indices, index + 3, index + 2, index + 1, index + 2, index, index + 1); + } else if (face == BlockFace.TOP) { + add(indices, index + 3, index + 1, index + 2, index + 2, index + 1, index); + } else { + backMaterial = frontMaterial; + continue; + } + add(positions, xx, yy, zz); + add(positions, xx + 1, yy, zz); + add(positions, xx, yy, zz + 1); + add(positions, xx + 1, yy, zz + 1); + index += 4; + backMaterial = frontMaterial; + } + } + } + // Mesh the faces on the z axis + for (int xx = 0; xx < Chunk.BLOCKS.SIZE; xx++) { + for (int yy = 0; yy < Chunk.BLOCKS.SIZE; yy++) { + BlockMaterial backMaterial = chunk.getBlock(xx, yy, -1); + for (int zz = 0; zz < Chunk.BLOCKS.SIZE + 1; zz++) { + final BlockMaterial frontMaterial = chunk.getBlock(xx, yy, zz); + final BlockFace face = getFace(backMaterial, frontMaterial, BlockFaces.EW); + if (face == BlockFace.EAST) { + add(indices, index + 3, index + 2, index + 1, index + 2, index, index + 1); + } else if (face == BlockFace.WEST) { + add(indices, index + 3, index + 1, index + 2, index + 2, index + 1, index); + } else { + backMaterial = frontMaterial; + continue; + } + add(positions, xx, yy + 1, zz); + add(positions, xx + 1, yy + 1, zz); + add(positions, xx, yy, zz); + add(positions, xx + 1, yy, zz); + index += 4; + backMaterial = frontMaterial; + } + } + } + return mesh; + } + + private BlockFace getFace(BlockMaterial back, BlockMaterial front, BlockFaces axis) { + if (!back.isInvisible() && !front.isFaceRendered(axis.get(0), back)) { + return axis.get(1); + } + if (!front.isInvisible() && !back.isFaceRendered(axis.get(1), front)) { + return axis.get(0); + } + return null; + } + + private static void add(TFloatList list, float x, float y, float z) { + list.add(x); + list.add(y); + list.add(z); + } + + private static void add(TIntList list, int i0, int i1, int i2, int i3, int i4, int i5) { + list.add(i0); + list.add(i1); + list.add(i2); + list.add(i3); + list.add(i4); + list.add(i5); + } +} diff --git a/src/main/java/org/spout/api/render/Renderer.java b/src/main/java/org/spout/api/render/Renderer.java new file mode 100644 index 0000000..3d09d42 --- /dev/null +++ b/src/main/java/org/spout/api/render/Renderer.java @@ -0,0 +1,22 @@ +package org.spout.api.render; + +import org.spout.math.vector.Vector2f; + +public interface Renderer { + + // TODO: getGLVersion + + /** + * Returns the resolution of the window, in pixels. + * + * @return the resolution of the window. + */ + Vector2f getResolution(); + + /** + * Returns the aspect ratio of the client, in pixels.

Ratio = (screen width / screen height) + * + * @return The ratio as a float + */ + float getAspectRatio(); +} diff --git a/src/main/java/org/spout/api/scheduler/IllegalTickSequenceException.java b/src/main/java/org/spout/api/scheduler/IllegalTickSequenceException.java new file mode 100644 index 0000000..96041fe --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/IllegalTickSequenceException.java @@ -0,0 +1,47 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +public class IllegalTickSequenceException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public IllegalTickSequenceException(int allowedStages, int restrictedStages, Thread t, int actualStage) { + super(getMessage(allowedStages, restrictedStages, t, actualStage)); + } + + public IllegalTickSequenceException(int allowedStages, int actualStage) { + super("Method called during (" + TickStage.getAllStages(actualStage) + ") when only (" + TickStage.getAllStages(allowedStages) + ") were allowed"); + } + + private static String getMessage(int allowedStages, int restrictedStages, Thread t, int actualStage) { + if (Thread.currentThread() != t) { + return "Method called by non-owning thread (" + Thread.currentThread() + ") during (" + TickStage.getAllStages(actualStage) + ") when only calls by (" + t + ") during (" + TickStage.getAllStages(allowedStages) + ") were allowed"; + } else { + return "Method called during (" + TickStage.getAllStages(actualStage) + ") when only (" + TickStage.getAllStages(restrictedStages) + ") were allowed for owning thread " + t; + } + } +} diff --git a/src/main/java/org/spout/api/scheduler/Scheduler.java b/src/main/java/org/spout/api/scheduler/Scheduler.java new file mode 100644 index 0000000..f318cc7 --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/Scheduler.java @@ -0,0 +1,37 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +public interface Scheduler extends TaskManager { + + /** + * Determines if the server is under heavy load.

The server is considered under heavy load if the previous tick went over time, or if the current tick has gone over time. + * + * @return true if the server is under heavy load + */ + public boolean isServerOverloaded(); +} diff --git a/src/main/java/org/spout/api/scheduler/SnapshotLock.java b/src/main/java/org/spout/api/scheduler/SnapshotLock.java new file mode 100644 index 0000000..8c5e8cb --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/SnapshotLock.java @@ -0,0 +1,64 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +/** + * A class to allow non-pulsed threads to synchronize with the pulsed thread system + */ +public interface SnapshotLock { + /** + * Readlocks the stable snapshot. + * + * This method will prevent server ticks from completing, so any locks should be short + * + * @param plugin the plugin + */ + public void readLock(Object plugin); + + /** + * Attempts to readlock the stable snapshot and returns immediately + * + * This method will prevent server ticks from completing, so any locks should be short + * + * @param plugin the plugin + */ + public boolean readTryLock(Object plugin); + + /** + * Releases a previous readlock + * + * @param plugin the plugin + */ + public void readUnlock(Object plugin); + + /** + * Gets if the lock is read locked + * + * @return true if locked + */ + public boolean isWriteLocked(); +} diff --git a/src/main/java/org/spout/api/scheduler/Task.java b/src/main/java/org/spout/api/scheduler/Task.java new file mode 100644 index 0000000..1a902e8 --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/Task.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +import org.spout.api.geo.cuboid.Region; + +/** + * Represents a task being executed by the scheduler + */ +public interface Task { + /** + * Returns the taskId for the task + * + * @return Task id number + */ + public int getTaskId(); + + /** + * Returns the Object that owns this task + * + * @return The Object that owns the task + */ + public Object getOwner(); + + /** + * Returns true if the Task is a sync task + * + * @return true if the task is run by main thread + */ + public boolean isSync(); + + /** + * Returns true if the Task is alive. Dead tasks are no longer being scheduled + * + * @return true if the task is alive + */ + public boolean isAlive(); + + /** + * Returns true if the Task is executing. + * + * @return true if the task is executing + */ + public boolean isExecuting(); + + /** + * Returns true if the task is a long lived async task + * + * @return true if the task is a long lived task + */ + public boolean isLongLived(); + + /** + * Cancels current Task + */ + public void cancel(); +} diff --git a/src/main/java/org/spout/api/scheduler/TaskManager.java b/src/main/java/org/spout/api/scheduler/TaskManager.java new file mode 100644 index 0000000..6c28857 --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/TaskManager.java @@ -0,0 +1,179 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +public interface TaskManager { + /** + * Schedules a once off task to occur as soon as possible This task will be executed by the main server thread. + * + * @param plugin the owner of the task + * @param task the task to execute + * @return the task + */ + public Task scheduleSyncDelayedTask(Object plugin, Runnable task); + + /** + * Schedules a once off task to occur as soon as possible This task will be executed by the main server thread. + * + * @param plugin the owner of the task + * @param task the task to execute + * @param priority the priority of the task + * @return the task + */ + public Task scheduleSyncDelayedTask(Object plugin, Runnable task, TaskPriority priority); + + /** + * Schedules a once off task to occur after a delay. This task will be executed by the main server thread + * + * @param plugin the owner of the task + * @param task the task to execute + * @param delay the delay, in ms, before the task starts + * @param priority the priority of the task + * @return the task + */ + public Task scheduleSyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority); + + /** + * Schedules a repeating task This task will be executed by the main server thread. The repeat will not be started if the task until the previous repeat has completed running. + * + * @param plugin the owner of the task + * @param task the task to execute + * @param delay the delay, in ms, before the task starts + * @param period the repeat period, in ms, of the task, or <= 0 to indicate a single shot task + * @param priority the priority of the task + * @return the task + */ + public Task scheduleSyncRepeatingTask(Object plugin, Runnable task, long delay, long period, TaskPriority priority); + + /** + * Schedules a once off short lived task to occur as soon as possible. This task will be executed by a thread managed by the scheduler + * + * @param plugin the owner of the task + * @param task the task to execute + * @return the task id of the task + */ + public Task scheduleAsyncTask(Object plugin, Runnable task); + + /** + * Schedules a once off task to occur as soon as possible. This task will be executed by a thread managed by the scheduler + * + * @param plugin the owner of the task + * @param task the task to execute + * @param longLife indicates that the thread is long lived + * @return the tas + */ + public Task scheduleAsyncTask(Object plugin, Runnable task, boolean longLife); + + /** + * Schedules a once off short lived task to occur after a delay. This task will be executed by a thread managed by the scheduler. + * + * @param plugin the owner of the task + * @param task the task to execute + * @param delay the delay, in ms, before the task starts + * @param priority the priority of the task + * @return the task + */ + public Task scheduleAsyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority); + + /** + * Schedules a once off task to occur after a delay. This task will be executed by a thread managed by the scheduler. + * + * @param plugin the owner of the task + * @param task the task to execute + * @param delay the delay, in ms, before the task starts + * @param priority the priority of the task + * @param longLife indicates that the thread is long lived + * @return the task + */ + public Task scheduleAsyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority, boolean longLife); + + /** + * Calls a method on the main thread and returns a Future object This task will be executed by the main server thread
+ * + * Note: The Future.get() methods must NOT be called from the main thread
Note 2: There is at least an average of 10ms latency until the isDone() method returns true
+ * + * @param plugin the owner of the task + * @param task the Callable to execute + * @param priority the priority of the task + * @return Future Future object related to the task + */ + public Future callSyncMethod(Object plugin, Callable task, TaskPriority priority); + + /** + * True if the task is an actively scheduled task + * + * @return actived scheduled + */ + public boolean isQueued(int taskId); + + /** + * Removes task from scheduler + */ + public void cancelTask(int taskId); + + /** + * Removes task from scheduler + */ + public void cancelTask(Task task); + + /** + * Removes all tasks associated with a particular object from the scheduler + */ + public void cancelTasks(Object plugin); + + /** + * Removes all tasks from the scheduler + */ + public void cancelAllTasks(); + + /** + * Returns a list of all active workers. + * + * This list contains asynch tasks that are being executed by separate threads. + * + * @return Active workers + */ + public List getActiveWorkers(); + + /** + * Returns a list of all pending tasks. The ordering of the tasks is not related to their order of execution. + * + * @return Active workers + */ + public List getPendingTasks(); + + /** + * Gets the up time for the scheduler. This is the time since server started for the main schedulers and the age of the world for the Region based schedulers.

It is updated once per tick. + * + * @return the up time in milliseconds + */ + public long getUpTime(); +} diff --git a/src/main/java/org/spout/api/scheduler/TaskPriority.java b/src/main/java/org/spout/api/scheduler/TaskPriority.java new file mode 100644 index 0000000..be2f6da --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/TaskPriority.java @@ -0,0 +1,75 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +public class TaskPriority { + /** + * Priority for tasks which may not be deferred + */ + public static final TaskPriority CRITICAL = new TaskPriority(0); + /** + * Priority for tasks which can be deferred by up to 50ms when under load + */ + public static final TaskPriority HIGHEST = new TaskPriority(50); + /** + * Priority for tasks which can be deferred by up to 150ms when under load + */ + public static final TaskPriority HIGH = new TaskPriority(150); + /** + * Priority for tasks which can be deferred by up to 500ms when under load + */ + public static final TaskPriority MEDIUM = new TaskPriority(500); + /** + * Priority for tasks which can be deferred by up to 500ms when under load + */ + public static final TaskPriority NORMAL = MEDIUM; + /** + * Priority for tasks which can be deferred by up to 1.5s when under load + */ + public static final TaskPriority LOW = new TaskPriority(1500); + /** + * Priority for tasks which can be deferred by up to 10s when under load + */ + public static final TaskPriority LOWEST = new TaskPriority(10000); + private final long maxDeferred; + + /** + * Creates a TaskPriority instance which sets the maximum time that a task can be deferred. + * + * @param maxDelay the maximum delay before + */ + public TaskPriority(long maxDeferred) { + this.maxDeferred = maxDeferred; + } + + /** + * Gets the maximum time that the task can be deferred. + */ + public long getMaxDeferred() { + return maxDeferred; + } +} diff --git a/src/main/java/org/spout/api/scheduler/TickStage.java b/src/main/java/org/spout/api/scheduler/TickStage.java new file mode 100644 index 0000000..5826f25 --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/TickStage.java @@ -0,0 +1,190 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +/** + * Represents the various tick stages.

The exact bit fields used are subject to change + */ +public class TickStage { + /** + * All tasks submitted to the main thread are executed during TICKSTART.

This stage is single threaded + */ + public final static int TICKSTART = 1; + /** + * This is the first stage of the execution of the tick + */ + public final static int STAGE1 = 1 << 1; + /** + * This is the second and subsequent stages of the tick + */ + public final static int STAGE2P = 1 << 2; + /** + * This is the final stage before entering the pre-snapshot stage.

This is for minor changes prior to the snapshot process. + */ + public final static int PHYSICS = 1 << 3; + /** + * This is the final stage before entering the pre-snapshot stage.

This is for minor changes prior to the snapshot process. + */ + public final static int GLOBAL_PHYSICS = 1 << 4; + /** + * This is the final stage before entering the pre-snapshot stage.

This is for minor changes prior to the snapshot process. + */ + public final static int DYNAMIC_BLOCKS = 1 << 5; + /** + * This is the final stage before entering the pre-snapshot stage.

This is for minor changes prior to the snapshot process. + */ + public final static int GLOBAL_DYNAMIC_BLOCKS = 1 << 6; + /** + * This is the final stage before entering the pre-snapshot stage.

This is for minor changes prior to the snapshot process. + */ + public final static int LIGHTING = 1 << 7; + /** + * This is the final stage before entering the pre-snapshot stage.

This is for minor changes prior to the snapshot process. + */ + public final static int FINALIZE = 1 << 8; + /** + * This stage occurs before the snapshot stage.

This is a MONITOR ONLY stage, no changes should be made during the stage. + */ + public final static int PRESNAPSHOT = 1 << 9; + /** + * This is the snapshot copy stage.

All snapshots are updated to the equal to the live value. + */ + public final static int SNAPSHOT = 1 << 10; + public final static int ALL_PHYSICS = PHYSICS | GLOBAL_PHYSICS; + public final static int ALL_DYNAMIC = DYNAMIC_BLOCKS | GLOBAL_DYNAMIC_BLOCKS; + public final static int ALL_PHYSICS_AND_DYNAMIC = ALL_PHYSICS | ALL_DYNAMIC; + + public static int getStageInt() { + return stage; + } + + public static String getStage(int num) { + switch (num) { + case 1: + return "TICKSTART"; + case 1 << 1: + return "STAGE1"; + case 1 << 2: + return "STAGE2P"; + case 1 << 3: + return "PHYSICS"; + case 1 << 4: + return "GLOBAL_PHYSICS"; + case 1 << 5: + return "DYNAMIC_BLOCKS"; + case 1 << 6: + return "GLOBAL_DYNAMIC_BLOCKS"; + case 1 << 7: + return "LIGHTING"; + case 1 << 8: + return "FINALIZE"; + case 1 << 9: + return "PRESNAPSHOT"; + case 1 << 10: + return "SNAPSHOT"; + default: + return "UNKNOWN"; + } + } + + public static String getAllStages(int num) { + int scan = 1; + boolean first = true; + StringBuilder sb = new StringBuilder(); + + while (scan != 0) { + int checkNum = num & scan; + if (checkNum != 0) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(getStage(checkNum)); + } + scan <<= 1; + } + return sb.toString(); + } + + private static int stage = TICKSTART; + + /** + * Sets the current stage. This is not synchronised, so should only be called during the stable period between stages. + * + * @param stage the stage + */ + public static void setStage(int stage) { + TickStage.stage = stage; + } + + /** + * Checks if the current stages is one of the valid allowed stages. + * + * @param allowedStages the OR of all the allowed stages + */ + public static void checkStage(int allowedStages) { + if (!testStage(allowedStages)) { + throw new IllegalTickSequenceException(allowedStages, stage); + } + } + + /** + * Checks if the current stages is one of the valid allowed stages, but does not throw an exception. + * + * @param allowedStages the OR of all the allowed stages + * @return true if the current stage is one of the allowed stages + */ + public static boolean testStage(int allowedStages) { + return (stage & allowedStages) != 0; + } + + /** + * Checks if the current thread is the owner thread and the current stage is one of the restricted stages, or that the current stage is one of the open stages + * + * @param allowedStages the OR of all the open stages + * @param restrictedStages the OR of all restricted stages + * @param ownerThread the thread that has restricted access + */ + public static void checkStage(int allowedStages, int restrictedStages, Thread ownerThread) { + if ((stage & allowedStages) == 0 && (((stage & restrictedStages) == 0) || Thread.currentThread() != ownerThread)) { + throw new IllegalTickSequenceException(allowedStages, restrictedStages, ownerThread, stage); + } + } + + /** + * Checks if the current thread is the owner thread and the current stage is one of the restricted stages + * + * @param restrictedStages the OR of all restricted stages + * @param ownerThread the thread that has restricted access + */ + public static void checkStage(int restrictedStages, Thread ownerThread) { + if (((stage & restrictedStages) == 0) || Thread.currentThread() != ownerThread) { + throw new IllegalTickSequenceException(restrictedStages, 0, ownerThread, stage); + } + } +} diff --git a/src/main/java/org/spout/api/scheduler/Timer.java b/src/main/java/org/spout/api/scheduler/Timer.java new file mode 100644 index 0000000..c570fc3 --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/Timer.java @@ -0,0 +1,165 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.api.scheduler; + +import java.util.Arrays; + +import org.apache.commons.lang3.SystemUtils; + +/** + * A time class. Calling the {@link #sync()} method at the end of each tick will cause the thread to sleep for the correct time delay between the ticks. {@link #start()} must be called just before the + * loop to start the timer. {@link #reset()} is used to reset the start time to the current time. + *

+ * Based on LWJGL's implementation of {@link org.lwjgl.opengl.Sync}. + */ +public class Timer { + // Time to sleep or yield before next tick + private long nextTick = -1; + // Last 10 running averages for sleeps and yields + private final RunAverages sleepDurations = new RunAverages(10, 1000 * 1000); + private final RunAverages yieldDurations = new RunAverages(10, (int) (-(getTime() - getTime()) * 1.333f)); + // The target tps + private final int tps; + + static { + // Makes windows thread sleeping more accurate + if (SystemUtils.IS_OS_WINDOWS) { + final Thread sleepingDaemon = new Thread() { + @Override + public void run() { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (Exception ignored) { + } + } + }; + sleepingDaemon.setName("Timer"); + sleepingDaemon.setDaemon(true); + sleepingDaemon.start(); + } + } + + /** + * Constructs a new timer. + * + * @param tps The target tps + */ + public Timer(int tps) { + this.tps = tps; + } + + /** + * Returns the timer's target TPS. + * @return The tps + */ + public int getTps() { + return tps; + } + + /** + * Starts the timer. + */ + public void start() { + nextTick = getTime(); + } + + /** + * Resets the timer. + */ + public void reset() { + start(); + } + + /** + * An accurate sync method that will attempt to run at the tps. It should be called once every tick. + */ + public void sync() { + if (nextTick < 0) { + throw new IllegalStateException("Timer was not started"); + } + if (tps <= 0) { + return; + } + try { + // Sleep until the average sleep time is greater than the time remaining until next tick + for (long time1 = getTime(), time2; nextTick - time1 > sleepDurations.average(); time1 = time2) { + Thread.sleep(1); + // Update average sleep time + sleepDurations.add((time2 = getTime()) - time1); + } + // Slowly dampen sleep average if too high to avoid yielding too much + sleepDurations.dampen(); + // Yield until the average yield time is greater than the time remaining until next tick + for (long time1 = getTime(), time2; nextTick - time1 > yieldDurations.average(); time1 = time2) { + Thread.yield(); + // Update average yield time + yieldDurations.add((time2 = getTime()) - time1); + } + } catch (InterruptedException ignored) { + } + // Schedule next frame, drop frames if it's too late for next frame + nextTick = Math.max(nextTick + 1000000000 / tps, getTime()); + } + + // Get the system time in nanoseconds + private static long getTime() { + return System.nanoTime(); + } + + // Holds a number of run times for averaging + private static class RunAverages { + // Dampen threshold, 10ms + private static final long DAMPEN_THRESHOLD = 10000000; + // Dampen factor, don't alter this value + private static final float DAMPEN_FACTOR = 0.9f; + private final long[] values; + private int currentIndex = 0; + + private RunAverages(int slotCount, long initialValue) { + values = new long[slotCount]; + Arrays.fill(values, initialValue); + } + + private void add(long value) { + currentIndex %= values.length; + values[currentIndex++] = value; + } + + private long average() { + long sum = 0; + for (long slot : values) { + sum += slot; + } + return sum / values.length; + } + + private void dampen() { + if (average() > DAMPEN_THRESHOLD) { + for (int i = 0; i < values.length; i++) { + values[i] *= DAMPEN_FACTOR; + } + } + } + } +} diff --git a/src/main/java/org/spout/api/scheduler/Worker.java b/src/main/java/org/spout/api/scheduler/Worker.java new file mode 100644 index 0000000..1b0101b --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/Worker.java @@ -0,0 +1,60 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler; + +/** + * Represents a worker thread for the scheduler. This gives information about the Thread object for the task, owner of the task and the taskId. + * + * Workers are used to execute async tasks. + */ +public interface Worker { + /** + * Returns the taskId for the task being executed by this worker + * + * @return Task id number + */ + public int getTaskId(); + + /** + * Returns the Object that owns this task + * + * @return The Object that owns the task + */ + public Object getOwner(); + + /** + * Attempts to cancel the task. This will trigger an interrupt for async tasks that are in progress. + */ + public void cancel(); + + /** + * Gets the task associated with this worker + * + * @return the task + */ + public Task getTask(); +} diff --git a/src/main/java/org/spout/api/scheduler/tickable/BasicTickable.java b/src/main/java/org/spout/api/scheduler/tickable/BasicTickable.java new file mode 100644 index 0000000..a238f5d --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/tickable/BasicTickable.java @@ -0,0 +1,36 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler.tickable; + +public abstract class BasicTickable implements Tickable { + @Override + public final void tick(float dt) { + if (canTick()) { + onTick(dt); + } + } +} diff --git a/src/main/java/org/spout/api/scheduler/tickable/TickPriority.java b/src/main/java/org/spout/api/scheduler/tickable/TickPriority.java new file mode 100644 index 0000000..eeb9fbd --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/tickable/TickPriority.java @@ -0,0 +1,47 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler.tickable; + +/** + * Represents a {@link org.spout.api.component.Component}s priority + */ +public enum TickPriority { + LOWEST(0), + LOW(1), + NORMAL(2), + HIGH(3), + HIGHEST(4); + private final int index; + + private TickPriority(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } +} diff --git a/src/main/java/org/spout/api/scheduler/tickable/Tickable.java b/src/main/java/org/spout/api/scheduler/tickable/Tickable.java new file mode 100644 index 0000000..016a448 --- /dev/null +++ b/src/main/java/org/spout/api/scheduler/tickable/Tickable.java @@ -0,0 +1,55 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.scheduler.tickable; + +public interface Tickable { + /** + * Called each simulation tick.
Override this to perform logic upon ticking.
1 tick = 1/20 second
20 ticks = 1 second
1200 ticks = 1 minute
72000 ticks = 1 + * hour
1728000 ticks = 1 day + * + * @param dt time since the last tick in seconds + */ + public void onTick(float dt); + + /** + * Whether or not this tickable can perform a tick + * + * @return true if it can tick, false if not + */ + public boolean canTick(); + + /** + * Ticks the Tickable.
Call this to make the Tickable tick.
+ * + * Standard implementation is if(canTick()) { onTick(dt); }
+ * + * 1 tick = 1/20 second
20 ticks = 1 second
1200 ticks = 1 minute
72000 ticks = 1 hour
1728000 ticks = 1 day + * + * @param dt time since the last tick in seconds + */ + public void tick(float dt); +} diff --git a/src/main/java/org/spout/api/store/BinaryFileStore.java b/src/main/java/org/spout/api/store/BinaryFileStore.java new file mode 100644 index 0000000..a6380be --- /dev/null +++ b/src/main/java/org/spout/api/store/BinaryFileStore.java @@ -0,0 +1,157 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.store; + +import com.flowpowered.commons.store.MemoryStore; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map.Entry; + +/** + * This implements a SimpleStore that is stored in memory. The save and load methods can be used to write the map to a binary file. + */ +public class BinaryFileStore extends MemoryStore { + private File file; + private boolean dirty = true; + + public BinaryFileStore(File file) { + super(); + this.file = file; + } + + public BinaryFileStore() { + this(null); + } + + public synchronized void setFile(File file) { + this.file = file; + } + + public synchronized File getFile() { + return file; + } + + @Override + public synchronized boolean clear() { + dirty = true; + return super.clear(); + } + + @Override + public synchronized boolean save() { + if (!dirty) { + return true; + } + + boolean saved = true; + DataOutputStream out = null; + try { + out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + Iterator> itr = super.getEntrySet().iterator(); + + while (itr.hasNext()) { + Entry next = itr.next(); + out.writeInt(next.getValue()); + out.writeUTF(next.getKey()); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + saved = false; + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + saved = false; + } + if (saved) { + dirty = false; + } + } + return saved; + } + + @Override + public synchronized boolean load() { + boolean loaded = true; + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); + + boolean eof = false; + while (!eof) { + try { + Integer id = in.readInt(); + String key = in.readUTF(); + set(key, id); + } catch (EOFException eofe) { + eof = true; + } + } + } catch (IOException ioe) { + loaded = false; + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException ioe) { + loaded = false; + } + } + if (loaded) { + dirty = false; + } + return loaded; + } + + @Override + public synchronized Integer remove(String key) { + Integer value = super.remove(key); + if (value != null) { + dirty = true; + } + return value; + } + + @Override + public synchronized Integer set(String key, Integer value) { + dirty = true; + return super.set(key, value); + } +} diff --git a/src/main/java/org/spout/api/store/FlatFileStore.java b/src/main/java/org/spout/api/store/FlatFileStore.java new file mode 100644 index 0000000..fd09092 --- /dev/null +++ b/src/main/java/org/spout/api/store/FlatFileStore.java @@ -0,0 +1,164 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.store; + +import com.flowpowered.commons.FileUtil; +import com.flowpowered.commons.store.MemoryStore; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map.Entry; + +/** + * This implements a SimpleStore that is stored in memory. The save and load methods can be used to write the map to a File. + */ +public class FlatFileStore extends MemoryStore { + private final File file; + private boolean dirty = false; + private final Class clazz; // preserve class, so parser knows what to do + + public FlatFileStore(File file, Class clazz) { + super(); + this.clazz = clazz; + this.file = file; + if (file != null) { + if (!file.exists()) { + if (!FileUtil.createFile(file)) { + } + } + } + } + + @Override + public synchronized boolean clear() { + dirty = true; + return super.clear(); + } + + @Override + public synchronized boolean save() { + if (!dirty) { + return true; + } + + Collection strings = getStrings(); + boolean saved = FileUtil.stringToFile(strings, file); + if (saved) { + dirty = false; + } + + return saved; + } + + @Override + public synchronized boolean load() { + Collection strings = FileUtil.fileToString(file); + if (strings == null) { + return false; + } + boolean loaded = processStrings(strings); + if (loaded) { + dirty = false; + } + return loaded; + } + + @Override + public synchronized T remove(String key) { + T value = super.remove(key); + if (value != null) { + dirty = true; + } + return value; + } + + @Override + public synchronized T set(String key, T value) { + dirty = true; + return super.set(key, value); + } + + private synchronized Collection getStrings() { + Iterator> itr = super.getEntrySet().iterator(); + ArrayList strings = new ArrayList<>(super.getSize()); + while (itr.hasNext()) { + Entry entry = itr.next(); + String encodedKey = encode(entry.getKey()); + T value = entry.getValue(); + strings.add(value + ":" + encodedKey); + } + return strings; + } + + private boolean processStrings(Collection strings) { + super.clear(); + for (String string : strings) { + String[] split = string.trim().split(":"); + if (split.length != 2) { + return false; + } + T value; + try { + value = parse(split[0]); + } catch (NumberFormatException nfe) { + return false; + } + String key = decode(split[1]); + set(key, value); + } + return true; + } + + private static String encode(String key) { + String encoded = key; + encoded = encoded.replace("\\", "\\\\"); + encoded = encoded.replace("\n", "\\n"); + encoded = encoded.replace(":", "\\:"); + return encoded; + } + + private static String decode(String encoded) { + String key = encoded; + key = key.replace("\\:", ":"); + key = key.replace("\\n", "\n"); + key = key.replace("\\\\", "\\"); + return encoded; + } + + @SuppressWarnings ("unchecked") + private T parse(String string) { + if (clazz.equals(Integer.class)) { + return (T) (Object) Integer.parseInt(string); + } else if (clazz.equals(String.class)) { + return (T) string; + } else { + throw new IllegalArgumentException("Unable to parse clazzes of type " + clazz.getName()); + } + } +} diff --git a/src/main/java/org/spout/api/util/SyncedMapEvent.java b/src/main/java/org/spout/api/util/SyncedMapEvent.java new file mode 100644 index 0000000..f5ea73b --- /dev/null +++ b/src/main/java/org/spout/api/util/SyncedMapEvent.java @@ -0,0 +1,62 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; + +import com.flowpowered.events.object.ObjectEvent; + +/** + * Event called when modifications occur on a StringMap + */ +public class SyncedMapEvent extends ObjectEvent { + public static enum Action { + ADD, + SET, + REMOVE, + } + + private final Action action; + private final List> modifiedElements; + + public SyncedMapEvent(SyncedStringMap map, Action action, List> modifiedElements) { + super(map); + this.action = action; + this.modifiedElements = Collections.unmodifiableList(modifiedElements); + } + + public Action getAction() { + return action; + } + + public List> getModifiedElements() { + return modifiedElements; + } +} diff --git a/src/main/java/org/spout/api/util/SyncedMapRegistry.java b/src/main/java/org/spout/api/util/SyncedMapRegistry.java new file mode 100644 index 0000000..14454a2 --- /dev/null +++ b/src/main/java/org/spout/api/util/SyncedMapRegistry.java @@ -0,0 +1,101 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import com.flowpowered.events.object.EventableBase; + +/** + * Represents a map for mapping Strings to unique ids. + * + * The class supports conversion of ids between maps and allocation of new unique ids for unknown Strings + * + * Conversions to and from parent/child maps are cached + */ +public final class SyncedMapRegistry extends EventableBase { + public static final byte REGISTRATION_MAP = -1; + protected static final SyncedStringMap STRING_MAP_REGISTRATION = new SyncedStringMap("REGISTRATION_MAP"); // This is a special case + protected static final ConcurrentMap> REGISTERED_MAPS = new ConcurrentHashMap<>(); + + public static SyncedStringMap get(String name) { + WeakReference ref = REGISTERED_MAPS.get(name); + if (ref != null) { + SyncedStringMap map = ref.get(); + if (map == null) { + REGISTERED_MAPS.remove(name); + } + return map; + } + return null; + } + + public static SyncedStringMap get(int id) { + if (id == REGISTRATION_MAP) { + return STRING_MAP_REGISTRATION; + } + String name = STRING_MAP_REGISTRATION.getString(id); + if (name != null) { + WeakReference ref = REGISTERED_MAPS.get(name); + if (ref != null) { + SyncedStringMap map = ref.get(); + if (map == null) { + REGISTERED_MAPS.remove(name); + } + return map; + } + } + return null; + } + + public static Collection getAll() { + Collection> rawMaps = REGISTERED_MAPS.values(); + List maps = new ArrayList<>(rawMaps.size()); + for (WeakReference ref : rawMaps) { + SyncedStringMap map = ref.get(); + if (map != null) { + maps.add(map); + } + } + return maps; + } + + public static SyncedStringMap getRegistrationMap() { + return STRING_MAP_REGISTRATION; + } + + public static int register(SyncedStringMap map) { + int id = STRING_MAP_REGISTRATION.register(map.getName()); + REGISTERED_MAPS.put(map.getName(), new WeakReference<>(map)); + return id; + } +} diff --git a/src/main/java/org/spout/api/util/SyncedStringMap.java b/src/main/java/org/spout/api/util/SyncedStringMap.java new file mode 100644 index 0000000..f391be4 --- /dev/null +++ b/src/main/java/org/spout/api/util/SyncedStringMap.java @@ -0,0 +1,139 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.flowpowered.commons.StringToUniqueIntegerMap; +import com.flowpowered.commons.store.SimpleStore; + +import org.apache.commons.lang3.tuple.Pair; + +import com.flowpowered.events.object.Eventable; +import com.flowpowered.events.object.EventableListener; + + +/** + * This class syncs a StringToUniqueIntegerMap from server to client + */ +public final class SyncedStringMap extends StringToUniqueIntegerMap implements Eventable { + private final CopyOnWriteArrayList> registeredListeners = new CopyOnWriteArrayList<>(); + private int id; + + protected SyncedStringMap(String name) { + super(name); + } + + protected SyncedStringMap(StringToUniqueIntegerMap parent, SimpleStore store, int minId, int maxId, String name) { + super(parent, store, minId, maxId, name); + } + + public static SyncedStringMap create(String name) { + SyncedStringMap map = new SyncedStringMap(name); + map.id = SyncedMapRegistry.register(map); + return map; + } + + public static SyncedStringMap create(StringToUniqueIntegerMap parent, SimpleStore store, int minId, int maxId, String name) { + SyncedStringMap map = new SyncedStringMap(parent, store, minId, maxId, name); + map.id = SyncedMapRegistry.register(map); + return map; + } + + @Override + public int register(String key) { + Integer id = store.get(key); + if (id != null) { + return id; + } + int local = super.register(key); + callEvent(new SyncedMapEvent(this, SyncedMapEvent.Action.ADD, Arrays.asList(Pair.of(local, key)))); + return local; + } + + @Override + public boolean register(String key, int id) { + Integer local = store.get(key); + if (local != null) { + return false; + } + callEvent(new SyncedMapEvent(this, SyncedMapEvent.Action.ADD, Arrays.asList(Pair.of(id, key)))); + return super.register(key, id); + } + + public void handleUpdate(SyncedMapEvent message) { + switch (message.getAction()) { + case SET: + super.clear(); + case ADD: + for (Pair pair : message.getModifiedElements()) { + store.set(pair.getValue(), pair.getKey()); + } + break; + case REMOVE: + for (Pair pair : message.getModifiedElements()) { + store.remove(pair.getValue()); + } + break; + } + callEvent(new SyncedMapEvent(this, message.getAction(), message.getModifiedElements())); + } + + @Override + public void clear() { + super.clear(); + callEvent(new SyncedMapEvent(this, SyncedMapEvent.Action.SET, new ArrayList>())); + } + + public int getId() { + return id; + } + + @Override + public void registerListener(EventableListener listener) { + registeredListeners.add(listener); + } + + @Override + public void unregisterAllListeners() { + registeredListeners.clear(); + } + + @Override + public void unregisterListener(EventableListener listener) { + registeredListeners.remove(listener); + } + + @Override + public void callEvent(SyncedMapEvent event) { + for (EventableListener listener : registeredListeners) { + listener.onEvent(event); + } + } +} diff --git a/src/main/java/org/spout/api/util/concurrent/ConcurrentList.java b/src/main/java/org/spout/api/util/concurrent/ConcurrentList.java new file mode 100644 index 0000000..cbae1d8 --- /dev/null +++ b/src/main/java/org/spout/api/util/concurrent/ConcurrentList.java @@ -0,0 +1,113 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.concurrent; + +import java.util.ArrayList; +import java.util.Collection; + +public class ConcurrentList extends ArrayList { + private static final long serialVersionUID = 1L; + /** + * A list of elements pending to be added + */ + private final ArrayList toAdd = new ArrayList<>(); + /** + * A list of elements pending to be removed + */ + private final ArrayList toRemove = new ArrayList<>(); + + /** + * Adds all current values to the removal queue + */ + public void clearDelayed() { + synchronized (toAdd) { + toAdd.clear(); + } + synchronized (toRemove) { + toRemove.clear(); + toRemove.addAll(this); + } + } + + /** + * Adds a value to the addition queue for this list + * + * @param value to add + */ + public void addDelayed(T value) { + synchronized (toAdd) { + toAdd.add(value); + } + } + + /** + * Adds all values to the addition queue for this list + * + * @param values to add + */ + public void addAllDelayed(Collection values) { + synchronized (toAdd) { + toAdd.addAll(values); + } + } + + /** + * Adds a value to the removal queue for this list + * + * @param value to remove + */ + public void removeDelayed(T value) { + synchronized (toRemove) { + toRemove.add(value); + } + } + + /** + * Adds all values to the removal queue for this list + * + * @param values to remove + */ + public void removeAllDelayed(Collection values) { + synchronized (toRemove) { + toRemove.addAll(values); + } + } + + /** + * Synchronizes this list with all previously done delayed operations + */ + public void sync() { + synchronized (toAdd) { + super.addAll(toAdd); + toAdd.clear(); + } + synchronized (toRemove) { + super.removeAll(toRemove); + toRemove.clear(); + } + } +} diff --git a/src/main/java/org/spout/api/util/concurrent/ConcurrentLongPriorityQueue.java b/src/main/java/org/spout/api/util/concurrent/ConcurrentLongPriorityQueue.java new file mode 100644 index 0000000..2ad95ac --- /dev/null +++ b/src/main/java/org/spout/api/util/concurrent/ConcurrentLongPriorityQueue.java @@ -0,0 +1,132 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.concurrent; + +import java.util.Map.Entry; +import java.util.Queue; +import java.util.concurrent.ConcurrentSkipListMap; + +import org.spout.math.GenericMath; + +public class ConcurrentLongPriorityQueue { + private final long keyMask; + private final long keyStep; + protected final ConcurrentSkipListMap> queueMap = new ConcurrentSkipListMap<>(); + + public ConcurrentLongPriorityQueue(long resolution) { + if (resolution < 1) { + resolution = 1; + } + long mask = GenericMath.roundUpPow2(resolution); + while (mask > resolution) { + mask >>= 1; + } + this.keyMask = ~(mask - 1); + this.keyStep = mask; + } + + /** + * Adds a prioritized element to the queue + */ + public boolean add(T o) { + return addRaw(o); + } + + /** + * This method is used to add elements to the queue when redirecting. It should not be used for other purposes. + */ + public boolean redirect(T o) { + return addRaw(o); + } + + private boolean addRaw(T o) { + Long key = getKey(o.getPriority()); + RedirectableConcurrentLinkedQueue queue = queueMap.get(key); + if (queue == null) { + queue = new RedirectableConcurrentLinkedQueue<>(key); + RedirectableConcurrentLinkedQueue previous = queueMap.putIfAbsent(key, queue); + if (previous != null) { + queue = previous; + } + } + return queue.add(o); + } + + /** + * Removes a prioritized element from the queue + */ + public boolean remove(T o) { + Long key = getKey(o.getPriority()); + RedirectableConcurrentLinkedQueue queue = queueMap.get(key); + if (queue == null) { + return false; + } + return queue.remove(o); + } + + /** + * Polls the queue for entries with a priority before or equal to the given threshold.
The sub-queue returned may have some entries that occur after the threshold and may not include all entries + * that occur before the threshold. The method returns null if there are no sub-queues before the threshold + */ + public Queue poll(long threshold) { + Entry> first = queueMap.firstEntry(); + if (first == null || first.getKey() > threshold) { + return null; + } else { + return first.getValue(); + } + } + + /** + * This method must be called for every sub-queue that is returned by the poll method. + * + * @param queue the queue that is returned + * @return true if the threshold was covered by this sub-queue, so no further calls to poll() are required + */ + public boolean complete(Queue queue, long threshold) { + RedirectableConcurrentLinkedQueue q = (RedirectableConcurrentLinkedQueue) queue; + boolean empty = q.isEmpty(); + if (empty) { + queueMap.remove(q.getPriority(), q); + q.setRedirect(this); + q.dumpToRedirect(this); + } + return q.getPriority() + keyStep > threshold; + } + + /** + * Returns true if the given queue is completely below the threshold + */ + public boolean isFullyBelowThreshold(Queue queue, long threshold) { + RedirectableConcurrentLinkedQueue q = (RedirectableConcurrentLinkedQueue) queue; + return q.getPriority() + keyStep <= threshold; + } + + private Long getKey(long priority) { + return priority & keyMask; + } +} diff --git a/src/main/java/org/spout/api/util/concurrent/LongPrioritized.java b/src/main/java/org/spout/api/util/concurrent/LongPrioritized.java new file mode 100644 index 0000000..9f43ab1 --- /dev/null +++ b/src/main/java/org/spout/api/util/concurrent/LongPrioritized.java @@ -0,0 +1,34 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.concurrent; + +public interface LongPrioritized { + /** + * Gets the priority of this object + */ + public long getPriority(); +} diff --git a/src/main/java/org/spout/api/util/concurrent/RedirectableConcurrentLinkedQueue.java b/src/main/java/org/spout/api/util/concurrent/RedirectableConcurrentLinkedQueue.java new file mode 100644 index 0000000..12acd78 --- /dev/null +++ b/src/main/java/org/spout/api/util/concurrent/RedirectableConcurrentLinkedQueue.java @@ -0,0 +1,68 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.concurrent; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicReference; + +public class RedirectableConcurrentLinkedQueue extends ConcurrentLinkedQueue implements LongPrioritized { + private static final long serialVersionUID = 1L; + private final AtomicReference> redirect = new AtomicReference<>(); + private final long priority; + + public RedirectableConcurrentLinkedQueue(long priority) { + this.priority = priority; + } + + @Override + public boolean add(T e) { + super.add(e); + ConcurrentLongPriorityQueue r = redirect.get(); + if (r != null) { + dumpToRedirect(r); + } + return true; + } + + public void dumpToRedirect(ConcurrentLongPriorityQueue target) { + T next; + while ((next = poll()) != null) { + target.redirect(next); + } + } + + public void setRedirect(ConcurrentLongPriorityQueue target) { + if (!redirect.compareAndSet(null, target)) { + throw new IllegalStateException("Redirect may not be set more than once per redirectable queue"); + } + } + + @Override + public long getPriority() { + return priority; + } +} diff --git a/src/main/java/org/spout/api/util/concurrent/SpinLock.java b/src/main/java/org/spout/api/util/concurrent/SpinLock.java new file mode 100644 index 0000000..c474835 --- /dev/null +++ b/src/main/java/org/spout/api/util/concurrent/SpinLock.java @@ -0,0 +1,158 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.concurrent; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +/** + * A non-reentrant spin lock.

The lock will spin 100 times before falling back to using wait/notify. + */ +public class SpinLock implements Lock { + private static int MAX_SPINS = 100; + private AtomicBoolean locked = new AtomicBoolean(); + private AtomicInteger waiting = new AtomicInteger(); + + @Override + public void lock() { + for (int i = 0; i < MAX_SPINS; i++) { + if (tryLock()) { + return; + } + } + + boolean interrupted = false; + boolean success = false; + + try { + while (!success) { + try { + waitLock(); + success = true; + } catch (InterruptedException ie) { + interrupted = true; + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + for (int i = 0; i < MAX_SPINS; i++) { + if (tryLock()) { + return; + } + } + waitLock(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + long endTime = System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(time, unit) + 1; + boolean timedOut = false; + while (!tryLock()) { + timedOut = System.currentTimeMillis() >= endTime; + if (timedOut) { + break; + } + } + return !timedOut; + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock() { + return locked.compareAndSet(false, true); + } + + private void waitLock() throws InterruptedException { + waiting.incrementAndGet(); + try { + synchronized (waiting) { + while (!tryLock()) { + waiting.wait(); + } + } + } finally { + waiting.decrementAndGet(); + } + } + + @Override + public void unlock() { + if (!locked.compareAndSet(true, false)) { + throw new IllegalStateException("Attempt to unlock lock when it isn't locked"); + } + if (!waiting.compareAndSet(0, 0)) { + synchronized (waiting) { + waiting.notifyAll(); + } + } + } + + public static void dualLock(Lock a, Lock b) { + if (a == b) { + a.lock(); + return; + } + + while (true) { + a.lock(); + if (b.tryLock()) { + return; + } + a.unlock(); + b.lock(); + if (a.tryLock()) { + return; + } + b.unlock(); + } + } + + public static void dualUnlock(Lock a, Lock b) { + try { + a.unlock(); + } finally { + if (b != a) { + b.unlock(); + } + } + } +} + diff --git a/src/main/java/org/spout/api/util/cuboid/CuboidBlockMaterialBuffer.java b/src/main/java/org/spout/api/util/cuboid/CuboidBlockMaterialBuffer.java new file mode 100644 index 0000000..d3e7397 --- /dev/null +++ b/src/main/java/org/spout/api/util/cuboid/CuboidBlockMaterialBuffer.java @@ -0,0 +1,207 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.cuboid; + +import java.util.Arrays; + +import org.spout.api.material.BlockMaterial; +import org.spout.math.vector.Vector3f; + +public class CuboidBlockMaterialBuffer extends ImmutableCuboidBlockMaterialBuffer { + private CuboidBlockMaterialBuffer source; + private final ImmutableCuboidBlockMaterialBuffer backBuffer; + + public CuboidBlockMaterialBuffer(CuboidBlockMaterialBuffer buffer) { + this(buffer, false); + } + + public CuboidBlockMaterialBuffer(CuboidBlockMaterialBuffer buffer, boolean backBuffer) { + super(buffer); + if (backBuffer) { + this.backBuffer = new ImmutableCuboidBlockMaterialBuffer(this); + } else { + this.backBuffer = null; + } + } + + public CuboidBlockMaterialBuffer(int baseX, int baseY, int baseZ, int sizeX, int sizeY, int sizeZ, short[] id, short[] data) { + this(baseX, baseY, baseZ, sizeX, sizeY, sizeZ, id, data, false); + } + + public CuboidBlockMaterialBuffer(int baseX, int baseY, int baseZ, int sizeX, int sizeY, int sizeZ, short[] id, short[] data, boolean backBuffer) { + super(baseX, baseY, baseZ, sizeX, sizeY, sizeZ, id, data); + if (backBuffer) { + this.backBuffer = new ImmutableCuboidBlockMaterialBuffer(this); + } else { + this.backBuffer = null; + } + } + + public CuboidBlockMaterialBuffer(int baseX, int baseY, int baseZ, int sizeX, int sizeY, int sizeZ) { + this(baseX, baseY, baseZ, sizeX, sizeY, sizeZ, false); + } + + public CuboidBlockMaterialBuffer(int baseX, int baseY, int baseZ, int sizeX, int sizeY, int sizeZ, boolean backBuffer) { + super(baseX, baseY, baseZ, sizeX, sizeY, sizeZ); + if (backBuffer) { + this.backBuffer = new ImmutableCuboidBlockMaterialBuffer(this); + } else { + this.backBuffer = null; + } + } + + public CuboidBlockMaterialBuffer(double baseX, double baseY, double baseZ, double sizeX, double sizeY, double sizeZ) { + this(baseX, baseY, baseZ, sizeX, sizeY, sizeZ, false); + } + + public CuboidBlockMaterialBuffer(double baseX, double baseY, double baseZ, double sizeX, double sizeY, double sizeZ, boolean backBuffer) { + super(baseX, baseY, baseZ, sizeX, sizeY, sizeZ); + if (backBuffer) { + this.backBuffer = new ImmutableCuboidBlockMaterialBuffer(this); + } else { + this.backBuffer = null; + } + } + + public CuboidBlockMaterialBuffer(Vector3f base, Vector3f size) { + this(base, size, false); + } + + public CuboidBlockMaterialBuffer(Vector3f base, Vector3f size, boolean backBuffer) { + super(base, size); + if (backBuffer) { + this.backBuffer = new ImmutableCuboidBlockMaterialBuffer(this); + } else { + this.backBuffer = null; + } + } + + @Override + public void copyElement(int thisIndex, int sourceIndex, int runLength) { + final int end = thisIndex + runLength; + for (; thisIndex < end; thisIndex++) { + id[thisIndex] = source.id[sourceIndex]; + data[thisIndex] = source.data[sourceIndex++]; + } + } + + @Override + public void setSource(CuboidBuffer source) { + if (source instanceof CuboidBlockMaterialBuffer) { + this.source = (CuboidBlockMaterialBuffer) source; + } else { + throw new IllegalArgumentException("Only CuboidShortBuffers may be used as the data source when copying to a CuboidShortBuffer"); + } + } + + /** + * Sets a horizontal layer of blocks to a given material + * + * @param y - coordinate of the start of the layer + * @param height of the layer + * @param material to set to + */ + public void setHorizontalLayer(int y, int height, BlockMaterial material) { + setHorizontalLayer(y, height, material.getId(), material.getData()); + } + + /** + * Sets a horizontal layer of blocks to a given material id and data + * + * @param y - coordinate of the start of the layer + * @param height of the layer + * @param id of the material to set to + * @param data to set to + */ + public void setHorizontalLayer(int y, int height, short id, short data) { + final int startIndex = getIndex(this.baseX, y, this.baseZ); + final int endIndex = getIndex(this.topX - 1, y + height - 1, this.topZ - 1) + 1; + if (startIndex < 0 || endIndex <= 0) { + throw new IllegalArgumentException("Layer Y-Coordinate (y=" + y + ", height=" + height + ") are outside the buffer"); + } + Arrays.fill(this.id, startIndex, endIndex, id); + Arrays.fill(this.data, startIndex, endIndex, data); + } + + /** + * Sets a single block material + * + * @param x - coordinate of the block + * @param y - coordinate of the block + * @param z - coordinate of the block + * @param material to set to + */ + public void set(int x, int y, int z, BlockMaterial material) { + int index = getIndex(x, y, z); + if (index < 0) { + throw new IllegalArgumentException("Coordinate (" + x + ", " + y + ", " + z + ") is outside the buffer"); + } + + this.id[index] = material.getId(); + this.data[index] = material.getData(); + } + + /** + * Sets a single block material id and data + * + * @param x - coordinate of the block + * @param y - coordinate of the block + * @param z - coordinate of the block + * @param id of the material to set to + * @param data to set to + */ + public void set(int x, int y, int z, short id, short data) { + int index = getIndex(x, y, z); + if (index < 0) { + throw new IllegalArgumentException("Coordinate (" + x + ", " + y + ", " + z + ") is outside the buffer"); + } + + this.id[index] = id; + this.data[index] = data; + } + + public void flood(BlockMaterial material) { + for (int i = 0; i < id.length; i++) { + this.id[i] = material.getId(); + this.data[i] = material.getData(); + } + } + + @Override + public short[] getRawId() { + return id; + } + + @Override + public short[] getRawData() { + return data; + } + + public ImmutableCuboidBlockMaterialBuffer getBackBuffer() { + return backBuffer == null ? this : backBuffer; + } +} diff --git a/src/main/java/org/spout/api/util/cuboid/CuboidBuffer.java b/src/main/java/org/spout/api/util/cuboid/CuboidBuffer.java new file mode 100644 index 0000000..04d8bb0 --- /dev/null +++ b/src/main/java/org/spout/api/util/cuboid/CuboidBuffer.java @@ -0,0 +1,271 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.cuboid; + +import org.spout.api.geo.cuboid.Chunk; + +import org.spout.math.vector.Vector3f; + +/** + * This class implements a Cuboid common methods for a one dimensional array Cuboid Buffer + * + * Elements are stored in column order and each column is +1 on the Z dimension relative to the previous one. + * + * Each YZ plane is followed by the plane corresponding to +1 on the X dimension. + * + * It is assumed that the Cuboid has dimensions (SX, SY, SZ) and the base is set at the origin. + * + * buffer[0] = data(0, 0, 0 ) buffer[1] = data(0, 1, 0 ) ..... buffer[SY-1] = data(0, SY-1, 0 ) buffer[SY] = data(0, 0 1 ) .... buffer[SZ*SY - 1] = data(0, SY-1, SZ-1) buffer[SZ*SY] = data(1, 0, 0 ) + * .... buffer[SZ*SY*SX -1] = data(SX-1, SY-1, SZ-1) + * + * TODO is this the best package to put this? + */ +public abstract class CuboidBuffer { + protected final Vector3f size; + protected final int sizeX; + protected final int sizeY; + protected final int sizeZ; + protected final Vector3f base; + protected final int baseX; + protected final int baseY; + protected final int baseZ; + /* + * Note: These values are not actually within the cuboid The cuboid goes + * from baseX to baseX + sizeX - 1 top = base + size + */ + protected final Vector3f top; + protected final int topX; + protected final int topY; + protected final int topZ; + protected final int Xinc; + protected final int Yinc; + protected final int Zinc; + + protected CuboidBuffer(int baseX, int baseY, int baseZ, int sizeX, int sizeY, int sizeZ) { + this.sizeX = sizeX; + this.sizeY = sizeY; + this.sizeZ = sizeZ; + + this.size = new Vector3f(sizeX, sizeY, sizeZ); + + this.baseX = baseX; + this.baseY = baseY; + this.baseZ = baseZ; + + this.base = new Vector3f(baseX, baseY, baseZ); + + this.topX = baseX + sizeX; + this.topY = baseY + sizeY; + this.topZ = baseZ + sizeZ; + + this.top = new Vector3f(this.topX, this.topY, this.topZ); + + Yinc = sizeZ * (Zinc = sizeX * (Xinc = 1)); + } + + protected CuboidBuffer(double baseX, double baseY, double baseZ, double sizeX, double sizeY, double sizeZ) { + this((int) baseX, (int) baseY, (int) baseZ, (int) sizeX, (int) sizeY, (int) sizeZ); + } + + protected CuboidBuffer(Vector3f base, Vector3f size) { + this(base.getX(), base.getY(), base.getZ(), size.getX(), size.getY(), size.getZ()); + } + + /** + * Gets a Point representing the base of this CuboidBuffer + */ + public Vector3f getBase() { + return base; + } + + /** + * Gets the X-coordinate of the chunk the base of this CuboidBuffer is in + * + * @return base chunk X-coordinate + */ + public int getBaseChunkX() { + return baseX >> Chunk.BLOCKS.BITS; + } + + /** + * Gets the Y-coordinate of the chunk the base of this CuboidBuffer is in + * + * @return base chunk Y-coordinate + */ + public int getBaseChunkY() { + return baseY >> Chunk.BLOCKS.BITS; + } + + /** + * Gets the Z-coordinate of the chunk the base of this CuboidBuffer is in + * + * @return base chunk Z-coordinate + */ + public int getBaseChunkZ() { + return baseZ >> Chunk.BLOCKS.BITS; + } + + /** + * Gets the size of the CuboidBuffer + */ + public Vector3f getSize() { + return size; + } + + /** + * Gets the volume of the CuboidBuffer + */ + public int getVolume() { + return sizeX * sizeY * sizeZ; + } + + /** + * Gets the top-coordinates of the CuboidBuffer, these are outside this buffer
These coordinates are an addition of base and size + */ + public Vector3f getTop() { + return top; + } + + /** + * Return true if the coordinates are inside the buffer. + * + * @param x The x coordinate to check. + * @param y The y coordinate to check. + * @param z The Z coordinate to check. + * @return True if the coordinate are in the buffer, false if not. + */ + public boolean isInside(int x, int y, int z) { + return getIndex(x, y, z) >= 0; + } + + /** + * Copies the data contained within the given CuboidShortBuffer to this one. Any non-overlapping locations are ignored + * + * @param source The CuboidShortBuffer source from which to copy the data. + */ + public void write(CuboidBuffer source) { + CuboidBufferCopyRun run = new CuboidBufferCopyRun(source, this); + + int sourceIndex = run.getBaseSource(); + int thisIndex = run.getBaseTarget(); + int runLength = run.getLength(); + int innerRepeats = run.getInnerRepeats(); + int outerRepeats = run.getOuterRepeats(); + + setSource(source); + + if (!(sourceIndex == -1 || thisIndex == -1)) { + for (int x = 0; x < outerRepeats; x++) { + int outerSourceIndex = sourceIndex; + int outerThisIndex = thisIndex; + for (int z = 0; z < innerRepeats; z++) { + copyElement(outerThisIndex, outerSourceIndex, runLength); + + outerSourceIndex += source.Zinc; + outerThisIndex += Zinc; + } + sourceIndex += source.Yinc; + thisIndex += Yinc; + } + } + } + + protected int getIndex(int x, int y, int z) { + return getIndex(this, x, y, z); + } + + protected static int getIndex(CuboidBuffer source, int x, int y, int z) { + if (x < source.baseX || x >= source.topX || y < source.baseY || y >= source.topY || z < source.baseZ || z >= source.topZ) { + return -1; + } + + return (y - source.baseY) * source.Yinc + (z - source.baseZ) * source.Zinc + (x - source.baseX) * source.Xinc; + } + + protected CuboidBufferCopyRun getCopyRun(CuboidBuffer other) { + return new CuboidBufferCopyRun(this, other); + } + + public abstract void copyElement(int thisIndex, int sourceIndex, int runLength); + + public abstract void setSource(CuboidBuffer source); + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{Buffer Size=" + sizeX * sizeY * sizeZ + ", Base=(" + baseX + ", " + baseY + ", " + baseZ + "}, Size=(" + sizeX + ", " + sizeY + ", " + sizeZ + "), " + "Increments=(" + Xinc + ", " + Yinc + ", " + Zinc + "), Top=(" + topX + ", " + topY + ", " + topZ + ")}"; + } + + protected static class CuboidBufferCopyRun { + private int overlapBaseX; + private int overlapBaseY; + private int overlapBaseZ; + private int overlapSizeX; + private int overlapSizeY; + private int overlapSizeZ; + private int sourceIndex; + private int targetIndex; + + public CuboidBufferCopyRun(CuboidBuffer source, CuboidBuffer target) { + overlapBaseX = Math.max(source.baseX, target.baseX); + overlapBaseY = Math.max(source.baseY, target.baseY); + overlapBaseZ = Math.max(source.baseZ, target.baseZ); + + overlapSizeX = Math.min(source.topX, target.topX) - overlapBaseX; + overlapSizeY = Math.min(source.topY, target.topY) - overlapBaseY; + overlapSizeZ = Math.min(source.topZ, target.topZ) - overlapBaseZ; + + if (overlapSizeX < 0 || overlapSizeY < 0 || overlapSizeZ < 0) { + sourceIndex = -1; + targetIndex = -1; + return; + } + + sourceIndex = getIndex(source, overlapBaseX, overlapBaseY, overlapBaseZ); + targetIndex = getIndex(target, overlapBaseX, overlapBaseY, overlapBaseZ); + } + + public int getBaseSource() { + return sourceIndex; + } + + public int getBaseTarget() { + return targetIndex; + } + + public int getLength() { + return overlapSizeX; + } + + public int getInnerRepeats() { + return overlapSizeZ; + } + + public int getOuterRepeats() { + return overlapSizeY; + } + } +} diff --git a/src/main/java/org/spout/api/util/cuboid/ImmutableCuboidBlockMaterialBuffer.java b/src/main/java/org/spout/api/util/cuboid/ImmutableCuboidBlockMaterialBuffer.java new file mode 100644 index 0000000..7d8e1bc --- /dev/null +++ b/src/main/java/org/spout/api/util/cuboid/ImmutableCuboidBlockMaterialBuffer.java @@ -0,0 +1,119 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.cuboid; + +import org.spout.api.material.BlockMaterial; +import org.spout.math.vector.Vector3f; +import org.spout.api.util.cuboid.procedure.CuboidBlockMaterialProcedure; + +public class ImmutableCuboidBlockMaterialBuffer extends CuboidBuffer { + protected final short[] id; + protected final short[] data; + + public ImmutableCuboidBlockMaterialBuffer(CuboidBlockMaterialBuffer buffer) { + super(buffer.getBase().getFloorX(), buffer.getBase().getFloorY(), buffer.getBase().getFloorZ(), buffer.getSize().getFloorX(), buffer.getSize().getFloorY(), buffer.getSize().getFloorZ()); + this.id = new short[buffer.id.length]; + this.data = new short[buffer.data.length]; + System.arraycopy(buffer.id, 0, this.id, 0, buffer.id.length); + System.arraycopy(buffer.data, 0, this.data, 0, buffer.data.length); + } + + public ImmutableCuboidBlockMaterialBuffer(int baseX, int baseY, int baseZ, int sizeX, int sizeY, int sizeZ, short[] id, short[] data) { + super(baseX, baseY, baseZ, sizeX, sizeY, sizeZ); + this.id = id; + this.data = data; + } + + public ImmutableCuboidBlockMaterialBuffer(int baseX, int baseY, int baseZ, int sizeX, int sizeY, int sizeZ) { + this(baseX, baseY, baseZ, sizeX, sizeY, sizeZ, new short[sizeX * sizeY * sizeZ], new short[sizeX * sizeY * sizeZ]); + } + + public ImmutableCuboidBlockMaterialBuffer(double baseX, double baseY, double baseZ, double sizeX, double sizeY, double sizeZ) { + this((int) baseX, (int) baseY, (int) baseZ, (int) sizeX, (int) sizeY, (int) sizeZ, new short[(int) (sizeX * sizeY * sizeZ)], new short[(int) (sizeX * sizeY * sizeZ)]); + } + + public ImmutableCuboidBlockMaterialBuffer(Vector3f base, Vector3f size) { + this((int) base.getX(), (int) base.getY(), (int) base.getZ(), (int) size.getX(), (int) size.getY(), (int) size.getZ(), new short[(int) (size.getX() * size.getY() * size.getZ())], new short[(int) (size.getX() * size.getY() * size.getZ())]); + } + + @Override + public void copyElement(int thisIndex, int sourceIndex, int runLength) { + throw new UnsupportedOperationException("This buffer is immutable"); + } + + @Override + public void setSource(CuboidBuffer source) { + } + + public BlockMaterial get(int x, int y, int z) { + int index = getIndex(x, y, z); + if (index < 0) { + throw new IllegalArgumentException("Coordinate (" + x + ", " + y + ", " + z + ") is outside the buffer"); + } + + return BlockMaterial.get(id[index], data[index]); + } + + public short getId(int x, int y, int z) { + int index = getIndex(x, y, z); + if (index < 0) { + throw new IllegalArgumentException("Coordinate (" + x + ", " + y + ", " + z + ") is outside the buffer"); + } + return id[index]; + } + + public short getData(int x, int y, int z) { + int index = getIndex(x, y, z); + if (index < 0) { + throw new IllegalArgumentException("Coordinate (" + x + ", " + y + ", " + z + ") is outside the buffer"); + } + + return data[index]; + } + + public void forEach(CuboidBlockMaterialProcedure procedure) { + int index = 0; + for (int y = baseY; y < topY; y++) { + for (int z = baseZ; z < topZ; z++) { + for (int x = baseX; x < topX; x++) { + if (!procedure.execute(x, y, z, id[index], data[index])) { + return; + } + index++; + } + } + } + } + + public short[] getRawId() { + return id; + } + + public short[] getRawData() { + return data; + } +} diff --git a/src/main/java/org/spout/api/util/cuboid/procedure/CuboidBlockMaterialProcedure.java b/src/main/java/org/spout/api/util/cuboid/procedure/CuboidBlockMaterialProcedure.java new file mode 100644 index 0000000..6e1fb6a --- /dev/null +++ b/src/main/java/org/spout/api/util/cuboid/procedure/CuboidBlockMaterialProcedure.java @@ -0,0 +1,40 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.cuboid.procedure; + +public interface CuboidBlockMaterialProcedure { + /** + * Procedure for iterating over a CuboidBlockMaterialBuffer + * + * @param x the x coordinate + * @param y the y coordinate + * @param z the z coordinate + * @param id the id + * @param data the data + */ + public boolean execute(int x, int y, int z, short id, short data); +} diff --git a/src/main/java/org/spout/api/util/future/SimpleFuture.java b/src/main/java/org/spout/api/util/future/SimpleFuture.java new file mode 100644 index 0000000..4425ee4 --- /dev/null +++ b/src/main/java/org/spout/api/util/future/SimpleFuture.java @@ -0,0 +1,139 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.future; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +public class SimpleFuture implements Future { + private static Object THROWABLE = new Object(); + private static Object CANCEL = new Object(); + private static Object NULL = new Object(); + private AtomicReference resultRef = new AtomicReference<>(null); + private AtomicReference throwable = new AtomicReference<>(null); + + @SuppressWarnings ("unchecked") + public boolean setThrowable(Throwable t) { + if (!throwable.compareAndSet(null, t)) { + return false; + } + + if (!resultRef.compareAndSet(null, (T) THROWABLE)) { + return false; + } + + synchronized (resultRef) { + resultRef.notifyAll(); + } + + return true; + } + + @SuppressWarnings ("unchecked") + public boolean setResult(T result) { + if (result == null) { + result = (T) NULL; + } + + if (!resultRef.compareAndSet(null, result)) { + return false; + } + + synchronized (resultRef) { + resultRef.notifyAll(); + } + + return true; + } + + @SuppressWarnings ("unchecked") + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return resultRef.compareAndSet(null, (T) CANCEL); + } + + @Override + public boolean isCancelled() { + return resultRef.get() == CANCEL; + } + + @Override + public boolean isDone() { + return resultRef.get() != null; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + try { + return get(0, TimeUnit.MILLISECONDS); + } catch (TimeoutException toe) { + throw new IllegalStateException("Attempting to get with an infinite timeout should not cause a timeout exception", toe); + } + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + boolean noTimeout = timeout <= 0; + + long timeoutInMS = unit.toMillis(timeout); + + if (!noTimeout && timeoutInMS <= 0) { + timeoutInMS = 1; + } + long currentTime = System.currentTimeMillis(); + long endTime = currentTime + timeoutInMS; + + while (noTimeout || currentTime < endTime) { + synchronized (resultRef) { + T result = resultRef.get(); + if (result != null) { + if (result == NULL || result == CANCEL) { + return null; + } + + if (result == THROWABLE) { + Throwable t = throwable.get(); + throw new ExecutionException("Exception occured when trying to retrieve the result of this future", t); + } + + return result; + } + + if (noTimeout) { + resultRef.wait(); + } else { + resultRef.wait(endTime - currentTime); + } + } + currentTime = System.currentTimeMillis(); + } + throw new TimeoutException("Wait duration of " + (currentTime - (endTime - timeoutInMS)) + "ms exceeds timeout of " + timeout + unit.toString()); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/ArrayHash.java b/src/main/java/org/spout/api/util/hashing/ArrayHash.java new file mode 100644 index 0000000..8b4de4d --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/ArrayHash.java @@ -0,0 +1,61 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class ArrayHash { + public static long hash(byte[] array) { + long hash = 1; + for (int i = 0; i < array.length; i++) { + hash += (hash << 5) + array[i]; + } + return hash; + } + + public static long hash(short[] array) { + long hash = 1; + for (int i = 0; i < array.length; i++) { + hash += (hash << 5) + array[i]; + } + return hash; + } + + public static long hash(int[] array) { + long hash = 1; + for (int i = 0; i < array.length; i++) { + hash += (hash << 5) + array[i]; + } + return hash; + } + + public static long hash(long[] array) { + long hash = 1; + for (int i = 0; i < array.length; i++) { + hash += (hash << 5) + array[i]; + } + return hash; + } +} diff --git a/src/main/java/org/spout/api/util/hashing/ByteShortByteHashed.java b/src/main/java/org/spout/api/util/hashing/ByteShortByteHashed.java new file mode 100644 index 0000000..744cd54 --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/ByteShortByteHashed.java @@ -0,0 +1,71 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class ByteShortByteHashed { + /** + * Creates a long key from 2 bytes and a short + * + * @param key1 a byte value + * @param key2 a short value + * @param key3 a byte value + * @return a long which is the concatenation of key1, key2 and key3 + */ + public static int key(int key1, int key2, int key3) { + return (key1 & 0xFF) << 24 | (key3 & 0xFF) << 16 | key2 & 0xFFFF; + } + + /** + * Gets the first 8-bit integer value from a long key + * + * @param key to get from + * @return the first 8-bit integer value in the key + */ + public static byte key1(int key) { + return (byte) (key >> 24); + } + + /** + * Gets the second 16-bit integer value from a long key + * + * @param key to get from + * @return the second 16-bit integer value in the key + */ + public static short key2(int key) { + return (short) key; + } + + /** + * Gets the third 8-bit integer value from a long key + * + * @param key to get from + * @return the third 8-bit integer value in the key + */ + public static byte key3(int key) { + return (byte) (key >> 16); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/ByteTripleHashed.java b/src/main/java/org/spout/api/util/hashing/ByteTripleHashed.java new file mode 100644 index 0000000..5581f02 --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/ByteTripleHashed.java @@ -0,0 +1,71 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class ByteTripleHashed { + /** + * Packs the first 8 most significant bits of each byte into an int + * + * @param x an byte value + * @param y an byte value + * @param z an byte value + * @return The first 8 most significant bits of each byte packed into an int + */ + public static int key(int x, int y, int z) { + return (x & 0xFF) << 16 | (z & 0xFF) << 8 | y & 0xFF; + } + + /** + * Gets the first 8-bit integer value from an int key + * + * @param key to get from + * @return the first 8-bit integer value in the key + */ + public static byte key1(int key) { + return (byte) (key >> 16 & 0xFF); + } + + /** + * Gets the second 8-bit integer value from an int key + * + * @param key to get from + * @return the second 8-bit integer value in the key + */ + public static byte key2(int key) { + return (byte) (key & 0xFF); + } + + /** + * Gets the third 8-bit integer value from an int key + * + * @param key to get from + * @return the third 8-bit integer value in the key + */ + public static byte key3(int key) { + return (byte) (key >> 8 & 0xFF); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/Int10TripleHashed.java b/src/main/java/org/spout/api/util/hashing/Int10TripleHashed.java new file mode 100644 index 0000000..cc50f64 --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/Int10TripleHashed.java @@ -0,0 +1,93 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class Int10TripleHashed { + private int bx; + private int by; + private int bz; + + public Int10TripleHashed() { + } + + public Int10TripleHashed(int bx, int by, int bz) { + this.bx = bx; + this.by = by; + this.bz = bz; + } + + /** + * Sets the base of the hash to the given values + */ + public final void setBase(int bx, int by, int bz) { + this.bx = bx; + this.by = by; + this.bz = bz; + } + + /** + * Packs given x, y, z coordinates. The coords must represent a point within a 1024 sized cuboid with the base at the (bx, by, bz) + * + * @param x an int value + * @param y an int value + * @param z an int value + * @return the packed int + */ + public final int key(int x, int y, int z) { + return (((x - bx) & 0x3FF) << 22) | (((y - by) & 0x3FF) << 11) | ((z - bz) & 0x3FF); + } + + /** + * Gets the x coordinate value from the int key + * + * @param key to get from + * @return the x coord + */ + public final int keyX(int key) { + return bx + ((key >> 22) & 0x3FF); + } + + /** + * Gets the y coordinate value from the int key + * + * @param key to get from + * @return the y coord + */ + public final int keyY(int key) { + return by + ((key >> 11) & 0x3FF); + } + + /** + * Gets the y coordinate value from the int key + * + * @param key to get from + * @return the y coord + */ + public final int keyZ(int key) { + return bz + (key & 0x3FF); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/Int21TripleHashed.java b/src/main/java/org/spout/api/util/hashing/Int21TripleHashed.java new file mode 100644 index 0000000..545e0c7 --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/Int21TripleHashed.java @@ -0,0 +1,75 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class Int21TripleHashed { + /** + * Packs the most significant and the twenty least significant of each int into a long + * + * @param x an int value + * @param y an int value + * @param z an int value + * @return the most significant and the twenty least significant of each int packed into a long + */ + public static long key(int x, int y, int z) { + return ((long) ((x >> 11) & 0x100000 | x & 0xFFFFF)) << 42 | ((long) ((y >> 11) & 0x100000 | y & 0xFFFFF)) << 21 | ((z >> 11) & 0x100000 | z & 0xFFFFF); + } + + /** + * Gets the first 21-bit integer value from a long key + * + * @param key to get from + * @return the first 21-bit integer value in the key + */ + public static int key1(long key) { + return keyInt((key >> 42) & 0x1FFFFF); + } + + /** + * Gets the second 21-bit integer value from a long key + * + * @param key to get from + * @return the second 21-bit integer value in the key + */ + public static int key2(long key) { + return keyInt((key >> 21) & 0x1FFFFF); + } + + /** + * Gets the third 21-bit integer value from a long key + * + * @param key to get from + * @return the third 21-bit integer value in the key + */ + public static int key3(long key) { + return keyInt(key & 0x1FFFFF); + } + + private static int keyInt(long key) { + return (int) (key - ((key & 0x100000) << 1)); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/IntPairHashed.java b/src/main/java/org/spout/api/util/hashing/IntPairHashed.java new file mode 100644 index 0000000..01230ad --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/IntPairHashed.java @@ -0,0 +1,60 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class IntPairHashed { + /** + * Creates a long key from 2 ints + * + * @param key1 an int value + * @param key2 an int value + * @return a long which is the concatenation of key1 and key2 + */ + public static long key(int key1, int key2) { + return (long) key1 << 32 | key2 & 0xFFFFFFFFL; + } + + /** + * Gets the first 32-bit integer value from an long key + * + * @param key to get from + * @return the first 32-bit integer value in the key + */ + public static int key1(long key) { + return (int) (key >> 32 & 0xFFFFFFFFL); + } + + /** + * Gets the second 32-bit integer value from an long key + * + * @param key to get from + * @return the second 32-bit integer value in the key + */ + public static int key2(long key) { + return (int) (key & 0xFFFFFFFFL); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/NibblePairHashed.java b/src/main/java/org/spout/api/util/hashing/NibblePairHashed.java new file mode 100644 index 0000000..84b4e84 --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/NibblePairHashed.java @@ -0,0 +1,85 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class NibblePairHashed { + /** + * Packs the first 4 most significant bits of each byte into a byte + * + * @param key1 a byte value + * @param key2 a byte value + * @return The first 4 most significant bits of each byte packed into a byte + */ + public static byte key(int key1, int key2) { + return (byte) (((key1 & 0xF) << 4) | (key2 & 0xF)); + } + + /** + * Packs the first 4 most significant bits of each byte into an int with the top 16 bits as zero. + * + * @param key1 a byte value + * @param key2 a byte value + * @return The first 4 most significant bits of each byte packed into a byte + */ + public static int intKey(int key1, int key2) { + return key(key1, key2) & 0xFF; + } + + /** + * Sets 4 most significant bits in the composite to the 4 least significant bits in the key + */ + public static byte setKey1(int composite, int key1) { + return (byte) (((key1 & 0xF) << 4) | (composite & 0xF)); + } + + /** + * Sets 4 least significant bits in the composite to the 4 least significant bits in the key + */ + public static byte setKey2(int composite, int key2) { + return (byte) ((composite & 0xF0) | (key2 & 0xF)); + } + + /** + * Returns the 4 most significant bits in the byte value. + * + * @param composite to separate + * @return the 4 most significant bits in a byte + */ + public static byte key1(int composite) { + return (byte) ((composite >> 4) & 0xF); + } + + /** + * Returns the 4 least significant bits in the byte value. + * + * @param composite to separate + * @return the 4 least significant bits in a byte + */ + public static byte key2(int composite) { + return (byte) (composite & 0xF); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/NibbleQuadHashed.java b/src/main/java/org/spout/api/util/hashing/NibbleQuadHashed.java new file mode 100644 index 0000000..3a83a49 --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/NibbleQuadHashed.java @@ -0,0 +1,108 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; +/* + * This file is part of SpoutAPI. + * + * Copyright (c) 2011-2012, SpoutDev + * SpoutAPI is licensed under the SpoutDev License Version 1. + * + * SpoutAPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the SpoutDev License Version 1. + * + * SpoutAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the SpoutDev License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, + * including the MIT license. + */ + +public abstract class NibbleQuadHashed { + /** + * Packs the first 4 least significant bits of each byte into a short + * + * @param key1 a byte value + * @param key2 a byte value + * @param key3 a byte value + * @param key4 a byte value + * @return The first 4 most significant bits of each byte packed into a short + */ + public static short key(int key1, int key2, int key3, int key4) { + return (short) ((key1 & 0xF) << 12 | (key2 & 0xF) << 8 | (key3 & 0xF) << 4 | key4 & 0xF); + } + + /** + * Gets the first 4-bit integer value from a short key + * + * @param key to get from + * @return the first 4-bit integer value in the key + */ + public static byte key1(int key) { + return (byte) ((key >> 12) & 0xF); + } + + /** + * Gets the second 4-bit integer value from a short key + * + * @param key to get from + * @return the second 4-bit integer value in the key + */ + public static byte key2(int key) { + return (byte) ((key >> 8) & 0xF); + } + + /** + * Gets the third 4-bit integer value from a short key + * + * @param key to get from + * @return the third 4-bit integer value in the key + */ + public static byte key3(int key) { + return (byte) ((key >> 4) & 0xF); + } + + /** + * Gets the fourth 4-bit integer value from a short key + * + * @param key to get from + * @return the fourth 4-bit integer value in the key + */ + public static byte key4(int key) { + return (byte) (key & 0xF); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/ShortPairHashed.java b/src/main/java/org/spout/api/util/hashing/ShortPairHashed.java new file mode 100644 index 0000000..5858e4e --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/ShortPairHashed.java @@ -0,0 +1,60 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class ShortPairHashed { + /** + * Squashes 2 short values into 1 int, with the first value in the most significant bits and the second value in the least significant bits. + * + * @param key1 to squash + * @param key2 to squash + * @return squashed int + */ + public static int key(short key1, short key2) { + return key1 << 16 | key2 & 0xFFFF; + } + + /** + * Returns the 16 most significant bits (short) in the int value. + * + * @param composite to separate + * @return the 16 most significant bits in an int + */ + public static short key1(int composite) { + return (short) ((composite >> 16) & 0xFFFF); + } + + /** + * Returns the 16 least significant bits (short) in the int value. + * + * @param composite to separate + * @return the 16 least significant bits in an int + */ + public static short key2(int composite) { + return (short) (composite & 0xFFFF); + } +} diff --git a/src/main/java/org/spout/api/util/hashing/SignedTenBitTripleHashed.java b/src/main/java/org/spout/api/util/hashing/SignedTenBitTripleHashed.java new file mode 100644 index 0000000..da873eb --- /dev/null +++ b/src/main/java/org/spout/api/util/hashing/SignedTenBitTripleHashed.java @@ -0,0 +1,107 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.hashing; + +public class SignedTenBitTripleHashed { + private final static int mask = 0xFFDFFBFF; + private final static int[] shiftMask = new int[16]; + + static { + for (int i = 0; i < 16; i++) { + int single = 0x3FF >> i; + shiftMask[i] = (single << 22) | (single << 11) | single; + } + } + + /** + * Packs the first 8 most significant bits of each byte into an int + * + * @param x an byte value + * @param y an byte value + * @param z an byte value + * @return The first 8 most significant bits of each byte packed into an int + */ + public static int key(int x, int y, int z) { + return (x & 0x3FF) << 22 | (z & 0x3FF) << 11 | y & 0x3FF; + } + + /** + * Gets the first signed 10-bit integer value from an int key + * + * @param key to get from + * @return the first 8-bit integer value in the key + */ + public static int key1(int key) { + return key >> 22; + } + + /** + * Gets the second signed 10-bit integer value from an int key + * + * @param key to get from + * @return the second 8-bit integer value in the key + */ + public static int key2(int key) { + return (key << 22) >> 22; + } + + /** + * Gets the third signed 10-bit integer value from an int key + * + * @param key to get from + * @return the third 8-bit integer value in the key + */ + public static int key3(int key) { + return (key << 11) >> 22; + } + + /** + * Adds the given offset to the packed key + * + * @param key the base key + * @param x the x offset + * @param y the y offset + * @param z the z offset + * @return the new key + */ + public static int add(int key, int x, int y, int z) { + int offset = key(x, y, z); + return (key + offset) & mask; + } + + /** + * Shifts the given key to the right.

This method only works for keys if all 3 sub-keys are positive + * + * @param key the key + * @param shift the right shift + */ + public static int positiveRightShift(int key, int shift) { + int single = 0x3FF >> shift; + int shiftMask = (single << 22) | (single << 11) | single; + return shiftMask & (key >> shift); + } +} diff --git a/src/main/java/org/spout/api/util/sanitation/SafeCast.java b/src/main/java/org/spout/api/util/sanitation/SafeCast.java new file mode 100644 index 0000000..7dfeac7 --- /dev/null +++ b/src/main/java/org/spout/api/util/sanitation/SafeCast.java @@ -0,0 +1,105 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.sanitation; + +public class SafeCast { + public static long toLong(Object o, long def) { + if (!(o instanceof Long)) { + return def; + } + + return (Long) o; + } + + public static int toInt(Object o, int def) { + if (!(o instanceof Integer)) { + return def; + } + + return (Integer) o; + } + + public static byte toByte(Object o, byte def) { + if (!(o instanceof Byte)) { + return def; + } + + return (Byte) o; + } + + public static float toFloat(Object o, float def) { + if (!(o instanceof Float)) { + return def; + } + + return (Float) o; + } + + public static byte[] toByteArray(Object o, byte[] def) { + if (!(o instanceof byte[])) { + return def; + } + + return (byte[]) o; + } + + public static short[] toShortArray(Object o, short[] def) { + if (!(o instanceof short[])) { + return def; + } + + return (short[]) o; + } + + public static int[] toIntArray(Object o, int[] def) { + if (!(o instanceof int[])) { + return def; + } + + return (int[]) o; + } + + public static String toString(Object o, String def) { + if (!(o instanceof String)) { + return def; + } + + return (String) o; + } + + public static T toGeneric(Object o, U def, Class clazz) { + if (o == null) { + return def; + } + + try { + return clazz.cast(o); + } catch (ClassCastException e) { + return def; + } + } +} diff --git a/src/main/java/org/spout/api/util/thread/annotation/DelayedWrite.java b/src/main/java/org/spout/api/util/thread/annotation/DelayedWrite.java new file mode 100644 index 0000000..6706d01 --- /dev/null +++ b/src/main/java/org/spout/api/util/thread/annotation/DelayedWrite.java @@ -0,0 +1,41 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.thread.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Retention (value = RetentionPolicy.RUNTIME) +public @interface DelayedWrite { + public String author() default "SpoutDev"; + + public String version() default "1.0"; + + public String shortDescription() default "Indicates that this method submits an update for writing. The changes will be incorporated into the next snapshot. Snapshots are taken at the end of every tick."; +} diff --git a/src/main/java/org/spout/api/util/thread/annotation/LiveRead.java b/src/main/java/org/spout/api/util/thread/annotation/LiveRead.java new file mode 100644 index 0000000..5459626 --- /dev/null +++ b/src/main/java/org/spout/api/util/thread/annotation/LiveRead.java @@ -0,0 +1,35 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.thread.annotation; + +public @interface LiveRead { + public String author() default "SpoutDev"; + + public String version() default "1.0"; + + public String shortDescription() default "Indicates that this method reads the current value of an object. " + "This may have adverse performance implications as it requires thread synchronisation with the managing thread. " + "All previously submitted DelayedWrites should complete before this read returns"; +} diff --git a/src/main/java/org/spout/api/util/thread/annotation/LiveWrite.java b/src/main/java/org/spout/api/util/thread/annotation/LiveWrite.java new file mode 100644 index 0000000..2f6683f --- /dev/null +++ b/src/main/java/org/spout/api/util/thread/annotation/LiveWrite.java @@ -0,0 +1,35 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.thread.annotation; + +public @interface LiveWrite { + public String author() default "SpoutDev"; + + public String version() default "1.0"; + + public String shortDescription() default "Indicates that this method updates the value of an object immediately. " + "This may have adverse performance implications as it requires thread synchronisation with the managing thread."; +} diff --git a/src/main/java/org/spout/api/util/thread/annotation/SnapshotRead.java b/src/main/java/org/spout/api/util/thread/annotation/SnapshotRead.java new file mode 100644 index 0000000..c045feb --- /dev/null +++ b/src/main/java/org/spout/api/util/thread/annotation/SnapshotRead.java @@ -0,0 +1,41 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.thread.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Retention (value = RetentionPolicy.RUNTIME) +public @interface SnapshotRead { + public String author() default "SpoutDev"; + + public String version() default "1.0"; + + public String shortDescription() default "Indicates that this method reads a snapshot value. Snapshots are taken at the end of every tick"; +} diff --git a/src/main/java/org/spout/api/util/thread/annotation/Threadsafe.java b/src/main/java/org/spout/api/util/thread/annotation/Threadsafe.java new file mode 100644 index 0000000..1d6e1af --- /dev/null +++ b/src/main/java/org/spout/api/util/thread/annotation/Threadsafe.java @@ -0,0 +1,35 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.api.util.thread.annotation; + +public @interface Threadsafe { + public String author() default "SpoutDev"; + + public String version() default "1.0"; + + public String shortDescription() default "Indicates that the method is inherently thread-safe."; +} diff --git a/src/main/java/org/spout/engine/SpoutApplication.java b/src/main/java/org/spout/engine/SpoutApplication.java new file mode 100644 index 0000000..6446f1b --- /dev/null +++ b/src/main/java/org/spout/engine/SpoutApplication.java @@ -0,0 +1,93 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine; + +import java.io.File; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; + +import org.spout.api.Platform; +import org.spout.api.Spout; +import org.spout.engine.util.argument.PlatformConverter; + +/** + * A main class for launching various platforms + */ +public class SpoutApplication { + @Parameter (names = {"--platform", "-platform", "--p", "-p"}, converter = PlatformConverter.class) + public Platform platform = Platform.SINGLEPLAYER; + @Parameter (names = {"--debug", "-debug", "--d", "-d"}, description = "Debug Mode") + public boolean debug = false; + @Parameter (names = {"--ccoverride"}, description = "Override ARB_CREATE_CONTEXT for the client") + public boolean ccoverride = false; + @Parameter (names = {"--path"}, description = "Override path for the client") + public String path = null; + @Parameter (names = {"--protocol"}, description = "Protocol to connect with") + public String protocol = null; + @Parameter (names = {"--server"}, description = "Server to connect to") + public String server = null; + @Parameter (names = {"--port"}, description = "Port to connect to") + public int port = -1; + @Parameter (names = {"--user"}, description = "User to connect as") + public String user = null; + + public static void main(String[] args) { + try { + SpoutApplication main = new SpoutApplication(); + JCommander commands = new JCommander(main); + commands.parse(args); + if (main.path != null) { + File dir = new File(main.path); + if (!dir.exists()) { + dir.mkdirs(); + } + } + + SpoutEngine engine; + switch (main.platform) { + case CLIENT: + engine = new SpoutClient(main); + break; + case SERVER: + engine = new SpoutServer(main); + break; + case SINGLEPLAYER: + engine = new SpoutSingleplayer(main); + break; + default: + throw new IllegalArgumentException("Unknown platform: " + main.platform); + } + + Spout.setEngine(engine); + engine.start(); + } catch (Throwable t) { + t.printStackTrace(); + Runtime.getRuntime().halt(1); + } + } +} diff --git a/src/main/java/org/spout/engine/SpoutClient.java b/src/main/java/org/spout/engine/SpoutClient.java new file mode 100644 index 0000000..f9fa3b0 --- /dev/null +++ b/src/main/java/org/spout/engine/SpoutClient.java @@ -0,0 +1,49 @@ +package org.spout.engine; + +import java.util.concurrent.atomic.AtomicReference; +import org.spout.api.Client; +import org.spout.api.Platform; +import org.spout.api.entity.Player; +import org.spout.api.geo.World; +import org.spout.api.geo.WorldManager; +import org.spout.api.render.Renderer; +import org.spout.engine.entity.SpoutPlayer; +import org.spout.engine.geo.SpoutWorld; +import org.spout.engine.geo.SpoutWorldManager; + +public class SpoutClient extends SpoutEngine implements Client { + private final AtomicReference player = new AtomicReference<>(); + private final AtomicReference activeWorld = new AtomicReference<>(); + private final SpoutWorldManager worldManager; + + public SpoutClient(SpoutApplication args) { + super(args); + this.worldManager = new SpoutWorldManager(this); + } + + @Override + public Platform getPlatform() { + return Platform.CLIENT; + } + + @Override + public WorldManager getWorldManager() { + return worldManager; + } + + @Override + public Player getPlayer() { + return player.get(); + } + + @Override + public World getWorld() { + return activeWorld.get(); + } + + @Override + public Renderer getRenderer() { + return getScheduler().getRenderThread().getRenderer(); + } + +} diff --git a/src/main/java/org/spout/engine/SpoutEngine.java b/src/main/java/org/spout/engine/SpoutEngine.java new file mode 100644 index 0000000..e7fe700 --- /dev/null +++ b/src/main/java/org/spout/engine/SpoutEngine.java @@ -0,0 +1,84 @@ +package org.spout.engine; + +import org.spout.engine.filesystem.SpoutFileSystem; +import com.flowpowered.events.EventManager; +import com.flowpowered.events.SimpleEventManager; +import com.flowpowered.filesystem.FileSystem; + +import org.spout.api.Engine; +import org.spout.api.material.MaterialRegistry; +import org.spout.api.scheduler.TaskPriority; +import org.spout.api.util.SyncedStringMap; +import org.spout.engine.scheduler.SpoutScheduler; +import org.spout.engine.util.thread.snapshotable.SnapshotManager; + +public abstract class SpoutEngine implements Engine { + private final SpoutApplication args; + private final EventManager eventManager; + private final FileSystem fileSystem; + + private SpoutScheduler scheduler; + protected final SnapshotManager snapshotManager = new SnapshotManager(); + private SyncedStringMap itemMap; + + + public SpoutEngine(SpoutApplication args) { + this.args = args; + this.eventManager = new SimpleEventManager(); + this.fileSystem = new SpoutFileSystem(); + } + + @Override + public String getVersion() { + return getClass().getPackage().getImplementationVersion(); + } + + + public void start() { + itemMap = MaterialRegistry.setupRegistry(); + scheduler = new SpoutScheduler(this); + scheduler.startMainThread(); + System.out.println("Engine started."); + } + + @Override + public boolean stop() { + scheduler.stop(); + System.out.println("Engine stopped"); + return true; + } + + @Override + public boolean stop(String reason) { + return stop(); + } + + @Override + public boolean debugMode() { + return args.debug; + } + + @Override + public SpoutScheduler getScheduler() { + return scheduler; + } + + @Override + public FileSystem getFileSystem() { + return fileSystem; + } + + @Override + public EventManager getEventManager() { + return eventManager; + } + + @Override + public String getName() { + return "Spout Engine"; + } + + public SnapshotManager getSnapshotManager() { + return snapshotManager; + } +} diff --git a/src/main/java/org/spout/engine/SpoutServer.java b/src/main/java/org/spout/engine/SpoutServer.java new file mode 100644 index 0000000..09fa1a4 --- /dev/null +++ b/src/main/java/org/spout/engine/SpoutServer.java @@ -0,0 +1,79 @@ +package org.spout.engine; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import com.flowpowered.commons.StringUtil; + +import org.spout.api.Platform; +import org.spout.api.Server; +import org.spout.api.entity.Player; +import org.spout.api.geo.ServerWorldManager; +import org.spout.engine.entity.SpoutPlayer; +import org.spout.engine.geo.SpoutServerWorldManager; +import org.spout.engine.util.thread.snapshotable.SnapshotableLinkedHashMap; + +public class SpoutServer extends SpoutEngine implements Server { + private final SnapshotableLinkedHashMap players; + private final SpoutServerWorldManager worldManager; + + public SpoutServer(SpoutApplication args) { + super(args); + players = new SnapshotableLinkedHashMap<>(snapshotManager); + worldManager = new SpoutServerWorldManager(this); + } + + @Override + public Platform getPlatform() { + return Platform.SERVER; + } + + @Override + public Collection getOnlinePlayers() { + Map playerList = players.get(); + ArrayList onlinePlayers = new ArrayList<>(playerList.size()); + for (SpoutPlayer player : playerList.values()) { + if (player.isOnline()) { + onlinePlayers.add(player); + } + } + return onlinePlayers; + } + + @Override + public int getMaxPlayers() { + // TODO: config + return 5; + } + + @Override + public void broadcastMessage(String message) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void broadcastMessage(String permission, String message) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Player getPlayer(String name, boolean exact) { + name = name.toLowerCase(); + if (exact) { + for (Player player : players.getValues()) { + if (player.getName().equalsIgnoreCase(name)) { + return player; + } + } + return null; + } else { + return StringUtil.getShortest(StringUtil.matchName(players.getValues(), name)); + } + } + + @Override + public ServerWorldManager getWorldManager() { + return worldManager; + } +} diff --git a/src/main/java/org/spout/engine/SpoutSingleplayer.java b/src/main/java/org/spout/engine/SpoutSingleplayer.java new file mode 100644 index 0000000..85d46fb --- /dev/null +++ b/src/main/java/org/spout/engine/SpoutSingleplayer.java @@ -0,0 +1,66 @@ +package org.spout.engine; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.spout.api.Platform; +import org.spout.api.Singleplayer; +import org.spout.api.entity.Player; +import org.spout.api.generator.FlatWorldGenerator; +import org.spout.api.geo.World; +import org.spout.api.material.BlockMaterial; +import org.spout.engine.entity.SpoutPlayer; +import org.spout.engine.geo.SpoutWorld; +import org.spout.engine.render.DeployNatives; +import org.spout.engine.render.SpoutRenderer; + +public class SpoutSingleplayer extends SpoutServer implements Singleplayer { + private final AtomicReference player = new AtomicReference<>(); + private final AtomicReference activeWorld = new AtomicReference<>(); + + public SpoutSingleplayer(SpoutApplication args) { + super(args); + } + + + @Override + public void start() { + super.start(); + try { + DeployNatives.deploy(); + } catch (Exception ex) { + Logger.getLogger(SpoutSingleplayer.class.getName()).log(Level.SEVERE, null, ex); + return; + } + getScheduler().startRenderThread(); + getWorldManager().loadWorld("fallback", new FlatWorldGenerator(BlockMaterial.SOLID_BLUE)); + } + + @Override + public boolean stop() { + return super.stop(); + + } + + @Override + public Platform getPlatform() { + return Platform.SINGLEPLAYER; + } + + @Override + public Player getPlayer() { + return player.get(); + } + + @Override + public World getWorld() { + return activeWorld.get(); + } + + @Override + public SpoutRenderer getRenderer() { + return getScheduler().getRenderThread().getRenderer(); + } + +} diff --git a/src/main/java/org/spout/engine/entity/SpoutEntity.java b/src/main/java/org/spout/engine/entity/SpoutEntity.java new file mode 100644 index 0000000..e42d567 --- /dev/null +++ b/src/main/java/org/spout/engine/entity/SpoutEntity.java @@ -0,0 +1,132 @@ +package org.spout.engine.entity; + +import java.util.Collection; +import java.util.UUID; + +import com.flowpowered.commons.datatable.ManagedMap; +import org.spout.api.Engine; +import org.spout.api.component.Component; +import org.spout.api.entity.Entity; +import org.spout.api.entity.EntitySnapshot; +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.geo.cuboid.Region; + +public class SpoutEntity implements Entity { + + @Override + public int getId() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public UUID getUID() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Engine getEngine() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isRemoved() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setSavable(boolean savable) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isSavable() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getChunk() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegion() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public EntitySnapshot snapshot() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ManagedMap getData() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void onTick(float dt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean canTick() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void tick(float dt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public World getWorld() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public T add(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public T get(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Collection getAll(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Collection getAllOfType(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public T getExact(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public T getType(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public T detach(Class type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/main/java/org/spout/engine/entity/SpoutPlayer.java b/src/main/java/org/spout/engine/entity/SpoutPlayer.java new file mode 100644 index 0000000..4055dad --- /dev/null +++ b/src/main/java/org/spout/engine/entity/SpoutPlayer.java @@ -0,0 +1,144 @@ +package org.spout.engine.entity; + +import java.util.List; +import java.util.Set; + +import com.flowpowered.permissions.PermissionDomain; +import org.spout.api.entity.Entity; +import org.spout.api.entity.Player; +import org.spout.flow.commands.CommandException; +import org.spout.flow.commands.CommandSender; + +public class SpoutPlayer implements Player { + + @Override + public String getName() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public String getDisplayName() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setDisplayName(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isOnline() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean hasJoinedBefore() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void kick() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void kick(String reason) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void ban() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void ban(boolean kick) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void ban(boolean kick, String reason) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean save() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setVisible(Entity entity, boolean visible) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getInvisibleEntities() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isInvisible(Entity entity) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void sendCommand(String command, String... args) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity getEntity() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void sendMessage(String message) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void sendMessage(CommandSender from, String message) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void sendMessageRaw(String message, String type) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void processCommand(String commandLine) throws CommandException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean hasPermission(String permission) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean hasPermission(String permission, PermissionDomain domain) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isInGroup(String group) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isInGroup(String group, PermissionDomain domain) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Set getGroups() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Set getGroups(PermissionDomain domain) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/main/java/org/spout/engine/filesystem/SpoutFileSystem.java b/src/main/java/org/spout/engine/filesystem/SpoutFileSystem.java new file mode 100644 index 0000000..3d6f56b --- /dev/null +++ b/src/main/java/org/spout/engine/filesystem/SpoutFileSystem.java @@ -0,0 +1,8 @@ +package org.spout.engine.filesystem; + +import java.io.File; +import com.flowpowered.filesystem.SimpleFileSystem; + +public class SpoutFileSystem extends SimpleFileSystem { + public static final File WORLDS_DIRECTORY = new File("worlds"); +} diff --git a/src/main/java/org/spout/engine/filesystem/WorldFiles.java b/src/main/java/org/spout/engine/filesystem/WorldFiles.java new file mode 100644 index 0000000..49c232a --- /dev/null +++ b/src/main/java/org/spout/engine/filesystem/WorldFiles.java @@ -0,0 +1,203 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.filesystem; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.UUID; + +import com.flowpowered.commons.StringToUniqueIntegerMap; +import com.flowpowered.commons.datatable.SerializableMap; + +import org.spout.api.Server; +import org.spout.api.Spout; +import org.spout.api.generator.WorldGenerator; +import org.spout.api.geo.discrete.Transform; +import org.spout.api.io.nbt.TransformTag; +import org.spout.api.io.nbt.UUIDTag; +import org.spout.api.store.BinaryFileStore; +import org.spout.api.util.sanitation.SafeCast; +import org.spout.engine.SpoutEngine; +import org.spout.engine.geo.SpoutServerWorld; +import org.spout.nbt.ByteArrayTag; +import org.spout.nbt.ByteTag; +import org.spout.nbt.CompoundMap; +import org.spout.nbt.CompoundTag; +import org.spout.nbt.LongTag; +import org.spout.nbt.StringTag; +import org.spout.nbt.stream.NBTInputStream; +import org.spout.nbt.stream.NBTOutputStream; +import org.spout.nbt.util.NBTMapper; + +public class WorldFiles { + public static final byte WORLD_VERSION = 1; + + public static SpoutServerWorld loadWorld(E engine, WorldGenerator generator, String worldName) { + File worldDir = new File(SpoutFileSystem.WORLDS_DIRECTORY, worldName); + worldDir.mkdirs(); + File worldFile = new File(worldDir, "world.dat"); + + SpoutServerWorld world = null; + + File itemMapFile = new File(worldDir, "materials.dat"); + BinaryFileStore itemStore = new BinaryFileStore(itemMapFile); + if (itemMapFile.exists()) { + itemStore.load(); + } + + //StringToUniqueIntegerMap itemMap = new StringToUniqueIntegerMap(engine.getEngineItemMap(), itemStore, 0, Short.MAX_VALUE, worldName + "ItemMap"); + StringToUniqueIntegerMap itemMap = null; + + /* + File lightingMapFile = new File(worldDir, "lighting.dat"); + BinaryFileStore lightingStore = new BinaryFileStore(lightingMapFile); + if (lightingMapFile.exists()) { + lightingStore.load(); + } + StringToUniqueIntegerMap lightingMap = new StringToUniqueIntegerMap(engine.getEngineLightingMap(), lightingStore, 0, Short.MAX_VALUE, worldName + "LightingMap"); + */ + + try { + InputStream is = new FileInputStream(worldFile); + NBTInputStream ns = new NBTInputStream(is, false); + CompoundMap map; + try { + CompoundTag tag = (CompoundTag) ns.readTag(); + map = tag.getValue(); + } finally { + try { + ns.close(); + } catch (IOException e) { + Spout.info("Cannot close world file"); + } + } + Spout.info("Loading world [{0}]", worldName); + world = loadWorldImpl(worldName, map, generator, itemMap); + } catch (FileNotFoundException ioe) { + Spout.info("Creating new world named [{0}]", worldName); + + world = new SpoutServerWorld(worldName, generator); + world.save(); + } catch (IOException ioe) { + Spout.severe("Error reading file for world " + worldName, ioe); + } + return world; + } + + private static SpoutServerWorld loadWorldImpl(String name, CompoundMap map, WorldGenerator fallbackGenerator, StringToUniqueIntegerMap itemMap) { + byte version = SafeCast.toByte(NBTMapper.toTagValue(map.get("version")), (byte) -1); + if (version > WORLD_VERSION) { + Spout.severe("World version " + version + " exceeds maximum allowed value of " + WORLD_VERSION); + return null; + } else if (version < WORLD_VERSION) { + Spout.severe("Outdated World version " + version); + return null; + } + + String generatorName = SafeCast.toString(NBTMapper.toTagValue(map.get("generator")), null); + Long seed = SafeCast.toLong(NBTMapper.toTagValue(map.get("seed")), 0); + byte[] extraData = SafeCast.toByteArray(NBTMapper.toTagValue(map.get("extra_data")), null); + Long age = SafeCast.toLong(NBTMapper.toTagValue(map.get("age")), 0); + UUID uuid = UUIDTag.getValue(map.get("uuid")); + + WorldGenerator generator = findGenerator(generatorName, fallbackGenerator); + + SpoutServerWorld world = new SpoutServerWorld(name, uuid, age, generator, seed); + + Transform t = TransformTag.getValue(world, map.get("spawn_position")); + + world.setSpawnPoint(t); + + SerializableMap dataMap = world.getData(); + dataMap.clear(); + try { + dataMap.deserialize(extraData); + } catch (IOException e) { + Spout.severe("Could not deserialize datatable for world: " + name, e); + } + + return world; + } + + private static WorldGenerator findGenerator(String wanted, WorldGenerator given) { + // TODO: lookup class name + if (!wanted.equals(given.getClass().getName())) { + Spout.severe("World was saved last with the generator: " + wanted + " but is being loaded with: " + given.getClass().getName() + " THIS MAY CAUSE WORLD CORRUPTION!"); + } + return given; + } + + public static void saveWorld(SpoutServerWorld world) { + + File worldDir = new File(SpoutFileSystem.WORLDS_DIRECTORY, world.getName()); + + worldDir.mkdirs(); + + File worldFile = new File(worldDir, "world.dat"); + + //world.getItemMap().save(); + + //world.getLightingMap().save(); + + CompoundMap map = saveWorldImpl(world); + + NBTOutputStream ns = null; + try { + OutputStream is = new FileOutputStream(worldFile); + ns = new NBTOutputStream(is, false); + ns.writeTag(new CompoundTag("world_" + world.getName(), map)); + } catch (IOException ioe) { + Spout.severe("Error writing file for world " + world.getName()); + } finally { + if (ns != null) { + try { + ns.close(); + } catch (IOException ignore) { + } + } + } + } + + private static CompoundMap saveWorldImpl(SpoutServerWorld world) { + CompoundMap map = new CompoundMap(); + + map.put(new ByteTag("version", WORLD_VERSION)); + map.put(new StringTag("generator", world.getGenerator().getName())); + map.put(new LongTag("seed", world.getSeed())); + map.put(new ByteArrayTag("extra_data", world.getData().serialize())); + map.put(new LongTag("age", world.getAge())); + map.put(new UUIDTag("uuid", world.getUID())); + map.put(new TransformTag("spawn_position", world.getSpawnPoint())); + + return map; + } +} diff --git a/src/main/java/org/spout/engine/geo/SpoutBlock.java b/src/main/java/org/spout/engine/geo/SpoutBlock.java new file mode 100644 index 0000000..32ea7cf --- /dev/null +++ b/src/main/java/org/spout/engine/geo/SpoutBlock.java @@ -0,0 +1,302 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.geo; + +import java.lang.ref.WeakReference; +import java.util.Collection; + +import com.flowpowered.commons.StringUtil; +import com.flowpowered.commons.datatable.ManagedMap; +import com.flowpowered.events.Cause; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import org.spout.api.Platform; +import org.spout.api.Spout; +import org.spout.api.component.BlockComponentOwner; +import org.spout.api.component.Component; +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.cuboid.reference.ChunkReference; +import org.spout.api.geo.discrete.Point; +import org.spout.api.material.BlockMaterial; +import org.spout.api.material.Material; +import org.spout.api.material.block.BlockFace; +import org.spout.math.vector.Vector3f; +import org.spout.math.vector.Vector3i; + +public class SpoutBlock implements Block { + private final int x, y, z; + private final WeakReference world; + private final ChunkReference chunk; + + public SpoutBlock(SpoutWorld world, int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + this.world = new WeakReference<>(world); + this.chunk = new ChunkReference(new Point(getWorld(), this.x, this.y, this.z)); + } + + @Override + public Point getPosition() { + return new Point(getWorld(), this.x + 0.5f, this.y + 0.5f, this.z + 0.5f); + } + + @Override + public SpoutChunk getChunk() { + return (SpoutChunk) this.chunk.refresh(LoadOption.LOAD_GEN); + } + + @Override + public SpoutWorld getWorld() { + SpoutWorld world = this.world.get(); + if (world == null) { + throw new IllegalStateException("The world has been unloaded!"); + } + return world; + } + + @Override + public int getX() { + return this.x; + } + + @Override + public int getY() { + return this.y; + } + + @Override + public int getZ() { + return this.z; + } + + @Override + public Block translate(BlockFace offset, int distance) { + return this.translate(offset.getOffset().mul(distance)); + } + + @Override + public Block translate(BlockFace offset) { + if (offset == null) { + return null; + } + return this.translate(offset.getOffset()); + } + + @Override + public Block translate(Vector3f offset) { + return this.translate((int) offset.getX(), (int) offset.getY(), (int) offset.getZ()); + } + + @Override + public Block translate(Vector3i offset) { + return this.translate(offset.getX(), offset.getY(), offset.getZ()); + } + + @Override + public Block translate(int dx, int dy, int dz) { + return new SpoutBlock(getWorld(), this.x + dx, this.y + dy, this.z + dz); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other != null && other instanceof Block) { + Block b = (Block) other; + return b.getWorld() == this.getWorld() && b.getX() == this.getX() && b.getY() == this.getY() && b.getZ() == this.getZ(); + } else { + return false; + } + } + + @Override + public int hashCode() { + return new HashCodeBuilder().append(getWorld()).append(getX()).append(getY()).append(getZ()).toHashCode(); + } + + @Override + public String toString() { + return StringUtil.toNamedString(this, this.world.get(), this.x, this.y, this.z); + } + + @Override + public boolean setMaterial(BlockMaterial material, int data, Cause cause) { + // TODO once stable, remove this + if (Spout.getPlatform() != Platform.SERVER) { + throw new UnsupportedOperationException("Temporary lockdown of setMaterial. Server only!"); + } + return this.getChunk().setBlockMaterial(x, y, z, material, (short) data, cause); + } + + @Override + public boolean setMaterial(BlockMaterial material, int data) { + return setMaterial(material, data, null); + } + + @Override + public SpoutBlock setData(BlockMaterial data) { + return this.setData(data.getData()); + } + + @Override + public SpoutBlock setData(int data) { + return setData(data, null); + } + + @Override + public SpoutBlock setData(int data, Cause cause) { + this.getChunk().setBlockData(this.x, this.y, this.z, (short) data, cause); + return this; + } + + @Override + public SpoutBlock addData(int data) { + this.getChunk().addBlockData(this.x, this.y, this.z, (short) data, null); + return this; + } + + @Override + public short getBlockData() { + return this.getChunk().getBlockData(this.x, this.y, this.z); + } + + @Override + public short setDataBits(int bits) { + return this.getChunk().setBlockDataBits(this.x, this.y, this.z, bits, null); + } + + @Override + public short setDataBits(int bits, boolean set) { + return this.getChunk().setBlockDataBits(this.x, this.y, this.z, bits, set, null); + } + + @Override + public short clearDataBits(int bits) { + return this.getChunk().clearBlockDataBits(this.x, this.y, this.z, bits, null); + } + + @Override + public int getDataField(int bits) { + return this.getChunk().getBlockDataField(this.x, this.y, this.z, bits); + } + + @Override + public boolean isDataBitSet(int bits) { + return this.getChunk().isBlockDataBitSet(this.x, this.y, this.z, bits); + } + + @Override + public int setDataField(int bits, int value) { + return this.getChunk().setBlockDataField(this.x, this.y, this.z, bits, value, null); + } + + @Override + public int addDataField(int bits, int value) { + return this.getChunk().addBlockDataField(this.x, this.y, this.z, bits, value, null); + } + + @Override + public SpoutRegion getRegion() { + return (SpoutRegion) this.getChunk().getRegion(); + } + + @Override + public BlockMaterial getMaterial() { + return this.getChunk().getBlockMaterial(this.x, this.y, this.z); + } + + @Override + public boolean setMaterial(BlockMaterial material) { + return this.setMaterial(material, material.getData()); + } + + @Override + public boolean setMaterial(BlockMaterial material, Cause cause) { + return this.setMaterial(material, material.getData(), cause); + } + + @Override + public boolean isMaterial(Material... materials) { + return getMaterial().isMaterial(materials); + } + + @Override + public T add(Class type) { + return getChunk().getBlockComponentOwner(x, y, z, true).add(type); + } + + @Override + public T detach(Class type) { + BlockComponentOwner owner = getChunk().getBlockComponentOwner(x, y, z, false); + if (owner != null) { + return owner.detach(type); + } + return null; + } + + @Override + public Collection values() { + return getChunk().getBlockComponentOwner(x, y, z, true).values(); + } + + @Override + public ManagedMap getData() { + BlockComponentOwner owner = getChunk().getBlockComponentOwner(x, y, z, false); + if (owner == null) { + throw new IllegalStateException("The datatable is only available on blocks who have a BlockComponentOwner (blocks with components added)"); + } + return owner.getData(); + } + + @Override + public T get(Class type) { + return getChunk().getBlockComponentOwner(x, y, z, true).get(type); + } + + @Override + public T getType(Class type) { + return getChunk().getBlockComponentOwner(x, y, z, true).getType(type); + } + + @Override + public T getExact(Class type) { + return getChunk().getBlockComponentOwner(x, y, z, true).getExact(type); + } + + @Override + public Collection getAll(Class type) { + return getChunk().getBlockComponentOwner(x, y, z, true).getAll(type); + } + + @Override + public Collection getAllOfType(Class type) { + return getChunk().getBlockComponentOwner(x, y, z, true).getAllOfType(type); + } +} diff --git a/src/main/java/org/spout/engine/geo/SpoutChunk.java b/src/main/java/org/spout/engine/geo/SpoutChunk.java new file mode 100644 index 0000000..c74a945 --- /dev/null +++ b/src/main/java/org/spout/engine/geo/SpoutChunk.java @@ -0,0 +1,292 @@ +package org.spout.engine.geo; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.Future; + +import com.flowpowered.commons.datatable.ManagedHashMap; +import com.flowpowered.events.Cause; +import gnu.trove.map.hash.TShortObjectHashMap; +import org.spout.api.component.BlockComponentOwner; +import org.spout.api.entity.Entity; +import org.spout.api.entity.Player; +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.cuboid.BlockComponentContainer; +import org.spout.api.geo.cuboid.BlockContainer; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.geo.cuboid.ChunkSnapshot; +import org.spout.api.geo.cuboid.Region; +import org.spout.api.material.BlockMaterial; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; +import org.spout.api.util.hashing.NibbleQuadHashed; +import org.spout.math.vector.Vector3f; + +public class SpoutChunk extends Chunk { + + /** + * Data map and Datatable associated with it + */ + protected final ManagedHashMap dataMap; + /** + * Not thread safe, synchronize on access + */ + private final TShortObjectHashMap blockComponents = new TShortObjectHashMap<>(); + + public SpoutChunk(World world, float x, float y, float z) { + super(world, x, y, z); + this.dataMap = new ManagedHashMap(); + } + + @Override + public void unload(boolean save) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void save() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ChunkSnapshot getSnapshot() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public ChunkSnapshot getSnapshot(ChunkSnapshot.SnapshotType type, ChunkSnapshot.EntityType entities, ChunkSnapshot.ExtraData data) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void fillBlockContainer(BlockContainer container) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void fillBlockComponentContainer(BlockComponentContainer container) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Future getFutureSnapshot() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Future getFutureSnapshot(ChunkSnapshot.SnapshotType type, ChunkSnapshot.EntityType entities, ChunkSnapshot.ExtraData data) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean refreshObserver(Entity player) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean removeObserver(Entity player) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegion() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isLoaded() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean populate() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean populate(boolean force) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void populate(boolean sync, boolean observe) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void populate(boolean sync, boolean observe, boolean priority) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isPopulated() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getEntities() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getLiveEntities() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getNumObservers() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Set getObservingPlayers() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Set getObservers() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getGenerationIndex() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean setBlockData(int x, int y, int z, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean addBlockData(int x, int y, int z, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean setBlockMaterial(int x, int y, int z, BlockMaterial material, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean compareAndSetData(int x, int y, int z, int expect, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short setBlockDataBits(int x, int y, int z, int bits, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short setBlockDataBits(int x, int y, int z, int bits, boolean set, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short clearBlockDataBits(int x, int y, int z, int bits, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getBlockDataField(int x, int y, int z, int bits) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isBlockDataBitSet(int x, int y, int z, int bits) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int setBlockDataField(int x, int y, int z, int bits, int value, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int addBlockDataField(int x, int y, int z, int bits, int value, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Block getBlock(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Block getBlock(float x, float y, float z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Block getBlock(Vector3f position) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean commitCuboid(CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setCuboid(CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setCuboid(int x, int y, int z, CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(boolean backBuffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz, boolean backBuffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void getCuboid(int bx, int by, int bz, CuboidBlockMaterialBuffer buffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void getCuboid(CuboidBlockMaterialBuffer buffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public BlockMaterial getBlockMaterial(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getBlockFullState(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short getBlockData(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public BlockComponentOwner getBlockComponentOwner(int x, int y, int z, boolean create) { + synchronized (blockComponents) { + short packed = NibbleQuadHashed.key(x, y, z, 0); + BlockComponentOwner value = blockComponents.get(packed); + if (value == null && create) { + value = new BlockComponentOwner(dataMap, NibbleQuadHashed.key1(packed), NibbleQuadHashed.key2(packed), NibbleQuadHashed.key3(packed), getWorld()); + blockComponents.put(packed, value); + } + return value; + } + } +} diff --git a/src/main/java/org/spout/engine/geo/SpoutRegion.java b/src/main/java/org/spout/engine/geo/SpoutRegion.java new file mode 100644 index 0000000..a987e23 --- /dev/null +++ b/src/main/java/org/spout/engine/geo/SpoutRegion.java @@ -0,0 +1,255 @@ +package org.spout.engine.geo; + +import java.util.List; + +import com.flowpowered.events.Cause; +import org.spout.api.entity.Entity; +import org.spout.api.entity.Player; +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.geo.cuboid.Region; +import org.spout.api.material.BlockMaterial; +import org.spout.api.material.block.BlockFace; +import org.spout.api.scheduler.TaskManager; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; +import org.spout.math.vector.Vector3f; + +public class SpoutRegion extends Region { + + public SpoutRegion(World world, float x, float y, float z) { + super(world, x, y, z); + } + + @Override + public void save() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void unload(boolean save) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getAll() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity getEntity(int id) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getPlayers() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TaskManager getTaskManager() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isLoaded() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getChunk(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getChunkFromBlock(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getChunkFromBlock(Vector3f position, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean hasChunk(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean hasChunkAtBlock(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void saveChunk(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void unloadChunk(int x, int y, int z, boolean save) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getNumLoadedChunks() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean setBlockData(int x, int y, int z, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean addBlockData(int x, int y, int z, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean setBlockMaterial(int x, int y, int z, BlockMaterial material, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean compareAndSetData(int x, int y, int z, int expect, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short setBlockDataBits(int x, int y, int z, int bits, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short setBlockDataBits(int x, int y, int z, int bits, boolean set, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short clearBlockDataBits(int x, int y, int z, int bits, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getBlockDataField(int x, int y, int z, int bits) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isBlockDataBitSet(int x, int y, int z, int bits) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int setBlockDataField(int x, int y, int z, int bits, int value, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int addBlockDataField(int x, int y, int z, int bits, int value, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Block getBlock(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Block getBlock(float x, float y, float z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Block getBlock(Vector3f position) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean commitCuboid(CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setCuboid(CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setCuboid(int x, int y, int z, CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(boolean backBuffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz, boolean backBuffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void getCuboid(int bx, int by, int bz, CuboidBlockMaterialBuffer buffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void getCuboid(CuboidBlockMaterialBuffer buffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public BlockMaterial getBlockMaterial(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getBlockFullState(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short getBlockData(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getLocalRegion(BlockFace face, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getLocalRegion(int dx, int dy, int dz, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getLocalChunk(Chunk c, BlockFace face, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getLocalChunk(Chunk c, int ox, int oy, int oz, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getLocalChunk(int x, int y, int z, int ox, int oy, int oz, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getLocalChunk(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/main/java/org/spout/engine/geo/SpoutServerWorld.java b/src/main/java/org/spout/engine/geo/SpoutServerWorld.java new file mode 100644 index 0000000..802fc98 --- /dev/null +++ b/src/main/java/org/spout/engine/geo/SpoutServerWorld.java @@ -0,0 +1,78 @@ +package org.spout.engine.geo; + +import java.io.File; +import java.util.List; +import java.util.Random; +import java.util.UUID; + +import org.spout.api.generator.WorldGenerator; +import org.spout.api.geo.ServerWorld; +import org.spout.api.geo.discrete.Transform; +import org.spout.engine.filesystem.WorldFiles; +import org.spout.math.vector.Vector3f; + +public class SpoutServerWorld extends SpoutWorld implements ServerWorld { + private final WorldGenerator generator; + private final long seed; + /** + * The spawn position. + */ + private final Transform spawnLocation = new Transform(); + + public SpoutServerWorld(String name, UUID uid, long age, WorldGenerator generator, long seed) { + super(name, uid, age); + this.generator = generator; + this.seed = seed; + } + + public SpoutServerWorld(String name, WorldGenerator generator) { + super(name); + this.generator = generator; + this.seed = new Random().nextLong(); + } + + @Override + public WorldGenerator getGenerator() { + return generator; + } + + @Override + public long getSeed() { + return seed; + } + + @Override + public Transform getSpawnPoint() { + return spawnLocation; + } + + @Override + public void setSpawnPoint(Transform transform) { + spawnLocation.set(transform); + } + + @Override + public void unload(boolean save) { + } + + @Override + public void save() { + WorldFiles.saveWorld(this); + } + + @Override + public File getDirectory() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void queueChunksForGeneration(List chunks) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void queueChunkForGeneration(Vector3f chunk) { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/src/main/java/org/spout/engine/geo/SpoutServerWorldManager.java b/src/main/java/org/spout/engine/geo/SpoutServerWorldManager.java new file mode 100644 index 0000000..fb3ce04 --- /dev/null +++ b/src/main/java/org/spout/engine/geo/SpoutServerWorldManager.java @@ -0,0 +1,121 @@ +package org.spout.engine.geo; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.flowpowered.commons.StringUtil; + +import org.apache.commons.io.filefilter.DirectoryFileFilter; +import org.spout.api.Server; +import org.spout.api.generator.EmptyWorldGenerator; +import org.spout.api.generator.WorldGenerator; +import org.spout.api.geo.ServerWorld; +import org.spout.api.geo.ServerWorldManager; +import org.spout.api.geo.World; +import org.spout.engine.SpoutEngine; +import org.spout.engine.SpoutServer; +import org.spout.engine.filesystem.SpoutFileSystem; +import org.spout.engine.filesystem.WorldFiles; + + +public class SpoutServerWorldManager extends SpoutWorldManager implements ServerWorldManager { + private final WorldGenerator defaultGenerator = new EmptyWorldGenerator(); + + public SpoutServerWorldManager(E engine) { + super(engine); + } + + @Override + public Collection matchWorldFolder(String worldName) { + return StringUtil.matchFile(getWorldFolders(), worldName); + } + + @Override + public World loadWorld(String name, WorldGenerator generator) { + if (loadedWorlds.get().containsKey((name))) { + return loadedWorlds.get().get(name); + } + if (loadedWorlds.getLive().containsKey(name)) { + return loadedWorlds.getLive().get(name); + } + + if (generator == null) { + generator = defaultGenerator; + } + + SpoutServerWorld world = WorldFiles.loadWorld((SpoutServer) engine, generator, name); + + World oldWorld = loadedWorlds.putIfAbsent(name, world); + + if (oldWorld != null) { + return oldWorld; + } + + if (!engine.getScheduler().addAsyncManager(world)) { + throw new IllegalStateException("Unable to add world to the scheduler"); + } + //getEventManager().callDelayedEvent(new WorldLoadEvent(world)); + return world; + } + + @Override + public void save(boolean worlds, boolean players) { + // TODO: Auto-generated method stub + } + + @Override + public List getWorldFolders() { + File[] folders = this.getWorldFolder().listFiles((FilenameFilter) DirectoryFileFilter.INSTANCE); + if (folders == null || folders.length == 0) { + return new ArrayList<>(); + } + List worlds = new ArrayList<>(folders.length); + // Are they really world folders? + for (File world : folders) { + if (new File(world, "world.dat").exists()) { + worlds.add(world); + } + } + return worlds; + } + + @Override + public File getWorldFolder() { + return SpoutFileSystem.WORLDS_DIRECTORY; + } + + @Override + public WorldGenerator getDefaultGenerator() { + return defaultGenerator; + } + + @Override + public boolean unloadWorld(String name, boolean save) { + return unloadWorld((ServerWorld) loadedWorlds.getLive().get(name), save); + } + + @Override + public boolean unloadWorld(ServerWorld world, boolean save) { + if (world == null) { + return false; + } + + SpoutServerWorld w = (SpoutServerWorld) world; + boolean success = loadedWorlds.remove(world.getName(), w); + if (success) { + if (save) { + if (!engine.getScheduler().removeAsyncManager(w)) { + throw new IllegalStateException("Unable to remove world from scheduler when halting was attempted"); + } + //getEventManager().callDelayedEvent(new WorldUnloadEvent(world)); + w.unload(save); + } + // Note: Worlds should not allow being saved twice and/or throw exceptions if accessed after unloading. + // Also, should blank out as much internal world data as possible, in case plugins retain references to unloaded worlds. + } + return success; + } +} diff --git a/src/main/java/org/spout/engine/geo/SpoutWorld.java b/src/main/java/org/spout/engine/geo/SpoutWorld.java new file mode 100644 index 0000000..c210690 --- /dev/null +++ b/src/main/java/org/spout/engine/geo/SpoutWorld.java @@ -0,0 +1,463 @@ +package org.spout.engine.geo; + +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import com.flowpowered.commons.datatable.ManagedHashMap; +import com.flowpowered.commons.datatable.ManagedMap; +import com.flowpowered.events.Cause; +import org.spout.api.Engine; +import org.spout.api.component.BaseComponentOwner; +import org.spout.api.component.Component; +import org.spout.api.entity.Entity; +import org.spout.api.entity.EntityPrefab; +import org.spout.api.entity.Player; +import org.spout.api.generator.WorldGenerator; +import org.spout.api.geo.LoadOption; +import org.spout.api.geo.World; +import org.spout.api.geo.cuboid.Block; +import org.spout.api.geo.cuboid.Chunk; +import org.spout.api.geo.cuboid.Region; +import org.spout.api.geo.discrete.Point; +import org.spout.api.geo.discrete.Transform; +import org.spout.api.material.BlockMaterial; +import org.spout.api.scheduler.TaskManager; +import org.spout.api.util.cuboid.CuboidBlockMaterialBuffer; +import org.spout.engine.util.thread.AsyncManager; +import org.spout.engine.util.thread.snapshotable.SnapshotManager; +import org.spout.engine.util.thread.snapshotable.SnapshotableLong; +import org.spout.math.GenericMath; +import org.spout.math.vector.Vector3f; + +public class SpoutWorld extends BaseComponentOwner implements World, AsyncManager { + private final String name; + private final UUID uid; + private final SnapshotManager snapshotManager; + private final SnapshotableLong age; + + public SpoutWorld(String name, UUID uid, long age) { + this.name = name; + this.uid = uid; + this.snapshotManager = new SnapshotManager(); + this.age = new SnapshotableLong(snapshotManager, age); + } + + public SpoutWorld(String name) { + this(name, UUID.randomUUID(), 0); + } + + + @Override + public String getName() { + return name; + } + + @Override + public long getAge() { + return age.get(); + } + + @Override + public UUID getUID() { + return uid; + } + + @Override + public Entity getEntity(UUID uid) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity createEntity(Point point, Class... classes) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity createEntity(Point point, EntityPrefab prefab) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void spawnEntity(Entity e) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity createAndSpawnEntity(Point point, LoadOption option, EntityPrefab prefab) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity createAndSpawnEntity(Point point, LoadOption option, Class... classes) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity[] createAndSpawnEntity(Point[] points, LoadOption option, Class... classes) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Engine getEngine() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getAll() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity getEntity(int id) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TaskManager getParallelTaskManager() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public TaskManager getTaskManager() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getNearbyEntities(Point position, Entity ignore, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getNearbyEntities(Point position, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getNearbyEntities(Entity entity, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity getNearestEntity(Point position, Entity ignore, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity getNearestEntity(Point position, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Entity getNearestEntity(Entity entity, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getNearbyPlayers(Point position, Player ignore, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getNearbyPlayers(Point position, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getNearbyPlayers(Entity entity, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Player getNearestPlayer(Point position, Player ignore, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Player getNearestPlayer(Point position, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Player getNearestPlayer(Entity entity, int range) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setCuboid(CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setCuboid(int x, int y, int z, CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void getCuboid(int bx, int by, int bz, CuboidBlockMaterialBuffer buffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void getCuboid(CuboidBlockMaterialBuffer buffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public List getPlayers() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Collection getRegions() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegion(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegion(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegionFromChunk(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegionFromChunk(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegionFromBlock(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegionFromBlock(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegionFromBlock(Vector3f position) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Region getRegionFromBlock(Vector3f position, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + @Override + public Chunk getChunk(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean containsChunk(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getChunkFromBlock(int x, int y, int z, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Chunk getChunkFromBlock(Vector3f position, LoadOption loadopt) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean hasChunk(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean hasChunkAtBlock(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void saveChunk(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void unloadChunk(int x, int y, int z, boolean save) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getNumLoadedChunks() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean setBlockData(int x, int y, int z, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean addBlockData(int x, int y, int z, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean setBlockMaterial(int x, int y, int z, BlockMaterial material, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean compareAndSetData(int x, int y, int z, int expect, short data, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short setBlockDataBits(int x, int y, int z, int bits, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short setBlockDataBits(int x, int y, int z, int bits, boolean set, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short clearBlockDataBits(int x, int y, int z, int bits, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getBlockDataField(int x, int y, int z, int bits) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isBlockDataBitSet(int x, int y, int z, int bits) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int setBlockDataField(int x, int y, int z, int bits, int value, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int addBlockDataField(int x, int y, int z, int bits, int value, Cause source) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean containsBlock(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public SpoutBlock getBlock(int x, int y, int z) { + return new SpoutBlock(this, x, y, z); + } + + @Override + public SpoutBlock getBlock(float x, float y, float z) { + return this.getBlock(GenericMath.floor(x), GenericMath.floor(y), GenericMath.floor(z)); + } + + @Override + public SpoutBlock getBlock(Vector3f position) { + return this.getBlock(position.getX(), position.getY(), position.getZ()); + } + + @Override + public boolean commitCuboid(CuboidBlockMaterialBuffer buffer, Cause cause) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(boolean backBuffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public CuboidBlockMaterialBuffer getCuboid(int bx, int by, int bz, int sx, int sy, int sz, boolean backBuffer) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public BlockMaterial getBlockMaterial(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int getBlockFullState(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public short getBlockData(int x, int y, int z) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void finalizeRun() { + } + + @Override + public void preSnapshotRun() { + } + + @Override + public void copySnapshotRun() { + System.out.println("copySnapshot"); + } + + @Override + public void startTickRun(int stage, long delta) { + } + + @Override + public void runPhysics(int sequence) { + } + + @Override + public void runDynamicUpdates(long threshold, int sequence) { + } + + @Override + public void runLighting(int sequence) { + } + + @Override + public int getSequence() { + return -1; + } + + @Override + public long getFirstDynamicUpdateTime() { + return 0; + } + + @Override + public Thread getExecutionThread() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public void setExecutionThread(Thread t) { + } + + @Override + public int getMaxStage() { + return -1; + } +} diff --git a/src/main/java/org/spout/engine/geo/SpoutWorldManager.java b/src/main/java/org/spout/engine/geo/SpoutWorldManager.java new file mode 100644 index 0000000..c1b550c --- /dev/null +++ b/src/main/java/org/spout/engine/geo/SpoutWorldManager.java @@ -0,0 +1,65 @@ +package org.spout.engine.geo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; + +import com.flowpowered.commons.StringUtil; + +import org.spout.api.geo.World; +import org.spout.api.geo.WorldManager; +import org.spout.engine.SpoutEngine; +import org.spout.engine.util.thread.snapshotable.SnapshotableLinkedHashMap; + +public class SpoutWorldManager implements WorldManager { + protected final SpoutEngine engine; + protected final SnapshotableLinkedHashMap loadedWorlds; + + public SpoutWorldManager(SpoutEngine engine) { + loadedWorlds = new SnapshotableLinkedHashMap<>(engine.getSnapshotManager()); + this.engine = engine; + } + + @Override + public World getWorld(String name) { + return getWorld(name, true); + } + + @Override + public World getWorld(String name, boolean exact) { + if (exact) { + SpoutWorld world = loadedWorlds.get().get(name); + if (world != null) { + return world; + } + return loadedWorlds.get().get(name); + } else { + return StringUtil.getShortest(StringUtil.matchName(loadedWorlds.getValues(), name)); + } + } + + @Override + public Collection matchWorld(String name) { + return StringUtil.matchName(getWorlds(), name); + } + + @Override + public SpoutWorld getWorld(UUID uid) { + for (SpoutWorld world : loadedWorlds.getValues()) { + if (world.getUID().equals(uid)) { + return world; + } + } + return null; + } + + @Override + public Collection getWorlds() { + Collection w = new ArrayList<>(); + for (SpoutWorld world : loadedWorlds.getValues()) { + w.add(world); + } + return w; + } + +} diff --git a/src/main/java/org/spout/engine/render/DeployNatives.java b/src/main/java/org/spout/engine/render/DeployNatives.java new file mode 100644 index 0000000..be9c7cc --- /dev/null +++ b/src/main/java/org/spout/engine/render/DeployNatives.java @@ -0,0 +1,44 @@ +package org.spout.engine.render; + +import java.io.File; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.SystemUtils; + +public class DeployNatives { + public static void deploy() throws Exception { + final String osPath; + final String[] nativeLibs; + if (SystemUtils.IS_OS_WINDOWS) { + nativeLibs = new String[]{ + "jinput-dx8_64.dll", "jinput-dx8.dll", "jinput-raw_64.dll", "jinput-raw.dll", + "jinput-wintab.dll", "lwjgl.dll", "lwjgl64.dll", "OpenAL32.dll", "OpenAL64.dll" + }; + osPath = "windows/"; + } else if (SystemUtils.IS_OS_MAC) { + nativeLibs = new String[]{ + "libjinput-osx.jnilib", "liblwjgl.jnilib", "openal.dylib" + }; + osPath = "mac/"; + } else if (SystemUtils.IS_OS_LINUX) { + nativeLibs = new String[]{ + "liblwjgl.so", "liblwjgl64.so", "libopenal.so", "libopenal64.so", "libjinput-linux.so", + "libjinput-linux64.so" + }; + osPath = "linux/"; + } else { + throw new IllegalStateException("Could not get lwjgl natives for OS \"" + SystemUtils.OS_NAME + "\"."); + } + final File nativesDir = new File("natives" + File.separator + osPath); + nativesDir.mkdirs(); + for (String nativeLib : nativeLibs) { + final File nativeFile = new File(nativesDir, nativeLib); + if (!nativeFile.exists()) { + FileUtils.copyInputStreamToFile(DeployNatives.class.getResourceAsStream("/" + nativeLib), nativeFile); + } + } + final String nativesPath = nativesDir.getAbsolutePath(); + System.setProperty("org.lwjgl.librarypath", nativesPath); + System.setProperty("net.java.games.input.librarypath", nativesPath); + } +} diff --git a/src/main/java/org/spout/engine/render/MeshGenerator.java b/src/main/java/org/spout/engine/render/MeshGenerator.java new file mode 100644 index 0000000..498efde --- /dev/null +++ b/src/main/java/org/spout/engine/render/MeshGenerator.java @@ -0,0 +1,691 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.render; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import gnu.trove.list.TFloatList; +import gnu.trove.list.TIntList; +import gnu.trove.list.array.TFloatArrayList; +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TObjectIntHashMap; + +import org.spout.math.vector.Vector2f; +import org.spout.physics.math.Vector3; +import org.spout.renderer.data.VertexAttribute; +import org.spout.renderer.data.VertexAttribute.DataType; +import org.spout.renderer.data.VertexData; +import org.spout.renderer.util.CausticUtil; + +/** + * Generates various shape meshes of the desired size and stores them to the models. + */ +public class MeshGenerator { + /* + ^ + | y + | + | x + -------> + \ + \ + \ z + V + The axis system + */ + + /** + * Generates a crosshairs shaped wireframe in 3D. The center is at the intersection point of the three lines. + * + * @param destination Where to save the mesh (can be null) + * @param length The length for the three lines + * @return The vertex data + */ + public static VertexData generateCrosshairs(VertexData destination, float length) { + /* + \ | + \| + ----O----- + |\ + | \ + */ + // Model data buffers + if (destination == null) { + destination = new VertexData(); + } + final VertexAttribute positionsAttribute = new VertexAttribute("positions", DataType.FLOAT, 3); + destination.addAttribute(0, positionsAttribute); + final TFloatList positions = new TFloatArrayList(); + final TIntList indices = destination.getIndices(); + length /= 2; + // Add the x axis line + addAll(positions, -length, 0, 0, length, 0, 0); + addAll(indices, 0, 1); + // Add the y axis line + addAll(positions, 0, -length, 0, 0, length, 0); + addAll(indices, 2, 3); + // Add the z axis line + addAll(positions, 0, 0, -length, 0, 0, length); + addAll(indices, 4, 5); + // Put the mesh in the vertex data + positionsAttribute.setData(positions); + return destination; + } + + /** + * Generates a cuboid shaped wireframe (the outline of the cuboid). The center is at the middle of the cuboid. + * + * @param destination Where to save the mesh (can be null) + * @param size The size of the cuboid to generate, on x, y and z + * @return The vertex data + */ + public static VertexData generateWireCuboid(VertexData destination, Vector3 size) { + /* + 4------5 + |\ |\ + | 7------6 + | | | | + 0-|----1 | + \| \| + 3------2 + */ + // Corner positions + final Vector3 p = Vector3.divide(size, 2); + final Vector3 p6 = new Vector3(p.getX(), p.getY(), p.getZ()); + final Vector3 p0 = Vector3.negate(p6); + final Vector3 p7 = new Vector3(-p.getX(), p.getY(), p.getZ()); + final Vector3 p1 = Vector3.negate(p7); + final Vector3 p4 = new Vector3(-p.getX(), p.getY(), -p.getZ()); + final Vector3 p2 = Vector3.negate(p4); + final Vector3 p5 = new Vector3(p.getX(), p.getY(), -p.getZ()); + final Vector3 p3 = Vector3.negate(p5); + // Model data buffers + if (destination == null) { + destination = new VertexData(); + } + final VertexAttribute positionsAttribute = new VertexAttribute("positions", DataType.FLOAT, 3); + destination.addAttribute(0, positionsAttribute); + final TFloatList positions = new TFloatArrayList(); + final VertexAttribute normalsAttribute = new VertexAttribute("normals", DataType.FLOAT, 3); + destination.addAttribute(1, normalsAttribute); + final TFloatList normals = new TFloatArrayList(); + final TIntList indices = destination.getIndices(); + // Add all of the corners + addVector(positions, p0); + addVector(normals, p0.normalize()); + addVector(positions, p1); + addVector(normals, p1.normalize()); + addVector(positions, p2); + addVector(normals, p2.normalize()); + addVector(positions, p3); + addVector(normals, p3.normalize()); + addVector(positions, p4); + addVector(normals, p4.normalize()); + addVector(positions, p5); + addVector(normals, p5.normalize()); + addVector(positions, p6); + addVector(normals, p6.normalize()); + addVector(positions, p7); + addVector(normals, p7.normalize()); + // Face x + addAll(indices, 1, 2, 2, 6, 6, 5, 5, 1); + // Face y + addAll(indices, 4, 5, 5, 6, 6, 7, 7, 4); + // Face z + addAll(indices, 2, 3, 3, 7, 7, 6, 6, 2); + // Face -x + addAll(indices, 0, 3, 3, 7, 7, 4, 4, 0); + // Face -y + addAll(indices, 0, 1, 1, 2, 2, 3, 3, 0); + // Face -z + addAll(indices, 0, 1, 1, 5, 5, 4, 4, 0); + // Put the mesh in the vertex data + positionsAttribute.setData(positions); + normalsAttribute.setData(normals); + return destination; + } + + /** + * Generates a plane on xy. The center is at the middle of the plane. + * + * @param destination Where to save the mesh (can be null) + * @param size The size of the plane to generate, on x and y + * @return The vertex data + */ + public static VertexData generatePlane(VertexData destination, Vector2f size) { + /* + 2-----3 + | | + | | + 0-----1 + */ + // Corner positions + final Vector2f p = size.div(2); + final Vector3 p3 = new Vector3(p.getX(), p.getY(), 0); + final Vector3 p2 = new Vector3(-p.getX(), p.getY(), 0); + final Vector3 p1 = new Vector3(p.getX(), -p.getY(), 0); + final Vector3 p0 = new Vector3(-p.getX(), -p.getY(), 0); + // Normal + final Vector3 n = new Vector3(0, 0, 1); + // Model data buffers + if (destination == null) { + destination = new VertexData(); + } + final VertexAttribute positionsAttribute = new VertexAttribute("positions", DataType.FLOAT, 3); + destination.addAttribute(0, positionsAttribute); + final TFloatList positions = new TFloatArrayList(); + final VertexAttribute normalsAttribute = new VertexAttribute("normals", DataType.FLOAT, 3); + destination.addAttribute(1, normalsAttribute); + final TFloatList normals = new TFloatArrayList(); + final TIntList indices = destination.getIndices(); + // Face + addVector(positions, p0); + addVector(normals, n); + addVector(positions, p1); + addVector(normals, n); + addVector(positions, p2); + addVector(normals, n); + addVector(positions, p3); + addVector(normals, n); + addAll(indices, 0, 3, 2, 0, 1, 3); + // Put the mesh in the vertex data + positionsAttribute.setData(positions); + normalsAttribute.setData(normals); + return destination; + } + + /** + * Generates a textured plane on xy. The center is at the middle of the plane. + * + * @param destination Where to save the mesh (can be null) + * @param size The size of the plane to generate, on x and y + * @return The vertex data + */ + public static VertexData generateTexturedPlane(VertexData destination, Vector2f size) { + destination = generatePlane(destination, size); + final VertexAttribute textureAttribute = new VertexAttribute("textureCoords", DataType.FLOAT, 2); + destination.addAttribute(2, textureAttribute); + final TFloatList texture = new TFloatArrayList(); + // Face + addAll(texture, 0, 0); + addAll(texture, 1, 0); + addAll(texture, 0, 1); + addAll(texture, 1, 1); + // Put the mesh in the vertex data + textureAttribute.setData(texture); + return destination; + } + + /** + * Generates a solid cuboid mesh. This mesh includes the positions, normals, texture coords and tangents. The center is at the middle of the cuboid. + * + * @param destination Where to save the mesh (can be null) + * @param size The size of the cuboid to generate, on x, y and z + * @return The vertex data + */ + public static VertexData generateCuboid(VertexData destination, Vector3 size) { + /* + 4------5 + |\ |\ + | 7------6 + | | | | + 0-|----1 | + \| \| + 3------2 + */ + // Corner positions + final Vector3 p = Vector3.divide(size, 2); + final Vector3 p6 = new Vector3(p.getX(), p.getY(), p.getZ()); + final Vector3 p0 = Vector3.negate(p6); + final Vector3 p7 = new Vector3(-p.getX(), p.getY(), p.getZ()); + final Vector3 p1 = Vector3.negate(p7); + final Vector3 p4 = new Vector3(-p.getX(), p.getY(), -p.getZ()); + final Vector3 p2 = Vector3.negate(p4); + final Vector3 p5 = new Vector3(p.getX(), p.getY(), -p.getZ()); + final Vector3 p3 = Vector3.negate(p5); + // Face normals + final Vector3 nx = new Vector3(1, 0, 0); + final Vector3 ny = new Vector3(0, 1, 0); + final Vector3 nz = new Vector3(0, 0, 1); + final Vector3 nxN = new Vector3(-1, 0, 0); + final Vector3 nyN = new Vector3(0, -1, 0); + final Vector3 nzN = new Vector3(0, 0, -1); + // Create a destination if missing + if (destination == null) { + destination = new VertexData(); + } + // Positions + final VertexAttribute positionsAttribute = new VertexAttribute("positions", DataType.FLOAT, 3); + destination.addAttribute(0, positionsAttribute); + final TFloatList positions = new TFloatArrayList(); + // Normals + final VertexAttribute normalsAttribute = new VertexAttribute("normals", DataType.FLOAT, 3); + destination.addAttribute(1, normalsAttribute); + final TFloatList normals = new TFloatArrayList(); + final TIntList indices = destination.getIndices(); + // Texture coords + final VertexAttribute textureAttribute = new VertexAttribute("textureCoords", DataType.FLOAT, 2); + destination.addAttribute(2, textureAttribute); + final TFloatList textures = new TFloatArrayList(); + // Tangents + final VertexAttribute tangentAttribute = new VertexAttribute("tangents", DataType.FLOAT, 4); + destination.addAttribute(3, tangentAttribute); + final TFloatList tangents = new TFloatArrayList(); + // Face x + addVector(positions, p2); + addVector(normals, nx); + addAll(textures, 0, 0); + addVector(positions, p6); + addVector(normals, nx); + addAll(textures, 0, 1); + addVector(positions, p5); + addVector(normals, nx); + addAll(textures, 1, 1); + addVector(positions, p1); + addVector(normals, nx); + addAll(textures, 1, 0); + addAll(indices, 0, 2, 1, 0, 3, 2); + // Face y + addVector(positions, p4); + addVector(normals, ny); + addAll(textures, 0, 0); + addVector(positions, p5); + addVector(normals, ny); + addAll(textures, 0, 1); + addVector(positions, p6); + addVector(normals, ny); + addAll(textures, 1, 1); + addVector(positions, p7); + addVector(normals, ny); + addAll(textures, 1, 0); + addAll(indices, 4, 6, 5, 4, 7, 6); + // Face z + addVector(positions, p3); + addVector(normals, nz); + addAll(textures, 0, 0); + addVector(positions, p7); + addVector(normals, nz); + addAll(textures, 0, 1); + addVector(positions, p6); + addVector(normals, nz); + addAll(textures, 1, 1); + addVector(positions, p2); + addVector(normals, nz); + addAll(textures, 1, 0); + addAll(indices, 8, 10, 9, 8, 11, 10); + // Face -x + addVector(positions, p0); + addVector(normals, nxN); + addAll(textures, 0, 0); + addVector(positions, p4); + addVector(normals, nxN); + addAll(textures, 0, 1); + addVector(positions, p7); + addVector(normals, nxN); + addAll(textures, 1, 1); + addVector(positions, p3); + addVector(normals, nxN); + addAll(textures, 1, 0); + addAll(indices, 12, 14, 13, 12, 15, 14); + // Face -y + addVector(positions, p0); + addVector(normals, nyN); + addAll(textures, 0, 0); + addVector(positions, p3); + addVector(normals, nyN); + addAll(textures, 0, 1); + addVector(positions, p2); + addVector(normals, nyN); + addAll(textures, 1, 1); + addVector(positions, p1); + addVector(normals, nyN); + addAll(textures, 1, 0); + addAll(indices, 16, 18, 17, 16, 19, 18); + // Face -z + addVector(positions, p1); + addVector(normals, nzN); + addAll(textures, 0, 0); + addVector(positions, p5); + addVector(normals, nzN); + addAll(textures, 0, 1); + addVector(positions, p4); + addVector(normals, nzN); + addAll(textures, 1, 1); + addVector(positions, p0); + addVector(normals, nzN); + addAll(textures, 1, 0); + addAll(indices, 20, 22, 21, 20, 23, 22); + // Automatically generate the tangents + CausticUtil.generateTangents(positions, normals, textures, indices, tangents); + // Put the mesh in the vertex data + positionsAttribute.setData(positions); + normalsAttribute.setData(normals); + textureAttribute.setData(textures); + tangentAttribute.setData(tangents); + return destination; + } + + /** + * Generates a solid spherical mesh. The center is at the middle of the sphere. + * + * @param destination Where to save the mesh (can be null) + * @param radius The radius of the sphere + * @return The vertex data + */ + public static VertexData generateSphere(VertexData destination, float radius) { + // Octahedron positions + final Vector3 v0 = new Vector3(0.0f, -1.0f, 0.0f); + final Vector3 v1 = new Vector3(1.0f, 0.0f, 0.0f); + final Vector3 v2 = new Vector3(0.0f, 0.0f, 1.0f); + final Vector3 v3 = new Vector3(-1.0f, 0.0f, 0.0f); + final Vector3 v4 = new Vector3(0.0f, 0.0f, -1.0f); + final Vector3 v5 = new Vector3(0.0f, 1.0f, 0.0f); + // Build a list of triangles + final List triangles = new ArrayList<>(); + triangles.addAll(Arrays.asList( + new Triangle(v0, v1, v2), + new Triangle(v0, v2, v3), + new Triangle(v0, v3, v4), + new Triangle(v0, v4, v1), + new Triangle(v1, v5, v2), + new Triangle(v2, v5, v3), + new Triangle(v3, v5, v4), + new Triangle(v4, v5, v1))); + // List to store the subdivided triangles + final List newTriangles = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + // Subdivide all of the triangles by splitting the edges + for (Triangle triangle : triangles) { + newTriangles.addAll(Arrays.asList(subdivide(triangle))); + } + // Store the new triangles in the main list + triangles.clear(); + triangles.addAll(newTriangles); + // Clear the new triangles for the next run + newTriangles.clear(); + } + // Normalize the positions so they are all the same distance from the center + // then scale them to the appropriate radius + for (Triangle triangle : triangles) { + triangle.getV0().normalize().multiply(radius); + triangle.getV1().normalize().multiply(radius); + triangle.getV2().normalize().multiply(radius); + } + // Model data buffers + if (destination == null) { + destination = new VertexData(); + } + final VertexAttribute positionsAttribute = new VertexAttribute("positions", DataType.FLOAT, 3); + destination.addAttribute(0, positionsAttribute); + final TFloatList positions = new TFloatArrayList(); + final VertexAttribute normalsAttribute = new VertexAttribute("normals", DataType.FLOAT, 3); + destination.addAttribute(1, normalsAttribute); + final TFloatList normals = new TFloatArrayList(); + final TIntList indices = destination.getIndices(); + // Add the triangle faces to the data buffers + int index = 0; + // Keep track of already added vertices, so we can reuse them for a smaller mesh + final TObjectIntMap addedVertices = new TObjectIntHashMap<>(); + for (Triangle triangle : triangles) { + final Vector3 vt0 = triangle.getV0(); + final Vector3 vt1 = triangle.getV1(); + final Vector3 vt2 = triangle.getV2(); + if (addedVertices.containsKey(vt0)) { + addAll(indices, addedVertices.get(vt0)); + } else { + addVector(positions, vt0); + addVector(normals, vt0.getUnit()); + addedVertices.put(vt0, index); + addAll(indices, index++); + } + if (addedVertices.containsKey(vt1)) { + addAll(indices, addedVertices.get(vt1)); + } else { + addVector(positions, vt1); + addVector(normals, vt1.getUnit()); + addedVertices.put(vt1, index); + addAll(indices, index++); + } + if (addedVertices.containsKey(vt2)) { + addAll(indices, addedVertices.get(vt2)); + } else { + addVector(positions, vt2); + addVector(normals, vt2.getUnit()); + addedVertices.put(vt2, index); + addAll(indices, index++); + } + } + // Put the mesh in the vertex data + positionsAttribute.setData(positions); + normalsAttribute.setData(normals); + return destination; + } + + /** + * Generates a cylindrical solid mesh. The center is at middle of the of the cylinder. + * + * @param destination Where to save the mesh (can be null) + * @param radius The radius of the base and top + * @param height The height (distance from the base to the top) + * @return The vertex data + */ + public static VertexData generateCylinder(VertexData destination, float radius, float height) { + // 0,0,0 will be halfway up the cylinder in the middle + final float halfHeight = height / 2; + // The positions at the rims of the cylinders + final List rims = new ArrayList<>(); + for (int angle = 0; angle < 360; angle += 15) { + final double angleRads = Math.toRadians(angle); + rims.add(new Vector3( + radius * (float) Math.cos(angleRads), + halfHeight, + radius * (float) -Math.sin(angleRads))); + } + // Model data buffers + if (destination == null) { + destination = new VertexData(); + } + final VertexAttribute positionsAttribute = new VertexAttribute("positions", DataType.FLOAT, 3); + destination.addAttribute(0, positionsAttribute); + final TFloatList positions = new TFloatArrayList(); + final VertexAttribute normalsAttribute = new VertexAttribute("normals", DataType.FLOAT, 3); + destination.addAttribute(1, normalsAttribute); + final TFloatList normals = new TFloatArrayList(); + final TIntList indices = destination.getIndices(); + // The normals for the triangles of the top and bottom faces + final Vector3 topNormal = new Vector3(0, 1, 0); + final Vector3 bottomNormal = new Vector3(0, -1, 0); + // Add the top and bottom face center vertices + addVector(positions, new Vector3(0, halfHeight, 0));// 0 + addVector(normals, topNormal); + addVector(positions, new Vector3(0, -halfHeight, 0));// 1 + addVector(normals, bottomNormal); + // Add all the faces section by section, turning around the y axis + final int rimsSize = rims.size(); + for (int i = 0; i < rimsSize; i++) { + // Get the top and bottom vertex positions and the side normal + final Vector3 t = rims.get(i); + final Vector3 b = new Vector3(t.getX(), -t.getY(), t.getZ()); + final Vector3 n = new Vector3(t.getX(), 0, t.getZ()).normalize(); + // Top face vertex + addVector(positions, t);// index + addVector(normals, topNormal); + // Bottom face vertex + addVector(positions, b);// index + 1 + addVector(normals, bottomNormal); + // Side top vertex + addVector(positions, t);// index + 2 + addVector(normals, n); + // Side bottom vertex + addVector(positions, b);// index + 3 + addVector(normals, n); + // Get the current index for our vertices + final int currentIndex = i * 4 + 2; + // Get the index for the next iteration, wrapping around at the end + final int nextIndex = (i == rimsSize - 1 ? 0 : i + 1) * 4 + 2; + // Add the 4 triangles (1 top, 1 bottom, 2 for the side) + addAll(indices, 0, currentIndex, nextIndex); + addAll(indices, 1, nextIndex + 1, currentIndex + 1); + addAll(indices, currentIndex + 2, currentIndex + 3, nextIndex + 2); + addAll(indices, currentIndex + 3, nextIndex + 3, nextIndex + 2); + } + // Put the mesh in the vertex data + positionsAttribute.setData(positions); + normalsAttribute.setData(normals); + return destination; + } + + /** + * Generates a conical solid mesh. The center is at the middle of the cone. + * + * @param destination Where to save the mesh (can be null) + * @param radius The radius of the base + * @param height The height (distance from the base to the apex) + * @return The vertex data + */ + public static VertexData generateCone(VertexData destination, float radius, float height) { + // 0,0,0 will be halfway up the cone in the middle + final float halfHeight = height / 2; + // The positions at the bottom rim of the cone + final List rim = new ArrayList<>(); + for (int angle = 0; angle < 360; angle += 15) { + final double angleRads = Math.toRadians(angle); + rim.add(new Vector3( + radius * (float) Math.cos(angleRads), + -halfHeight, + radius * (float) -Math.sin(angleRads))); + } + // Model data buffers + if (destination == null) { + destination = new VertexData(); + } + final VertexAttribute positionsAttribute = new VertexAttribute("positions", DataType.FLOAT, 3); + destination.addAttribute(0, positionsAttribute); + final TFloatList positions = new TFloatArrayList(); + final VertexAttribute normalsAttribute = new VertexAttribute("normals", DataType.FLOAT, 3); + destination.addAttribute(1, normalsAttribute); + final TFloatList normals = new TFloatArrayList(); + final TIntList indices = destination.getIndices(); + // Apex of the cone + final Vector3 top = new Vector3(0, halfHeight, 0); + // The normal for the triangle of the bottom face + final Vector3 bottomNormal = new Vector3(0, -1, 0); + // Add the bottom face center vertex + addVector(positions, new Vector3(0, -halfHeight, 0));// 0 + addVector(normals, bottomNormal); + // Add all the faces section by section, turning around the y axis + final int rimSize = rim.size(); + for (int i = 0; i < rimSize; i++) { + // Get the bottom vertex position and the side normal + final Vector3 b = rim.get(i); + final Vector3 bn = new Vector3(b.getX(), 0, b.getZ()).normalize(); + // Average the current and next normal to get the top normal + final int nextI = i == rimSize - 1 ? 0 : i + 1; + final Vector3 nextB = rim.get(nextI); + final Vector3 tn = mean(bn, new Vector3(nextB.getX(), 0, nextB.getZ()).normalize()); + // Top side vertex + addVector(positions, top);// index + addVector(normals, tn); + // Bottom side vertex + addVector(positions, b);// index + 1 + addVector(normals, bn); + // Bottom face vertex + addVector(positions, b);// index + 2 + addVector(normals, bottomNormal); + // Get the current index for our vertices + final int currentIndex = i * 3 + 1; + // Get the index for the next iteration, wrapping around at the end + final int nextIndex = nextI * 3 + 1; + // Add the 2 triangles (1 side, 1 bottom) + addAll(indices, currentIndex, currentIndex + 1, nextIndex + 1); + addAll(indices, currentIndex + 2, 0, nextIndex + 2); + } + // Put the mesh in the vertex data + positionsAttribute.setData(positions); + normalsAttribute.setData(normals); + return destination; + } + + private static Vector3 mean(Vector3 v0, Vector3 v1) { + return new Vector3( + (v0.getX() + v1.getX()) / 2, + (v0.getY() + v1.getY()) / 2, + (v0.getZ() + v1.getZ()) / 2); + } + + private static void addVector(TFloatList to, Vector3 v) { + to.add(v.getX()); + to.add(v.getY()); + to.add(v.getZ()); + } + + private static void addAll(TIntList to, int... f) { + to.add(f); + } + + private static void addAll(TFloatList to, float... f) { + to.add(f); + } + + private static Triangle[] subdivide(Triangle triangle) { + final Vector3 e0 = Vector3.subtract(triangle.v1, triangle.v0).divide(2); + final Vector3 va = Vector3.add(triangle.v0, e0); + final Vector3 e1 = Vector3.subtract(triangle.v2, triangle.v1).divide(2); + final Vector3 vb = Vector3.add(triangle.v1, e1); + final Vector3 e2 = Vector3.subtract(triangle.v0, triangle.v2).divide(2); + final Vector3 vc = Vector3.add(triangle.v2, e2); + return new Triangle[]{ + new Triangle(triangle.v0, va, vc), + new Triangle(va, triangle.v1, vb), + new Triangle(vc, vb, triangle.v2), + new Triangle(va, vb, vc) + }; + } + + private static class Triangle { + private final Vector3 v0 = new Vector3(); + private final Vector3 v1 = new Vector3(); + private final Vector3 v2 = new Vector3(); + + private Triangle(Vector3 v0, Vector3 v1, Vector3 v2) { + this.v0.set(v0); + this.v1.set(v1); + this.v2.set(v2); + } + + private Vector3 getV0() { + return v0; + } + + private Vector3 getV1() { + return v1; + } + + private Vector3 getV2() { + return v2; + } + } +} diff --git a/src/main/java/org/spout/engine/render/SpoutRenderer.java b/src/main/java/org/spout/engine/render/SpoutRenderer.java new file mode 100644 index 0000000..8d8b8f2 --- /dev/null +++ b/src/main/java/org/spout/engine/render/SpoutRenderer.java @@ -0,0 +1,864 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.render; + +import javax.imageio.ImageIO; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.lwjgl.opengl.GLContext; +import org.spout.engine.render.effect.BlurEffect; +import org.spout.engine.render.effect.SSAOEffect; +import org.spout.engine.render.effect.ShadowMappingEffect; +import org.spout.engine.scheduler.SpoutScheduler; + +import org.spout.math.imaginary.Quaternionf; +import org.spout.math.matrix.Matrix3f; +import org.spout.math.matrix.Matrix4f; +import org.spout.math.vector.Vector2f; +import org.spout.math.vector.Vector3f; +import org.spout.renderer.Action.RenderModelsAction; +import org.spout.renderer.Camera; +import org.spout.renderer.GLImplementation; +import org.spout.renderer.GLVersioned.GLVersion; +import org.spout.renderer.Material; +import org.spout.renderer.Pipeline; +import org.spout.renderer.Pipeline.PipelineBuilder; +import org.spout.renderer.data.Color; +import org.spout.renderer.data.Uniform; +import org.spout.renderer.data.Uniform.ColorUniform; +import org.spout.renderer.data.Uniform.FloatUniform; +import org.spout.renderer.data.Uniform.IntUniform; +import org.spout.renderer.data.Uniform.Matrix4Uniform; +import org.spout.renderer.data.Uniform.Vector2Uniform; +import org.spout.renderer.data.Uniform.Vector3Uniform; +import org.spout.renderer.data.UniformHolder; +import org.spout.renderer.data.VertexAttribute.DataType; +import org.spout.renderer.gl.Context; +import org.spout.renderer.gl.Context.BlendFunction; +import org.spout.renderer.gl.Context.Capability; +import org.spout.renderer.gl.FrameBuffer; +import org.spout.renderer.gl.FrameBuffer.AttachmentPoint; +import org.spout.renderer.gl.GLFactory; +import org.spout.renderer.gl.Program; +import org.spout.renderer.gl.Shader; +import org.spout.renderer.gl.Texture; +import org.spout.renderer.gl.Texture.CompareMode; +import org.spout.renderer.gl.Texture.FilterMode; +import org.spout.renderer.gl.Texture.Format; +import org.spout.renderer.gl.Texture.InternalFormat; +import org.spout.renderer.gl.Texture.WrapMode; +import org.spout.renderer.gl.VertexArray; +import org.spout.renderer.model.Model; +import org.spout.renderer.model.StringModel; +import org.spout.renderer.util.Rectangle; + +/** + * The default renderer. Support OpenGL 2.1 and 3.2. Can render fully textured models with normal and specular mapping, ambient occlusion (SSAO), shadow mapping, Phong shading, motion blur and edge + * detection anti-aliasing. The default OpenGL version is 3.2. + */ +public class SpoutRenderer implements org.spout.api.render.Renderer { + // CONSTANTS + private final String WINDOW_TITLE = "Spout"; + private final Vector2f WINDOW_SIZE = new Vector2f(1200, 800); + private final Vector2f SHADOW_SIZE = new Vector2f(2048, 2048); + private final float ASPECT_RATIO = WINDOW_SIZE.getX() / WINDOW_SIZE.getY(); + private final float FIELD_OF_VIEW = 60; + private final float TAN_HALF_FOV = (float) Math.tan(Math.toRadians(FIELD_OF_VIEW) / 2); + private final float NEAR_PLANE = 0.1f; + private final float FAR_PLANE = 1000; + private final Vector2f PROJECTION = new Vector2f(FAR_PLANE / (FAR_PLANE - NEAR_PLANE), (-FAR_PLANE * NEAR_PLANE) / (FAR_PLANE - NEAR_PLANE)); + private final Pattern ATTACHMENT_PATTERN = Pattern.compile("([a-zA-Z]+)(\\d*)"); + private final DateFormat SCREENSHOT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss"); + // SETTINGS + private boolean cullBackFaces = true; + // EFFECT UNIFORMS + private final Vector3Uniform lightDirectionUniform = new Vector3Uniform("lightDirection", Vector3f.FORWARD); + private final Matrix4Uniform inverseViewMatrixUniform = new Matrix4Uniform("inverseViewMatrix", new Matrix4f()); + private final Matrix4Uniform lightViewMatrixUniform = new Matrix4Uniform("lightViewMatrix", new Matrix4f()); + private final Matrix4Uniform lightProjectionMatrixUniform = new Matrix4Uniform("lightProjectionMatrix", new Matrix4f()); + private final Matrix4Uniform previousViewMatrixUniform = new Matrix4Uniform("previousViewMatrix", new Matrix4f()); + private final Matrix4Uniform previousProjectionMatrixUniform = new Matrix4Uniform("previousProjectionMatrix", new Matrix4f()); + private final FloatUniform blurStrengthUniform = new FloatUniform("blurStrength", 1); + // CAMERAS + private final Camera modelCamera = Camera.createPerspective(FIELD_OF_VIEW, WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), NEAR_PLANE, FAR_PLANE); + private final Camera lightCamera = Camera.createOrthographic(50, -50, 50, -50, -50, 50); + private final Camera guiCamera = Camera.createOrthographic(1, 0, 1 / ASPECT_RATIO, 0, NEAR_PLANE, FAR_PLANE); + // OPENGL VERSION AND FACTORY + private GLVersion glVersion = GLVersion.GL30; + private GLFactory glFactory = GLImplementation.get(glVersion); + // CONTEXT + private Context context; + // RENDER LISTS + private final List modelRenderList = new ArrayList<>(); + private final List transparentModelList = new ArrayList<>(); + private final List guiRenderList = new ArrayList<>(); + // PIPELINE + private Pipeline pipeline; + // SHADERS + private final Map programs = new HashMap<>(); + // TEXTURES + private final Map textures = new HashMap<>(); + // MATERIALS + private final Map materials = new HashMap<>(); + // FRAME BUFFERS + private final Map frameBuffers = new HashMap<>(); + // VERTEX ARRAYS + private VertexArray deferredStageScreenVertexArray; + // EFFECTS + private SSAOEffect ssaoEffect; + private ShadowMappingEffect shadowMappingEffect; + private BlurEffect blurEffect; + // MODEL PROPERTIES + private Color solidModelColor; + // FPS MONITOR + private final TPSMonitor fpsMonitor = new TPSMonitor(); + private StringModel fpsMonitorModel; + private boolean fpsMonitorStarted = false; + + /** + * Creates the OpenGL context and initializes the internal resources for the renderer + */ + public void init() { + initContext(); + initEffects(); + initPrograms(); + initTextures(); + initMaterials(); + initFrameBuffers(); + initVertexArrays(); + initPipeline(); + addDefaultObjects(); + } + + private void initContext() { + // CONTEXT + context = glFactory.createContext(); + context.setWindowTitle(WINDOW_TITLE); + context.setWindowSize(WINDOW_SIZE); + context.create(); + context.setClearColor(new Color(0, 0, 0, 0)); + context.setCamera(modelCamera); + if (cullBackFaces) { + context.enableCapability(Capability.CULL_FACE); + } + context.enableCapability(Capability.DEPTH_TEST); + if (glVersion == GLVersion.GL30 || GLContext.getCapabilities().GL_ARB_depth_clamp) { + context.enableCapability(Capability.DEPTH_CLAMP); + } + final UniformHolder uniforms = context.getUniforms(); + uniforms.add(previousViewMatrixUniform); + uniforms.add(previousProjectionMatrixUniform); + } + + private void initEffects() { + final int blurSize = 2; + // SSAO + ssaoEffect = new SSAOEffect(glFactory, WINDOW_SIZE, 8, blurSize, 0.5f, 0.15f, 2); + // SHADOW MAPPING + shadowMappingEffect = new ShadowMappingEffect(glFactory, WINDOW_SIZE, 8, blurSize, 0.005f, 0.0004f); + // BLUR + blurEffect = new BlurEffect(WINDOW_SIZE, blurSize); + } + + private void initPipeline() { + PipelineBuilder pipelineBuilder = new PipelineBuilder(); + // MODEL + pipelineBuilder = pipelineBuilder + .bindFrameBuffer(frameBuffers.get("model")) + .clearBuffer() + .renderModels(modelRenderList); + // WEIGHTED SUM TRANSPARENCY + pipelineBuilder = pipelineBuilder + .disableDepthMask() + .disableCapabilities(Capability.CULL_FACE) + .enableCapabilities(Capability.BLEND) + .setBlendingFunctions(BlendFunction.GL_ONE, BlendFunction.GL_ONE) + .bindFrameBuffer(frameBuffers.get("weightedSum")) + .clearBuffer() + .renderModels(transparentModelList) + .disableCapabilities(Capability.BLEND) + .enableCapabilities(Capability.CULL_FACE) + .enableDepthMask(); + // LIGHT MODEL + pipelineBuilder = pipelineBuilder + .useViewPort(new Rectangle(Vector2f.ZERO, SHADOW_SIZE)) + .useCamera(lightCamera) + .bindFrameBuffer(frameBuffers.get("lightModel")) + .clearBuffer() + .renderModels(modelRenderList) + .useViewPort(new Rectangle(Vector2f.ZERO, WINDOW_SIZE)) + .useCamera(modelCamera); + // SSAO + if (glVersion == GLVersion.GL30 || GLContext.getCapabilities().GL_ARB_depth_clamp) { + pipelineBuilder = pipelineBuilder + .disableCapabilities(Capability.DEPTH_CLAMP); + } + pipelineBuilder = pipelineBuilder + .disableCapabilities(Capability.DEPTH_TEST) + .doAction(new DoDeferredStageAction("ssao", deferredStageScreenVertexArray, "ssao")); + // SHADOW + pipelineBuilder = pipelineBuilder + .doAction(new DoDeferredStageAction("shadow", deferredStageScreenVertexArray, "shadow")); + // BLUR + pipelineBuilder = pipelineBuilder + .doAction(new DoDeferredStageAction("blur", deferredStageScreenVertexArray, "blur")); + // LIGHTING + pipelineBuilder = pipelineBuilder + .doAction(new DoDeferredStageAction("lighting", deferredStageScreenVertexArray, "lighting")); + // ANTI ALIASING + pipelineBuilder = pipelineBuilder + .doAction(new DoDeferredStageAction("antiAliasing", deferredStageScreenVertexArray, "antiAliasing")); + // TRANSPARENCY BLENDING + pipelineBuilder = pipelineBuilder + .enableCapabilities(Capability.BLEND) + .setBlendingFunctions(BlendFunction.GL_ONE_MINUS_SRC_ALPHA, BlendFunction.GL_SRC_ALPHA) + .doAction(new DoDeferredStageAction("transparencyBlending", deferredStageScreenVertexArray, "transparencyBlending")) + .disableCapabilities(Capability.BLEND) + .enableDepthMask(); + // MOTION BLUR + pipelineBuilder = pipelineBuilder + .doAction(new DoDeferredStageAction("motionBlur", deferredStageScreenVertexArray, "motionBlur")) + .unbindFrameBuffer(frameBuffers.get("motionBlur")) + .enableCapabilities(Capability.DEPTH_TEST); + if (glVersion == GLVersion.GL30 || GLContext.getCapabilities().GL_ARB_depth_clamp) { + pipelineBuilder = pipelineBuilder + .enableCapabilities(Capability.DEPTH_CLAMP); + } + // GUI + pipelineBuilder = pipelineBuilder + .useCamera(guiCamera) + .clearBuffer() + .renderModels(guiRenderList) + .useCamera(modelCamera) + .updateDisplay(); + // BUILD + pipeline = pipelineBuilder.build(); + } + + private void initPrograms() { + // SOLID + loadProgram("solid"); + // TEXTURED + loadProgram("textured"); + /// FONT + loadProgram("font"); + // SSAO + loadProgram("ssao"); + // SHADOW + loadProgram("shadow"); + // BLUR + loadProgram("blur"); + // LIGHTING + loadProgram("lighting"); + // MOTION BLUR + loadProgram("motionBlur"); + // ANTI ALIASING + loadProgram("edaa"); + // WEIGHTED SUM + loadProgram("weightedSum"); + // TRANSPARENCY BLENDING + loadProgram("transparencyBlending"); + // SCREEN + loadProgram("screen"); + } + + private void loadProgram(String name) { + final String shaderPath = "/shaders/" + glVersion.toString().toLowerCase() + "/" + name; + // SHADERS + final Shader vert = glFactory.createShader(); + vert.setSource(SpoutRenderer.class.getResourceAsStream(shaderPath + ".vert")); + vert.create(); + final Shader frag = glFactory.createShader(); + frag.setSource(SpoutRenderer.class.getResourceAsStream(shaderPath + ".frag")); + frag.create(); + // PROGRAM + final Program program = glFactory.createProgram(); + program.addShader(vert); + program.addShader(frag); + program.create(); + programs.put(name, program); + } + + private void initTextures() { + Texture texture; + // COLORS + texture = createTexture("colors", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RGBA, InternalFormat.RGBA8); + texture.setWrapS(WrapMode.CLAMP_TO_EDGE); + texture.setWrapT(WrapMode.CLAMP_TO_EDGE); + texture.setMagFilter(FilterMode.LINEAR); + texture.setMinFilter(FilterMode.LINEAR); + texture.create(); + // NORMALS + texture = createTexture("normals", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RGBA, InternalFormat.RGBA8); + texture.create(); + // VERTEX NORMALS + texture = createTexture("vertexNormals", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RGBA, InternalFormat.RGBA8); + texture.create(); + // MATERIALS + texture = createTexture("materials", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RGB, InternalFormat.RGB8); + texture.create(); + // VELOCITIES + texture = createTexture("velocities", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RG, InternalFormat.RG16F); + texture.setComponentType(DataType.HALF_FLOAT); + texture.create(); + // DEPTHS + texture = createTexture("depths", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.DEPTH, InternalFormat.DEPTH_COMPONENT32); + texture.setWrapS(WrapMode.CLAMP_TO_EDGE); + texture.setWrapT(WrapMode.CLAMP_TO_EDGE); + texture.create(); + // LIGHT DEPTHS + texture = createTexture("lightDepths", SHADOW_SIZE.getFloorX(), SHADOW_SIZE.getFloorY(), Format.DEPTH, InternalFormat.DEPTH_COMPONENT32); + texture.setWrapS(WrapMode.CLAMP_TO_BORDER); + texture.setWrapT(WrapMode.CLAMP_TO_BORDER); + texture.setMagFilter(FilterMode.LINEAR); + texture.setMinFilter(FilterMode.LINEAR); + texture.setCompareMode(CompareMode.LESS); + texture.create(); + // SSAO + texture = createTexture("ssao", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RED, InternalFormat.R8); + texture.create(); + // SHADOW + texture = createTexture("shadow", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RED, InternalFormat.R8); + texture.create(); + // AUX R + texture = createTexture("auxR", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RED, InternalFormat.R8); + texture.create(); + // AUX RGBA + texture = createTexture("auxRGBA", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RGBA, InternalFormat.RGBA8); + texture.setMagFilter(FilterMode.LINEAR); + texture.setMinFilter(FilterMode.LINEAR); + texture.create(); + // WEIGHTED COLORS + texture = createTexture("weightedColors", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RGBA, InternalFormat.RGBA16F); + texture.create(); + // WEIGHTED VELOCITIES + texture = createTexture("weightedVelocities", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RG, InternalFormat.RG16F); + texture.create(); + // LAYER COUNTS + texture = createTexture("layerCounts", WINDOW_SIZE.getFloorX(), WINDOW_SIZE.getFloorY(), Format.RED, InternalFormat.R16F); + texture.create(); + } + + private Texture createTexture(String name, int width, int height, Format format, InternalFormat internalFormat) { + final Texture texture = glFactory.createTexture(); + texture.setFormat(format); + texture.setInternalFormat(internalFormat); + texture.setImageData(null, width, height); + textures.put(name, texture); + return texture; + } + + private void initMaterials() { + Material material; + // SOLID + material = createMaterial("solid", "solid"); + addUniforms(material, new FloatUniform("diffuseIntensity", 0.8f), new FloatUniform("specularIntensity", 1), new FloatUniform("ambientIntensity", 0.2f)); + // SSAO + material = createMaterial("ssao", "ssao", "0:normals", "1:depths"); + material.addTexture(2, ssaoEffect.getNoiseTexture()); + addUniforms(material, new Vector2Uniform("projection", PROJECTION), new FloatUniform("tanHalfFOV", TAN_HALF_FOV), new FloatUniform("aspectRatio", ASPECT_RATIO)); + addUniforms(material, ssaoEffect.getUniforms()); + // SHADOW + material = createMaterial("shadow", "shadow", "0:vertexNormals", "1:depths", "2:lightDepths"); + material.addTexture(3, shadowMappingEffect.getNoiseTexture()); + addUniforms(material, new Vector2Uniform("projection", PROJECTION), new FloatUniform("tanHalfFOV", TAN_HALF_FOV), new FloatUniform("aspectRatio", ASPECT_RATIO), + lightDirectionUniform, inverseViewMatrixUniform, lightViewMatrixUniform, lightProjectionMatrixUniform); + addUniforms(material, shadowMappingEffect.getUniforms()); + // BLUR + material = createMaterial("blur", "blur", "0:auxR", "1:auxRGBA"); + addUniforms(material, blurEffect.getUniforms()); + // LIGHTING + material = createMaterial("lighting", "lighting", "0:colors", "1:normals", "2:depths", "3:materials", "4:ssao", "5:shadow"); + addUniforms(material, new FloatUniform("tanHalfFOV", TAN_HALF_FOV), new FloatUniform("aspectRatio", ASPECT_RATIO), lightDirectionUniform); + // ANTI ALIASING + material = createMaterial("antiAliasing", "edaa", "0:auxRGBA", "1:vertexNormals", "2:depths"); + addUniforms(material, new Vector2Uniform("projection", PROJECTION), new Vector2Uniform("resolution", WINDOW_SIZE), new FloatUniform("maxSpan", 8), + new Vector2Uniform("barriers", new Vector2f(0.8f, 0.5f)), new Vector2Uniform("weights", new Vector2f(0.25f, 0.6f)), new FloatUniform("kernel", 0.75f)); + // TRANSPARENCY + material = createMaterial("transparency", "weightedSum"); + addUniforms(material, lightDirectionUniform, new FloatUniform("diffuseIntensity", 0.8f), new FloatUniform("specularIntensity", 1), new FloatUniform("ambientIntensity", 0.2f)); + // TRANSPARENCY BLENDING + createMaterial("transparencyBlending", "transparencyBlending", "0:weightedColors", "1:weightedVelocities", "2:layerCounts"); + // MOTION BLUR + material = createMaterial("motionBlur", "motionBlur", "0:colors", "1:velocities"); + addUniforms(material, new Vector2Uniform("resolution", WINDOW_SIZE), new IntUniform("sampleCount", 8), blurStrengthUniform); + // SCREEN + createMaterial("screen", "screen", "0:auxRGBA"); + } + + private Material createMaterial(String name, String program, String... textures) { + final Material material = new Material(programs.get(program)); + for (String texture : textures) { + final String[] indexAndName = texture.split(":"); + material.addTexture(Integer.parseInt(indexAndName[0]), this.textures.get(indexAndName[1])); + } + materials.put(name, material); + return material; + } + + private void addUniforms(Material material, Uniform... uniforms) { + for (Uniform uniform : uniforms) { + material.getUniforms().add(uniform); + } + } + + private void initFrameBuffers() { + // MODEL + createFrameBuffer("model", "C0:colors", "C1:normals", "C2:vertexNormals", "C3:materials", "C4:velocities", "D:depths"); + // LIGHT MODEL + createFrameBuffer("lightModel", "D:lightDepths"); + // SSAO + createFrameBuffer("ssao", "C0:auxR"); + // SHADOW + createFrameBuffer("shadow", "C0:auxRGBA"); + // BLUR + createFrameBuffer("blur", "C0:ssao", "C1:shadow"); + // LIGHTING + createFrameBuffer("lighting", "C0:auxRGBA"); + // ANTI ALIASING + createFrameBuffer("antiAliasing", "C0:colors"); + // WEIGHTED SUM + createFrameBuffer("weightedSum", "C0:weightedColors", "C1:weightedVelocities", "C2:layerCounts", "D:depths"); + // TRANSPARENCY BLENDING + createFrameBuffer("transparencyBlending", "C0:colors", "C1:velocities"); + // MOTION BLUR + createFrameBuffer("motionBlur", "C0:auxRGBA"); + } + + private FrameBuffer createFrameBuffer(String name, String... textures) { + final FrameBuffer frameBuffer = glFactory.createFrameBuffer(); + for (String texture : textures) { + final String[] typeAndName = texture.split(":"); + frameBuffer.attach(decodeAttachment(typeAndName[0]), this.textures.get(typeAndName[1])); + } + frameBuffer.create(); + frameBuffers.put(name, frameBuffer); + return frameBuffer; + } + + private void initVertexArrays() { + // DEFERRED STAGE SCREEN + deferredStageScreenVertexArray = glFactory.createVertexArray(); + deferredStageScreenVertexArray.setData(MeshGenerator.generateTexturedPlane(null, new Vector2f(2, 2))); + deferredStageScreenVertexArray.create(); + } + + private void addDefaultObjects() { + addScreen(); + addFPSMonitor(); + } + + /** + * Destroys the renderer internal resources and the OpenGL context. + */ + public void dispose() { + disposeEffects(); + disposePrograms(); + disposeTextures(); + disposeMaterials(); + disposeFrameBuffers(); + disposeVertexArrays(); + disposeContext(); + fpsMonitorStarted = false; + } + + private void disposeContext() { + // CONTEXT + context.destroy(); + } + + private void disposeEffects() { + // SSAO + ssaoEffect.dispose(); + // SHADOW MAPPING + shadowMappingEffect.dispose(); + // BLUR + blurEffect.dispose(); + } + + private void disposePrograms() { + for (Program program : programs.values()) { + // SHADERS + for (Shader shader : program.getShaders()) { + shader.destroy(); + } + // PROGRAM + program.destroy(); + } + } + + private void disposeTextures() { + for (Texture texture : textures.values()) { + texture.destroy(); + } + } + + private void disposeMaterials() { + materials.clear(); + } + + private void disposeFrameBuffers() { + for (FrameBuffer frameBuffer : frameBuffers.values()) { + frameBuffer.destroy(); + } + } + + private void disposeVertexArrays() { + // DEFERRED STAGE SCREEN + deferredStageScreenVertexArray.destroy(); + } + + /** + * Sets the OpenGL version. Must be done before initializing the renderer. + * + * @param version The OpenGL version to use + */ + public void setGLVersion(GLVersion version) { + glVersion = version; + glFactory = GLImplementation.get(version); + } + + /** + * Returns the OpenGL version. + * + * @return The OpenGL version + */ + public GLVersion getGLVersion() { + return glVersion; + } + + /** + * Returns the OpenGL factory. + * + * @return The OpenGL factory + */ + public GLFactory getGLFactory() { + return glFactory; + } + + /** + * Sets whether or not to cull the back faces of the geometry. + * + * @param cull Whether or not to cull the back faces + */ + public void setCullBackFaces(boolean cull) { + cullBackFaces = cull; + } + + /** + * Sets the color of solid untextured objects. + * + * @param color The solid color + */ + public void setSolidColor(Color color) { + solidModelColor = color; + } + + /** + * Returns the renderer camera + * + * @return The camera + */ + public Camera getCamera() { + return modelCamera; + } + + /** + * Updates the light direction and camera bounds to ensure that shadows are casted inside the cuboid defined by size. + * + * @param direction The light direction + * @param position The light camera position + * @param size The size of the cuboid that must have shadows + */ + public void updateLight(Vector3f direction, Vector3f position, Vector3f size) { + // Set the direction uniform + direction = direction.normalize(); + lightDirectionUniform.set(direction); + // Set the camera position + lightCamera.setPosition(position); + // Calculate the camera rotation from the direction and set + final Quaternionf rotation = Quaternionf.fromRotationTo(Vector3f.FORWARD.negate(), direction); + lightCamera.setRotation(rotation); + // Calculate the transformation from the camera bounds rotation to the identity rotation (its axis aligned space) + final Matrix3f axisAlignTransform = Matrix3f.createRotation(rotation).invert(); + // Calculate the points of the box to completely include inside the camera bounds + size = size.div(2); + Vector3f p6 = size; + Vector3f p0 = p6.negate(); + Vector3f p7 = new Vector3f(-size.getX(), size.getY(), size.getZ()); + Vector3f p1 = p7.negate(); + Vector3f p4 = new Vector3f(-size.getX(), size.getY(), -size.getZ()); + Vector3f p2 = p4.negate(); + Vector3f p5 = new Vector3f(size.getX(), size.getY(), -size.getZ()); + Vector3f p3 = p5.negate(); + // Transform those points to the axis aligned space of the camera bounds + p0 = axisAlignTransform.transform(p0); + p1 = axisAlignTransform.transform(p1); + p2 = axisAlignTransform.transform(p2); + p3 = axisAlignTransform.transform(p3); + p4 = axisAlignTransform.transform(p4); + p5 = axisAlignTransform.transform(p5); + p6 = axisAlignTransform.transform(p6); + p7 = axisAlignTransform.transform(p7); + // Calculate the new camera bounds so that the box is fully included in those bounds + final Vector3f low = p0.min(p1).min(p2).min(p3) + .min(p4).min(p5).min(p6).min(p7); + final Vector3f high = p0.max(p1).max(p2).max(p3) + .max(p4).max(p5).max(p6).max(p7); + // Calculate the size of the new camera bounds + size = high.sub(low).div(2); + // Update the camera to the new bounds + lightCamera.setProjection(Matrix4f.createOrthographic(size.getX(), -size.getX(), size.getY(), -size.getY(), -size.getZ(), size.getZ())); + } + + /** + * Adds a model to be rendered as a solid. + * + * @param model The model + */ + public void addSolidModel(Model model) { + model.setMaterial(materials.get("solid")); + model.getUniforms().add(new ColorUniform("modelColor", solidModelColor)); + addModel(model); + } + + /** + * Adds a model to be rendered as partially transparent. + * + * @param model The transparent model + */ + public void addTransparentModel(Model model) { + model.setMaterial(materials.get("transparency")); + model.getUniforms().add(new Matrix4Uniform("previousModelMatrix", model.getMatrix())); + transparentModelList.add(model); + } + + /** + * Adds a model to the renderer. + * + * @param model The model to add + */ + public void addModel(Model model) { + model.getUniforms().add(new Matrix4Uniform("previousModelMatrix", model.getMatrix())); + modelRenderList.add(model); + } + + /** + * Removes a model from the renderer. + * + * @param model The model to remove + */ + public void removeModel(Model model) { + modelRenderList.remove(model); + } + + /** + * Removes all the models from the renderer. + */ + public void clearModels() { + modelRenderList.clear(); + } + + /** + * Returns the modifiable list of the models. Changes in this list are reflected in the renderer. + * + * @return The modifiable list of models + */ + public List getModels() { + return modelRenderList; + } + + private void addScreen() { + guiRenderList.add(new Model(deferredStageScreenVertexArray, materials.get("screen"))); + + // TEST CODE + final VertexArray vertexArray = glFactory.createVertexArray(); + vertexArray.setData(MeshGenerator.generateCone(null, 10, 10)); + vertexArray.create(); + final Model model = new Model(vertexArray, materials.get("transparency")); + model.getUniforms().add(new ColorUniform("modelColor", new Color(200, 10, 10, 200))); + model.setPosition(new Vector3f(0, 22, 0)); + addTransparentModel(model); + final Model model2 = model.getInstance(); + model2.getUniforms().add(new ColorUniform("modelColor", new Color(10, 10, 200, 120))); + model2.setPosition(new Vector3f(0, 22, 0)); + model2.setRotation(Quaternionf.fromAngleDegAxis(180, 1, 0, 0)); + addTransparentModel(model2); + } + + private void addFPSMonitor() { + final Font ubuntu; + try { + ubuntu = Font.createFont(Font.TRUETYPE_FONT, SpoutRenderer.class.getResourceAsStream("/fonts/ubuntu-r.ttf")); + } catch (FontFormatException | IOException e) { + e.printStackTrace(); + return; + } + final StringModel sandboxModel = new StringModel(glFactory, programs.get("font"), "ClientWIPFPS0123456789-: ", ubuntu.deriveFont(Font.PLAIN, 15), WINDOW_SIZE.getFloorX()); + final float aspect = 1 / ASPECT_RATIO; + sandboxModel.setPosition(new Vector3f(0.005, aspect / 2 + 0.315, -0.1)); + sandboxModel.setString("Client - WIP"); + guiRenderList.add(sandboxModel); + final StringModel fpsModel = sandboxModel.getInstance(); + fpsModel.setPosition(new Vector3f(0.005, aspect / 2 + 0.285, -0.1)); + fpsModel.setString("FPS: " + fpsMonitor.getTPS()); + guiRenderList.add(fpsModel); + fpsMonitorModel = fpsModel; + } + + /** + * Renders the models to the window. + */ + public void render() { + if (!fpsMonitorStarted) { + fpsMonitor.start(); + fpsMonitorStarted = true; + } + // UPDATE PER-FRAME UNIFORMS + inverseViewMatrixUniform.set(modelCamera.getViewMatrix().invert()); + lightViewMatrixUniform.set(lightCamera.getViewMatrix()); + lightProjectionMatrixUniform.set(lightCamera.getProjectionMatrix()); + blurStrengthUniform.set((float) fpsMonitor.getTPS() / SpoutScheduler.TARGET_FPS); + // RENDER + pipeline.run(context); + // UPDATE PREVIOUS FRAME UNIFORMS + setPreviousModelMatrices(); + previousViewMatrixUniform.set(modelCamera.getViewMatrix()); + previousProjectionMatrixUniform.set(modelCamera.getProjectionMatrix()); + // UPDATE FPS + updateFPSMonitor(); + } + + private void setPreviousModelMatrices() { + for (Model model : modelRenderList) { + model.getUniforms().getMatrix4("previousModelMatrix").set(model.getMatrix()); + } + for (Model model : transparentModelList) { + model.getUniforms().getMatrix4("previousModelMatrix").set(model.getMatrix()); + } + } + + private void updateFPSMonitor() { + fpsMonitor.update(); + fpsMonitorModel.setString("FPS: " + fpsMonitor.getTPS()); + } + + /** + * Saves a screenshot (PNG) to the directory where the program is currently running, with the current date as the file name. + * + * @param outputDir The directory in which to output the file + */ + public void saveScreenshot(File outputDir) { + final ByteBuffer buffer = context.readCurrentFrame(new Rectangle(Vector2f.ZERO, WINDOW_SIZE), Format.RGB); + final int width = context.getWindowWidth(); + final int height = context.getWindowHeight(); + final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + final byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + final int srcIndex = (x + y * width) * 3; + final int destIndex = (x + (height - y - 1) * width) * 3; + data[destIndex + 2] = buffer.get(srcIndex); + data[destIndex + 1] = buffer.get(srcIndex + 1); + data[destIndex] = buffer.get(srcIndex + 2); + } + } + try { + ImageIO.write(image, "PNG", new File(outputDir, SCREENSHOT_DATE_FORMAT.format(Calendar.getInstance().getTime()) + ".png")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private AttachmentPoint decodeAttachment(String s) { + final Matcher matcher = ATTACHMENT_PATTERN.matcher(s); + if (!matcher.find()) { + return null; + } + switch (matcher.group(1).toUpperCase()) { + case "C": + switch (Integer.parseInt(matcher.group(2))) { + case 0: + return AttachmentPoint.COLOR0; + case 1: + return AttachmentPoint.COLOR1; + case 2: + return AttachmentPoint.COLOR2; + case 3: + return AttachmentPoint.COLOR3; + case 4: + return AttachmentPoint.COLOR4; + } + return null; + case "D": + return AttachmentPoint.DEPTH; + case "S": + return AttachmentPoint.STENCIL; + case "DS": + return AttachmentPoint.DEPTH_STENCIL; + } + return null; + } + + @Override + public Vector2f getResolution() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public float getAspectRatio() { + throw new UnsupportedOperationException("Not supported yet."); + } + + private class DoDeferredStageAction extends RenderModelsAction { + private final FrameBuffer frameBuffer; + + private DoDeferredStageAction(String frameBuffer, VertexArray screen, String material) { + super(Arrays.asList(new Model(screen, materials.get(material)))); + this.frameBuffer = frameBuffers.get(frameBuffer); + } + + @Override + public void execute(Context context) { + frameBuffer.bind(); + super.execute(context); + } + } +} diff --git a/src/main/java/org/spout/engine/render/TPSMonitor.java b/src/main/java/org/spout/engine/render/TPSMonitor.java new file mode 100644 index 0000000..18eae74 --- /dev/null +++ b/src/main/java/org/spout/engine/render/TPSMonitor.java @@ -0,0 +1,65 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.render; + +/** + * A basic TPS monitor. + */ +public class TPSMonitor { + private long lastUpdateTime; + private long elapsedTime = 0; + private int frameCount = 0; + private int tps; + + /** + * Starts the TPS monitor. + */ + public void start() { + lastUpdateTime = System.currentTimeMillis(); + } + + /** + * Updates the TPS. + */ + public void update() { + final long time = System.currentTimeMillis(); + elapsedTime += time - lastUpdateTime; + lastUpdateTime = time; + frameCount++; + if (elapsedTime >= 1000) { + tps = frameCount; + frameCount = 0; + elapsedTime = 0; + } + } + + /** + * Returns the TPS. + * + * @return The TPS + */ + public int getTPS() { + return tps; + } +} diff --git a/src/main/java/org/spout/engine/render/effect/BlurEffect.java b/src/main/java/org/spout/engine/render/effect/BlurEffect.java new file mode 100644 index 0000000..6ced2b2 --- /dev/null +++ b/src/main/java/org/spout/engine/render/effect/BlurEffect.java @@ -0,0 +1,49 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.render.effect; + +import org.spout.math.vector.Vector2f; +import org.spout.renderer.data.Uniform; +import org.spout.renderer.data.Uniform.IntUniform; +import org.spout.renderer.data.Uniform.Vector2Uniform; + +public class BlurEffect { + private final int blurSize; + private final Vector2f texelSize; + + public BlurEffect(Vector2f resolution, int blurSize) { + this.blurSize = blurSize; + this.texelSize = Vector2f.ONE.div(resolution); + } + + public void dispose() { + } + + public Uniform[] getUniforms() { + return new Uniform[]{ + new IntUniform("blurSize", blurSize), + new Vector2Uniform("texelSize", texelSize) + }; + } +} diff --git a/src/main/java/org/spout/engine/render/effect/SSAOEffect.java b/src/main/java/org/spout/engine/render/effect/SSAOEffect.java new file mode 100644 index 0000000..616eb5d --- /dev/null +++ b/src/main/java/org/spout/engine/render/effect/SSAOEffect.java @@ -0,0 +1,106 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.render.effect; + +import java.nio.ByteBuffer; +import java.util.Random; + +import org.spout.math.GenericMath; +import org.spout.math.vector.Vector2f; +import org.spout.math.vector.Vector3f; +import org.spout.renderer.data.Uniform; +import org.spout.renderer.data.Uniform.FloatUniform; +import org.spout.renderer.data.Uniform.IntUniform; +import org.spout.renderer.data.Uniform.Vector2Uniform; +import org.spout.renderer.data.Uniform.Vector3ArrayUniform; +import org.spout.renderer.gl.GLFactory; +import org.spout.renderer.gl.Texture; +import org.spout.renderer.gl.Texture.Format; +import org.spout.renderer.gl.Texture.InternalFormat; +import org.spout.renderer.util.CausticUtil; + +public class SSAOEffect { + private final int kernelSize; + private final Vector3f[] kernel; + private final float radius; + private final float threshold; + private final Vector2f noiseScale; + private final Texture noiseTexture; + private final float power; + + public SSAOEffect(GLFactory glFactory, Vector2f resolution, int kernelSize, int noiseSize, float radius, float threshold, float power) { + this.kernelSize = kernelSize; + this.kernel = new Vector3f[kernelSize]; + this.radius = radius; + this.threshold = threshold; + this.noiseScale = resolution.div(noiseSize); + this.noiseTexture = glFactory.createTexture(); + this.power = power; + // Generate the kernel + final Random random = new Random(); + for (int i = 0; i < kernelSize; i++) { + float scale = (float) i / kernelSize; + scale = GenericMath.lerp(threshold, 1, scale * scale); + // Create a set of random unit vectors inside a hemisphere + // The vectors are scaled so that the amount falls of as we get further away from the center + kernel[i] = new Vector3f(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, random.nextFloat()).normalize().mul(scale); + } + // Generate the noise texture + final int noiseTextureSize = noiseSize * noiseSize; + final ByteBuffer noiseTextureBuffer = CausticUtil.createByteBuffer(noiseTextureSize * 3); + for (int i = 0; i < noiseTextureSize; i++) { + // Random unit vectors around the z axis + Vector3f noise = new Vector3f(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1, 0).normalize(); + // Encode to unsigned byte, and place in buffer + noise = noise.mul(128).add(128, 128, 128); + noiseTextureBuffer.put((byte) (noise.getFloorX() & 0xff)); + noiseTextureBuffer.put((byte) (noise.getFloorY() & 0xff)); + noiseTextureBuffer.put((byte) (noise.getFloorZ() & 0xff)); + } + noiseTexture.setFormat(Format.RGB); + noiseTexture.setInternalFormat(InternalFormat.RGB8); + noiseTextureBuffer.flip(); + noiseTexture.setImageData(noiseTextureBuffer, noiseSize, noiseSize); + noiseTexture.create(); + } + + public void dispose() { + noiseTexture.destroy(); + } + + public Texture getNoiseTexture() { + return noiseTexture; + } + + public Uniform[] getUniforms() { + return new Uniform[]{ + new IntUniform("kernelSize", kernelSize), + new Vector3ArrayUniform("kernel", kernel), + new FloatUniform("radius", radius), + new FloatUniform("threshold", threshold), + new Vector2Uniform("noiseScale", noiseScale), + new FloatUniform("power", power) + }; + } +} diff --git a/src/main/java/org/spout/engine/render/effect/ShadowMappingEffect.java b/src/main/java/org/spout/engine/render/effect/ShadowMappingEffect.java new file mode 100644 index 0000000..da04115 --- /dev/null +++ b/src/main/java/org/spout/engine/render/effect/ShadowMappingEffect.java @@ -0,0 +1,98 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.render.effect; + +import java.nio.ByteBuffer; +import java.util.Random; + +import org.spout.math.vector.Vector2f; +import org.spout.renderer.data.Uniform; +import org.spout.renderer.data.Uniform.FloatUniform; +import org.spout.renderer.data.Uniform.IntUniform; +import org.spout.renderer.data.Uniform.Vector2ArrayUniform; +import org.spout.renderer.data.Uniform.Vector2Uniform; +import org.spout.renderer.gl.GLFactory; +import org.spout.renderer.gl.Texture; +import org.spout.renderer.gl.Texture.Format; +import org.spout.renderer.gl.Texture.InternalFormat; +import org.spout.renderer.util.CausticUtil; + +public class ShadowMappingEffect { + private final int kernelSize; + private final Vector2f[] kernel; + private final Vector2f noiseScale; + private final Texture noiseTexture; + private final float bias; + private final float radius; + + public ShadowMappingEffect(GLFactory glFactory, Vector2f resolution, int kernelSize, int noiseSize, float bias, float radius) { + this.kernelSize = kernelSize; + this.kernel = new Vector2f[kernelSize]; + this.noiseScale = resolution.div(noiseSize); + this.noiseTexture = glFactory.createTexture(); + this.bias = bias; + this.radius = radius; + // Generate the kernel + final Random random = new Random(); + for (int i = 0; i < kernelSize; i++) { + // Create a set of random unit vectors + kernel[i] = new Vector2f(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1).normalize(); + } + // Generate the noise texture + final int noiseTextureSize = noiseSize * noiseSize; + final ByteBuffer noiseTextureBuffer = CausticUtil.createByteBuffer(noiseTextureSize * 3); + for (int i = 0; i < noiseTextureSize; i++) { + // Random unit vectors around the z axis + Vector2f noise = new Vector2f(random.nextFloat() * 2 - 1, random.nextFloat() * 2 - 1).normalize(); + // Encode to unsigned byte, and place in buffer + noise = noise.mul(128).add(128, 128); + noiseTextureBuffer.put((byte) (noise.getFloorX() & 0xff)); + noiseTextureBuffer.put((byte) (noise.getFloorY() & 0xff)); + noiseTextureBuffer.put((byte) 0); + } + noiseTexture.setFormat(Format.RGB); + noiseTexture.setInternalFormat(InternalFormat.RGB8); + noiseTextureBuffer.flip(); + noiseTexture.setImageData(noiseTextureBuffer, noiseSize, noiseSize); + noiseTexture.create(); + } + + public void dispose() { + noiseTexture.destroy(); + } + + public Texture getNoiseTexture() { + return noiseTexture; + } + + public Uniform[] getUniforms() { + return new Uniform[]{ + new IntUniform("kernelSize", kernelSize), + new Vector2ArrayUniform("kernel", kernel), + new Vector2Uniform("noiseScale", noiseScale), + new FloatUniform("bias", bias), + new FloatUniform("radius", radius) + }; + } +} diff --git a/src/main/java/org/spout/engine/scheduler/MainThread.java b/src/main/java/org/spout/engine/scheduler/MainThread.java new file mode 100644 index 0000000..0acc821 --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/MainThread.java @@ -0,0 +1,271 @@ +package org.spout.engine.scheduler; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import com.flowpowered.commons.Named; + +import org.spout.api.Spout; +import org.spout.api.scheduler.TickStage; +import org.spout.api.scheduler.Worker; +import org.spout.api.util.concurrent.ConcurrentList; +import org.spout.engine.util.thread.AsyncManager; +import org.spout.engine.util.thread.coretasks.CopySnapshotTask; +import org.spout.engine.util.thread.coretasks.DynamicUpdatesTask; +import org.spout.engine.util.thread.coretasks.FinalizeTask; +import org.spout.engine.util.thread.coretasks.LightingTask; +import org.spout.engine.util.thread.coretasks.ManagerRunnableFactory; +import org.spout.engine.util.thread.coretasks.PhysicsTask; +import org.spout.engine.util.thread.coretasks.PreSnapshotTask; +import org.spout.engine.util.thread.coretasks.StartTickTask; + +public class MainThread extends SchedulerElement { + private final SpoutScheduler scheduler; + // Scheduler tasks + private final StartTickTask[] startTickTask = new StartTickTask[] {new StartTickTask(0), new StartTickTask(1), new StartTickTask(2)}; + private final DynamicUpdatesTask dynamicUpdatesTask = new DynamicUpdatesTask(); + private final PhysicsTask physicsTask = new PhysicsTask(); + private final LightingTask lightingTask = new LightingTask(); + private final FinalizeTask finalizeTask = new FinalizeTask(); + private final PreSnapshotTask preSnapshotTask = new PreSnapshotTask(); + private final CopySnapshotTask copySnapshotTask = new CopySnapshotTask(); + + /** + * Update count for physics and dynamic updates + */ + private final AtomicInteger updates = new AtomicInteger(0); + /** + * The threshold before physics and dynamic updates are aborted + */ + private final static int UPDATE_THRESHOLD = 100000; + /** + * A list of all AsyncManagers + */ + private final List asyncManagers = new ConcurrentList<>(); + // scheduler executor service + private final ExecutorService executorService; + + public MainThread(SpoutScheduler scheduler) { + super("MainThread", 20); + this.scheduler = scheduler; + executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 + 1, new MarkedNamedThreadFactory("SpoutScheduler - async manager executor service", true)); + } + + @Override + public void onStart() { + } + + @Override + public void onStop() { + doCopySnapshot(asyncManagers); + + scheduler.getTaskManager().heartbeat(SpoutScheduler.PULSE_EVERY << 2); + scheduler.getTaskManager().shutdown(1L); + + long delay = 2000; + while (!scheduler.getTaskManager().waitForAsyncTasks(delay)) { + List workers = scheduler.getTaskManager().getActiveWorkers(); + if (workers.isEmpty()) { + break; + } + Spout.info("Unable to shutdown due to async tasks still running"); + for (Worker w : workers) { + Object owner = w.getOwner() instanceof Named ? ((Named) w.getOwner()).getName() : w.getOwner(); + Spout.info("Task with id of " + w.getTaskId() + " owned by " + owner + " is still running"); + } + if (delay < 8000) { + delay <<= 1; + } + } + } + + // TODO: config + private final boolean DYNAMIC_UPDATES = true; + private final boolean BLOCK_PHYSICS = true; + private final boolean LIGHTING = true; + + @Override + public void onTick(long delta) { + // Delta is in nanos, we want millis with rounding + delta = Math.round(delta * 1e-6d); + + TickStage.setStage(TickStage.TICKSTART); + + scheduler.getTaskManager().heartbeat(delta); + + List managers = new ArrayList<>(asyncManagers); + + TickStage.setStage(TickStage.STAGE1); + + for (int stage = 0; stage < this.startTickTask.length; stage++) { + if (stage == 0) { + TickStage.setStage(TickStage.STAGE1); + } else { + TickStage.setStage(TickStage.STAGE2P); + } + + startTickTask[stage].setDelta(delta); + + int tickStage = stage == 0 ? TickStage.STAGE1 : TickStage.STAGE2P; + + runTasks(managers, startTickTask[stage], "Stage " + stage, tickStage); + } + + int totalUpdates = -1; + int lightUpdates = 0; + int dynamicUpdates = 0; + int physicsUpdates = 0; + updates.set(0); + int uD = 1; + int uP = 1; + while ((uD + uP) > 0 && totalUpdates < UPDATE_THRESHOLD) { + if (DYNAMIC_UPDATES) { + doDynamicUpdates(managers); + } + + uD = updates.getAndSet(0); + totalUpdates += uD; + dynamicUpdates += uD; + + if (BLOCK_PHYSICS) { + doPhysics(managers); + } + + uP = updates.getAndSet(0); + totalUpdates += uP; + physicsUpdates += uP; + } + + if (LIGHTING || !Spout.debugMode()) { + doLighting(managers); + } + + if (totalUpdates >= UPDATE_THRESHOLD) { + Spout.warn("Block updates per tick of " + totalUpdates + " exceeded the threshold " + UPDATE_THRESHOLD + "; " + dynamicUpdates + " dynamic updates, " + physicsUpdates + " block physics updates and " + lightUpdates + " lighting updates"); + } + + doFinalizeTick(managers); + + doCopySnapshot(managers); + } + + + private void doPhysics(List managers) { + int passStartUpdates = updates.get() - 1; + int startUpdates = updates.get(); + while (passStartUpdates < updates.get() && updates.get() < startUpdates + UPDATE_THRESHOLD) { + passStartUpdates = updates.get(); + this.runTasks(managers, physicsTask, "Physics", TickStage.GLOBAL_PHYSICS, TickStage.PHYSICS); + } + } + + private void doDynamicUpdates(List managers) { + int passStartUpdates = updates.get() - 1; + int startUpdates = updates.get(); + + TickStage.setStage(TickStage.GLOBAL_DYNAMIC_BLOCKS); + + long earliestTime = SpoutScheduler.END_OF_THE_WORLD; + + for (AsyncManager e : managers) { + long firstTime = e.getFirstDynamicUpdateTime(); + if (firstTime < earliestTime) { + earliestTime = firstTime; + } + } + + while (passStartUpdates < updates.get() && updates.get() < startUpdates + UPDATE_THRESHOLD) { + passStartUpdates = updates.get(); + + long threshold = earliestTime + SpoutScheduler.PULSE_EVERY - 1; + + dynamicUpdatesTask.setThreshold(threshold); + + this.runTasks(managers, dynamicUpdatesTask, "Dynamic Blocks", TickStage.GLOBAL_DYNAMIC_BLOCKS, TickStage.DYNAMIC_BLOCKS); + } + } + + private void doLighting(List managers) { + this.runTasks(managers, lightingTask, "Lighting", TickStage.LIGHTING); + } + + + private void doFinalizeTick(List managers) { + this.runTasks(managers, finalizeTask, "Finalize", TickStage.FINALIZE); + } + + private void doCopySnapshot(List managers) { + this.runTasks(managers, preSnapshotTask, "Pre-snapshot", TickStage.PRESNAPSHOT); + + this.runTasks(managers, copySnapshotTask, "Copy-snapshot", TickStage.SNAPSHOT); + } + + private void runTasks(List managers, ManagerRunnableFactory taskFactory, String stageString, int tickStage) { + runTasks(managers, taskFactory, stageString, tickStage, tickStage); + } + + private void runTasks(List managers, ManagerRunnableFactory taskFactory, String stageString, int globalStage, int localStage) { + int maxSequence = taskFactory.getMaxSequence(); + for (int s = taskFactory.getMinSequence(); s <= maxSequence; s++) { + if (s == -1) { + TickStage.setStage(localStage); + } else { + TickStage.setStage(globalStage); + } + List> futures = new ArrayList<>(managers.size()); + for (AsyncManager manager : managers) { + if (s == -1 || s == manager.getSequence()) { + Runnable r = taskFactory.getTask(manager, s); + if (r != null) { + futures.add(executorService.submit(r)); + } + } + } + forLoop: + for (int i = 0; i < futures.size(); i++) { + boolean done = false; + while (!done) { + try { + Future f = futures.get(i); + if (!f.isDone()) { + f.get(SpoutScheduler.PULSE_EVERY, TimeUnit.MILLISECONDS); + } + done = true; + } catch (InterruptedException e) { + Spout.info("Warning: main thread interrupted while waiting on tick stage task, " + taskFactory.getClass().getName()); + break forLoop; + } catch (ExecutionException e) { + Spout.info("Exception thrown when executing task, " + taskFactory.getClass().getName() + ", " + e.getMessage()); + e.printStackTrace(); + Spout.info("Caused by"); + e.getCause().printStackTrace(); + done = true; + } catch (TimeoutException e) { + } + } + } + } + } + + /** + * Adds an async manager to the scheduler + */ + public boolean addAsyncManager(AsyncManager manager) { + return asyncManagers.add(manager); + } + + /** + * Removes an async manager from the scheduler + */ + public boolean removeAsyncManager(AsyncManager manager) { + return asyncManagers.remove(manager); + } + +} diff --git a/src/main/java/org/spout/engine/scheduler/MarkedNamedThreadFactory.java b/src/main/java/org/spout/engine/scheduler/MarkedNamedThreadFactory.java new file mode 100644 index 0000000..9198df2 --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/MarkedNamedThreadFactory.java @@ -0,0 +1,22 @@ +package org.spout.engine.scheduler; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +public class MarkedNamedThreadFactory implements ThreadFactory { + private final AtomicInteger idCounter = new AtomicInteger(); + private final String namePrefix; + private final boolean daemon; + + public MarkedNamedThreadFactory(String namePrefix, boolean daemon) { + this.namePrefix = namePrefix; + this.daemon = daemon; + } + + @Override + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, "Executor{" + namePrefix + "-" + idCounter.getAndIncrement() + "}"); + thread.setDaemon(daemon); + return thread; + } +} diff --git a/src/main/java/org/spout/engine/scheduler/RenderThread.java b/src/main/java/org/spout/engine/scheduler/RenderThread.java new file mode 100644 index 0000000..0448cbc --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/RenderThread.java @@ -0,0 +1,39 @@ +package org.spout.engine.scheduler; + +import org.lwjgl.opengl.Display; +import org.spout.engine.render.SpoutRenderer; +import org.spout.renderer.GLVersioned; + +public class RenderThread extends SchedulerElement { + private final SpoutScheduler scheduler; + private final SpoutRenderer renderer; + + public RenderThread(SpoutScheduler scheduler) { + super("RenderThread", 60); + this.scheduler = scheduler; + this.renderer = new SpoutRenderer(); + } + + @Override + public void onStart() { + renderer.setGLVersion(GLVersioned.GLVersion.GL30); + renderer.init(); + } + + @Override + public void onStop() { + renderer.dispose(); + } + + @Override + public void onTick(long dt) { + if (Display.isCloseRequested()) { + scheduler.stop(); + } + renderer.render(); + } + + public SpoutRenderer getRenderer() { + return renderer; + } +} diff --git a/src/main/java/org/spout/engine/scheduler/SchedulerElement.java b/src/main/java/org/spout/engine/scheduler/SchedulerElement.java new file mode 100644 index 0000000..11141bf --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/SchedulerElement.java @@ -0,0 +1,63 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.scheduler; + +/** + * Represents an element that ticks at a specific TPS internally + */ +public abstract class SchedulerElement { + private final String name; + private final int tps; + private TPSLimitedThread thread; + + public SchedulerElement(String name, int tps) { + this.name = name; + this.tps = tps; + } + + public final void start() { + thread = new TPSLimitedThread(name, this, tps); + thread.start(); + } + + public final void stop() { + if (thread != null) { + thread.terminate(); + thread = null; + } + } + + public final boolean isRunning() { + return thread != null && thread.isRunning(); + } + + public abstract void onStart(); + + /** + * @param dt delta in nanoseconds + */ + public abstract void onTick(long dt); + + public abstract void onStop(); +} diff --git a/src/main/java/org/spout/engine/scheduler/SpoutScheduler.java b/src/main/java/org/spout/engine/scheduler/SpoutScheduler.java new file mode 100644 index 0000000..a9fbfba --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/SpoutScheduler.java @@ -0,0 +1,221 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.scheduler; + +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; + +import org.spout.api.Engine; +import org.spout.api.scheduler.Scheduler; +import org.spout.api.scheduler.Task; +import org.spout.api.scheduler.TaskPriority; +import org.spout.api.scheduler.Worker; +import org.spout.engine.util.thread.AsyncManager; + +/** + * A class which handles scheduling for the engine {@link SpoutTask}s.

Tasks can be submitted to the scheduler for execution by the main thread. These tasks are executed during a period where + * none of the auxiliary threads are executing.

Each tick consists of a number of stages. Each stage is executed in parallel, but the next stage is not started until all threads have + * completed the previous stage.

Except for executing queued serial tasks, all threads are run in parallel. The full sequence is as follows:

  • Single Thread
    • Execute + * queued tasks
      Tasks that are submitted for execution are executed one at a time.
  • Parallel Threads
    • Stage 1
      This is the first stage of execution. Most Events are + * generated during this stage and the API is fully open for use. - chunks are populated.
    • Stage 2
      During this stage, entity collisions are handled.
    • Finalize Tick
      During + * this stage - entities are moved between entity managers. - chunks are compressed if necessary.
    • Pre-snapshot
      This is a MONITOR stage, data is stable and no modifications are allowed. + *
    • Copy Snapshot
      During this stage all live values are copied to their stable snapshot. Data is unstable so no reads are permitted during this stage.
+ */ +public final class SpoutScheduler implements Scheduler { + /** + * The number of milliseconds between pulses. + */ + public static final int PULSE_EVERY = 50; + /** + * A time that is at least 1 Pulse below the maximum time instant + */ + public static final long END_OF_THE_WORLD = Long.MAX_VALUE - PULSE_EVERY; + /** + * Target Frames per Second for the renderer + */ + public static final int TARGET_FPS = 60; + private final SpoutTaskManager taskManager; + // SchedulerElements + private final MainThread mainThread; + private final RenderThread renderThread; + + /** + * Creates a new task scheduler. + */ + public SpoutScheduler(Engine engine) { + mainThread = new MainThread(this); + + if (engine.getPlatform().isClient()) { + renderThread = new RenderThread(this); + } else { + renderThread = null; + } + taskManager = new SpoutTaskManager(this); + } + + public void startMainThread() { + if (mainThread.isRunning()) { + throw new IllegalStateException("Attempt was made to start the main thread twice"); + } + + mainThread.start(); + } + + public void startRenderThread() { + if (renderThread.isRunning()) { + throw new IllegalStateException("Attempt was made to start the render thread twice"); + } + renderThread.start(); + } + + /** + * Stops the scheduler + */ + public void stop() { + mainThread.stop(); + if (renderThread != null) { + renderThread.stop(); + } + } + + @Override + public Task scheduleSyncDelayedTask(Object plugin, Runnable task) { + return taskManager.scheduleSyncDelayedTask(plugin, task); + } + + @Override + public Task scheduleSyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority) { + return taskManager.scheduleSyncDelayedTask(plugin, task, delay, priority); + } + + @Override + public Task scheduleSyncDelayedTask(Object plugin, Runnable task, TaskPriority priority) { + return taskManager.scheduleSyncDelayedTask(plugin, task, priority); + } + + @Override + public Task scheduleSyncRepeatingTask(Object plugin, Runnable task, long delay, long period, TaskPriority priority) { + return taskManager.scheduleSyncRepeatingTask(plugin, task, delay, period, priority); + } + + @Override + public Task scheduleAsyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority) { + return taskManager.scheduleAsyncDelayedTask(plugin, task, delay, priority); + } + + @Override + public Task scheduleAsyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority, boolean longLife) { + return taskManager.scheduleAsyncDelayedTask(plugin, task, delay, priority, longLife); + } + + @Override + public Task scheduleAsyncTask(Object plugin, Runnable task) { + return taskManager.scheduleAsyncTask(plugin, task); + } + + @Override + public Task scheduleAsyncTask(Object plugin, Runnable task, boolean longLife) { + return taskManager.scheduleAsyncTask(plugin, task, longLife); + } + + @Override + public Future callSyncMethod(Object plugin, Callable task, TaskPriority priority) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public boolean isQueued(int taskId) { + return taskManager.isQueued(taskId); + } + + @Override + public void cancelTask(int taskId) { + taskManager.cancelTask(taskId); + } + + @Override + public void cancelTask(Task task) { + taskManager.cancelTask(task); + } + + @Override + public void cancelTasks(Object plugin) { + taskManager.cancelTasks(plugin); + } + + @Override + public void cancelAllTasks() { + taskManager.cancelAllTasks(); + } + + @Override + public List getActiveWorkers() { + return taskManager.getActiveWorkers(); + } + + @Override + public List getPendingTasks() { + return taskManager.getPendingTasks(); + } + + @Override + public long getUpTime() { + return taskManager.getUpTime(); + } + + public SpoutTaskManager getTaskManager() { + return taskManager; + } + + @Override + public boolean isServerOverloaded() { + return false; + } + + public MainThread getMainThread() { + return mainThread; + } + + public RenderThread getRenderThread() { + return renderThread; + } + + /** + * Adds an async manager to the scheduler + */ + public boolean addAsyncManager(AsyncManager manager) { + return mainThread.addAsyncManager(manager); + } + + /** + * Removes an async manager from the scheduler + */ + public boolean removeAsyncManager(AsyncManager manager) { + return mainThread.removeAsyncManager(manager); + } +} diff --git a/src/main/java/org/spout/engine/scheduler/SpoutTask.java b/src/main/java/org/spout/engine/scheduler/SpoutTask.java new file mode 100644 index 0000000..cc5b4a1 --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/SpoutTask.java @@ -0,0 +1,344 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.scheduler; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import com.flowpowered.commons.Named; +import org.spout.api.scheduler.Scheduler; +import org.spout.api.scheduler.Task; +import org.spout.api.scheduler.TaskManager; +import org.spout.api.scheduler.TaskPriority; +import org.spout.api.util.concurrent.LongPrioritized; + +/** + * Represents a task which is executed periodically. + */ +public class SpoutTask implements Task, LongPrioritized { + /** + * The next task ID pending. + */ + private final static AtomicInteger nextTaskId = new AtomicInteger(0); + /** + * The ID of this task. + */ + private final int taskId; + /** + * The task priority + */ + private final TaskPriority priority; + /** + * The Runnable this task is representing. + */ + private final Runnable task; + /** + * The Plugin that owns this task + */ + private final Object owner; + /** + * The number of ticks before the call to the Runnable. + */ + private final long delay; + /** + * The number of ticks between each call to the Runnable. + */ + private final long period; + /** + * Indicates if the task is a synchronous task or an async task + */ + private final boolean sync; + /** + * Indicates the next scheduled time for the task to be called + */ + private final AtomicLong nextCallTime; + private final AtomicReference queueState = new AtomicReference<>(QueueState.UNQUEUED); + /** + * A flag indicating if the task is actually executing + */ + private final AtomicBoolean executing; + /** + * Indicates if the task is being deferred and when it started + */ + private long deferBegin = -1; + /** + * The manager associated with this task + */ + private final TaskManager manager; + /** + * The scheduler for the engine + */ + private final Scheduler scheduler; + /** + * Indicates that the task is long lived + */ + private final boolean longLife; + + /** + * Creates a new task with the specified period between consecutive calls to {@link #pulse()}. + */ + public SpoutTask(TaskManager manager, Scheduler scheduler, Object owner, Runnable task, boolean sync, long delay, long period, TaskPriority priority, boolean longLife) { + this.taskId = nextTaskId.getAndIncrement(); + this.nextCallTime = new AtomicLong(manager.getUpTime() + delay); + this.executing = new AtomicBoolean(false); + this.owner = owner; + this.task = task; + this.delay = delay; + this.period = period; + this.sync = sync; + this.priority = priority; + this.manager = manager; + this.scheduler = scheduler; + this.longLife = longLife; + } + + /** + * Gets the ID of this task. + */ + @Override + public int getTaskId() { + return taskId; + } + + @Override + public boolean isSync() { + return sync; + } + + @Override + public boolean isExecuting() { + return executing.get(); + } + + @Override + public Object getOwner() { + return owner; + } + + @Override + public boolean isAlive() { + return !queueState.get().isDead(); + } + + @Override + public boolean isLongLived() { + return longLife; + } + + @Override + public void cancel() { + manager.cancelTask(taskId); + } + + public long getNextCallTime() { + return nextCallTime.get(); + } + + protected long getPeriod() { + return this.period; + } + + protected long getDelay() { + return this.delay; + } + + /** + * Stops this task. + */ + public void stop() { + remove(); + } + + /** + * Executes the task. The task will fail to execute if it is no longer running, if it is called early, or if it is already executing. + * + * @return The task successfully executed. + */ + boolean pulse() { + if (queueState.get().isDead()) { + return false; + } + + if (scheduler.isServerOverloaded()) { + if (attemptDefer()) { + updateCallTime(SpoutScheduler.PULSE_EVERY); + return false; + } + } + + if (!executing.compareAndSet(false, true)) { + return false; + } + + try { + task.run(); + + updateCallTime(); + + if (period <= 0) { + queueState.set(QueueState.DEAD); + } + } finally { + executing.set(false); + } + + return true; + } + + public void remove() { + queueState.set(QueueState.DEAD); + } + + public boolean setQueued() { + if (!queueState.compareAndSet(QueueState.UNQUEUED, QueueState.QUEUED)) { + boolean success = false; + while (!success) { + QueueState oldState = queueState.get(); + switch (oldState) { + case DEAD: + return false; + case QUEUED: + throw new IllegalStateException("Task added in the queue twice without being removed"); + case UNQUEUED: + success = queueState.compareAndSet(QueueState.UNQUEUED, QueueState.QUEUED); + break; + default: + throw new IllegalStateException("Unknown queue state " + oldState); + } + } + } + return true; + } + + public boolean setUnqueued() { + if (!queueState.compareAndSet(QueueState.QUEUED, QueueState.UNQUEUED)) { + boolean success = false; + while (!success) { + QueueState oldState = queueState.get(); + switch (oldState) { + case DEAD: + return false; + case UNQUEUED: + throw new IllegalStateException("Task set as unqueued before being set as queued"); + case QUEUED: + success = queueState.compareAndSet(QueueState.QUEUED, QueueState.UNQUEUED); + break; + default: + throw new IllegalStateException("Unknown queue state " + oldState); + } + } + } + return true; + } + + @Override + public String toString() { + Object owner = getOwner(); + String ownerName = owner == null || !(owner instanceof Named) ? "null" : ((Named) owner).getName(); + return this.getClass().getSimpleName() + "{" + getTaskId() + ", " + ownerName + "}"; + } + + @Override + public int hashCode() { + return taskId; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof SpoutTask) { + SpoutTask other = (SpoutTask) o; + return other.taskId == taskId; + } else { + return false; + } + } + + private boolean attemptDefer() { + if (priority.getMaxDeferred() <= 0) { + return false; + } + if (deferBegin < 0) { + deferBegin = manager.getUpTime(); + return true; + } + + if (manager.getUpTime() - deferBegin > priority.getMaxDeferred()) { + deferBegin = -1; + return false; + } + + return true; + } + + private void updateCallTime() { + updateCallTime(period); + } + + private boolean updateCallTime(long offset) { + boolean success = setQueued(); + if (!success) { + return false; + } + try { + long now = manager.getUpTime(); + if (nextCallTime.addAndGet(offset) <= now) { + nextCallTime.set(now + 1); + } + } finally { + setUnqueued(); + } + return true; + } + + @Override + public long getPriority() { + return nextCallTime.get(); + } + + @SuppressWarnings ("unused") + private static enum QueueState { + QUEUED, + UNQUEUED, + DEAD; + + public boolean isDead() { + return this == DEAD; + } + + public boolean isQueued() { + return this == QUEUED; + } + + public boolean isUnQueued() { + return this == UNQUEUED; + } + } +} diff --git a/src/main/java/org/spout/engine/scheduler/SpoutTaskManager.java b/src/main/java/org/spout/engine/scheduler/SpoutTaskManager.java new file mode 100644 index 0000000..23f8cb5 --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/SpoutTaskManager.java @@ -0,0 +1,302 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.scheduler; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.spout.api.Spout; +import org.spout.api.scheduler.Scheduler; +import org.spout.api.scheduler.Task; +import org.spout.api.scheduler.TaskManager; +import org.spout.api.scheduler.TaskPriority; +import org.spout.api.scheduler.Worker; +import org.spout.engine.util.thread.AsyncManager; + +public class SpoutTaskManager implements TaskManager { + private final ConcurrentHashMap activeWorkers = new ConcurrentHashMap<>(); + private final ConcurrentHashMap activeTasks = new ConcurrentHashMap<>(); + private final TaskPriorityQueue taskQueue; + private final AtomicBoolean alive; + private final AtomicLong upTime; + private final Object scheduleLock = new Object(); + private final Scheduler scheduler; + private final ExecutorService pool = Executors.newFixedThreadPool(20, new MarkedNamedThreadFactory("Scheduler Thread Pool Thread", false)); + + public SpoutTaskManager(Scheduler scheduler) { + this(scheduler, null, 0L); + } + + public SpoutTaskManager(Scheduler scheduler, AsyncManager manager) { + this(scheduler, manager, 0L); + } + + public SpoutTaskManager(Scheduler scheduler, AsyncManager manager, long age) { + this.taskQueue = new TaskPriorityQueue(manager, SpoutScheduler.PULSE_EVERY / 4); + this.alive = new AtomicBoolean(true); + this.upTime = new AtomicLong(age); + this.scheduler = scheduler; + } + + @Override + public Task scheduleSyncDelayedTask(Object plugin, Runnable task) { + return scheduleSyncDelayedTask(plugin, task, 0, TaskPriority.CRITICAL); + } + + @Override + public Task scheduleSyncDelayedTask(Object plugin, Runnable task, TaskPriority priority) { + return scheduleSyncDelayedTask(plugin, task, 0, priority); + } + + @Override + public Task scheduleSyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority) { + return scheduleSyncRepeatingTask(plugin, task, delay, -1, priority); + } + + @Override + public Task scheduleSyncRepeatingTask(Object plugin, Runnable task, long delay, long period, TaskPriority priority) { + return schedule(new SpoutTask(this, scheduler, plugin, task, true, delay, period, priority, false)); + } + + @Override + public Task scheduleAsyncTask(Object plugin, Runnable task) { + return scheduleAsyncTask(plugin, task, false); + } + + @Override + public Task scheduleAsyncTask(Object plugin, Runnable task, boolean longLife) { + return scheduleAsyncDelayedTask(plugin, task, 0, TaskPriority.CRITICAL, longLife); + } + + @Override + public Task scheduleAsyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority) { + return scheduleAsyncDelayedTask(plugin, task, delay, priority, true); + } + + @Override + public Task scheduleAsyncDelayedTask(Object plugin, Runnable task, long delay, TaskPriority priority, boolean longLife) { + if (!alive.get()) { + return null; + } else { + return schedule(new SpoutTask(this, scheduler, plugin, task, false, delay, -1, priority, longLife)); + } + } + + @Override + public Future callSyncMethod(Object plugin, Callable task, TaskPriority priority) { + throw new UnsupportedOperationException("Not supported yet."); + } + + public void heartbeat(long delta) { + long upTime = this.upTime.addAndGet(delta); + + Queue q; + + while ((q = taskQueue.poll(upTime)) != null) { + boolean checkRequired = !taskQueue.isFullyBelowThreshold(q, upTime); + Iterator itr = q.iterator(); + while (itr.hasNext()) { + SpoutTask currentTask = itr.next(); + if (checkRequired && currentTask.getPriority() > upTime) { + continue; + } + + itr.remove(); + currentTask.setUnqueued(); + + if (!currentTask.isAlive()) { + continue; + } else if (currentTask.isSync()) { + currentTask.pulse(); + repeatSchedule(currentTask); + } else { + Spout.getLogger().info("Async repeating task submitted"); + } + } + if (taskQueue.complete(q, upTime)) { + break; + } + } + } + + public void cancelTask(SpoutTask task) { + if (task == null) { + throw new IllegalArgumentException("Task cannot be null!"); + } + synchronized (scheduleLock) { + task.stop(); + if (taskQueue.remove(task)) { + removeTask(task); + } + } + if (!task.isSync()) { + SpoutWorker worker = activeWorkers.get(task); + if (worker != null) { + worker.interrupt(); + } + } + } + + public Task schedule(SpoutTask task) { + synchronized (scheduleLock) { + if (!addTask(task)) { + return task; + } + if (!task.isSync()) { + SpoutWorker worker = new SpoutWorker(task, this); + addWorker(worker, task); + worker.start(pool); + } else { + taskQueue.add(task); + } + return task; + } + } + + protected Task repeatSchedule(SpoutTask task) { + synchronized (scheduleLock) { + if (task.isAlive()) { + schedule(task); + } else { + removeTask(task); + } + } + return task; + } + + public void addWorker(SpoutWorker worker, SpoutTask task) { + activeWorkers.put(task, worker); + } + + public boolean removeWorker(SpoutWorker worker, SpoutTask task) { + return activeWorkers.remove(task, worker); + } + + public boolean addTask(SpoutTask task) { + activeTasks.put(task.getTaskId(), task); + if (!alive.get()) { + cancelTask(task); + return false; + } + return true; + } + + public boolean removeTask(SpoutTask task) { + return activeTasks.remove(task.getTaskId(), task); + } + + @Override + public boolean isQueued(int taskId) { + return activeTasks.containsKey(taskId); + } + + @Override + public void cancelTask(int taskId) { + cancelTask(activeTasks.get(taskId)); + } + + @Override + public void cancelTask(Task task) { + if (task == null) { + throw new IllegalArgumentException("Task cannot be null!"); + } + cancelTask(activeTasks.get(task.getTaskId())); + } + + @Override + public void cancelTasks(Object plugin) { + ArrayList tasks = new ArrayList<>(activeTasks.values()); + for (SpoutTask task : tasks) { + if (task.getOwner() == plugin) { + cancelTask(task); + } + } + } + + @Override + public void cancelAllTasks() { + ArrayList tasks = new ArrayList<>(activeTasks.values()); + for (SpoutTask task : tasks) { + cancelTask(task); + } + } + + @Override + public List getActiveWorkers() { + return new ArrayList(activeWorkers.values()); + } + + public boolean waitForAsyncTasks(long timeout) { + long startTime = System.currentTimeMillis(); + while (System.currentTimeMillis() < startTime + timeout) { + try { + if (activeWorkers.isEmpty()) { + return true; + } + Thread.sleep(10); + } catch (InterruptedException ie) { + return false; + } + } + return false; + } + + @Override + public List getPendingTasks() { + List tasks = taskQueue.getTasks(); + List list = new ArrayList<>(tasks.size()); + for (SpoutTask t : tasks) { + list.add(t); + } + return list; + } + + public boolean shutdown() { + return shutdown(1); + } + + public boolean shutdown(long timeout) { + alive.set(false); + pool.shutdown(); + cancelAllTasks(); + return true; + } + + @Override + public long getUpTime() { + return upTime.get(); + } +} diff --git a/src/main/java/org/spout/engine/scheduler/SpoutWorker.java b/src/main/java/org/spout/engine/scheduler/SpoutWorker.java new file mode 100644 index 0000000..6147a17 --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/SpoutWorker.java @@ -0,0 +1,126 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.scheduler; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + +import org.spout.api.scheduler.Worker; +import org.spout.api.util.future.SimpleFuture; + +public class SpoutWorker implements Worker, Runnable { + @SuppressWarnings ("rawtypes") + private static final Future NOT_SUBMITED = new SimpleFuture(); + @SuppressWarnings ("rawtypes") + private static final Future CANCELLED = new SimpleFuture(); + private final int id; + private final Object owner; + private final SpoutTask task; + private final Thread thread; + private final Runnable r; + private AtomicReference> futureRef = new AtomicReference>(NOT_SUBMITED); + private boolean shouldContinue = true; + private final SpoutTaskManager taskManager; + + protected SpoutWorker(final SpoutTask task, final SpoutTaskManager taskManager) { + id = task.getTaskId(); + owner = task.getOwner(); + this.task = task; + String name = "Spout Worker{Owner:" + ((owner != null) ? owner.getClass().getName() : "none") + ", id:" + id + "}"; + r = new Runnable() { + @Override + public void run() { + task.pulse(); + taskManager.removeWorker(SpoutWorker.this, task); + taskManager.repeatSchedule(task); + } + }; + if (task.isLongLived()) { + thread = new Thread(r, name); + } else { + thread = null; + } + this.taskManager = taskManager; + } + + public void start(ExecutorService pool) { + if (thread != null) { + thread.start(); + } else { + Future future = pool.submit(r); + if (!this.futureRef.compareAndSet(NOT_SUBMITED, future)) { + future.cancel(true); + } + } + } + + @Override + public int hashCode() { + return id; + } + + @Override + public int getTaskId() { + return id; + } + + @Override + public Object getOwner() { + return owner; + } + + @Override + public SpoutTask getTask() { + return task; + } + + public boolean shouldContinue() { + return shouldContinue; + } + + @Override + public void cancel() { + taskManager.cancelTask(task); + } + + public void interrupt() { + if (thread != null) { + thread.interrupt(); + } else { + if (!this.futureRef.compareAndSet(NOT_SUBMITED, CANCELLED)) { + Future future = futureRef.get(); + future.cancel(true); + } + } + } + + @Override + public void run() { + shouldContinue = task.pulse(); + } +} diff --git a/src/main/java/org/spout/engine/scheduler/TPSLimitedThread.java b/src/main/java/org/spout/engine/scheduler/TPSLimitedThread.java new file mode 100644 index 0000000..868f5c1 --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/TPSLimitedThread.java @@ -0,0 +1,69 @@ +/** + * This file is part of Client, licensed under the MIT License (MIT). + * + * Copyright (c) 2013 Spoutcraft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spout.engine.scheduler; + +import org.spout.api.scheduler.Timer; + +/** + * Represents a thread that runs at a specific TPS until terminated. + */ +public class TPSLimitedThread extends Thread { + private static final long NANOS_IN_SECOND = 1000000000; + private final SchedulerElement element; + private final Timer timer; + private volatile boolean running = true; + + public TPSLimitedThread(String name, SchedulerElement element, int tps) { + super(name); + this.element = element; + timer = new Timer(tps); + } + + @Override + public void run() { + element.onStart(); + timer.start(); + long currentTime; + long lastTime = getTime() - (long) (1f / timer.getTps() * NANOS_IN_SECOND); + while (running) { + // We put this before everything so that the Timer accurately schedules the first tick + timer.sync(); + element.onTick((currentTime = getTime()) - lastTime); + lastTime = currentTime; + } + element.onStop(); + } + + public void terminate() { + running = false; + } + + public boolean isRunning() { + return running; + } + + private static long getTime() { + return System.nanoTime(); + } +} diff --git a/src/main/java/org/spout/engine/scheduler/TaskPriorityQueue.java b/src/main/java/org/spout/engine/scheduler/TaskPriorityQueue.java new file mode 100644 index 0000000..dac190a --- /dev/null +++ b/src/main/java/org/spout/engine/scheduler/TaskPriorityQueue.java @@ -0,0 +1,116 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.scheduler; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; + +import org.spout.api.util.concurrent.ConcurrentLongPriorityQueue; +import org.spout.api.util.concurrent.RedirectableConcurrentLinkedQueue; +import org.spout.engine.util.thread.AsyncManager; + +public class TaskPriorityQueue extends ConcurrentLongPriorityQueue { + private final AsyncManager taskManager; + + public TaskPriorityQueue(AsyncManager manager, long resolution) { + super(resolution); + taskManager = manager; + } + + /** + * Gets the first pending task on the queue. A task is considered pending if its next call time is less than or equal to the given current time.

NOTE: This method should only be called from + * a single thread. + * + * @param currentTime the current time + * @return the first pending task, or null if no task is pending + */ + public Queue getPendingTask(long currentTime) { + if (Thread.currentThread() != taskManager.getExecutionThread()) { + throw new IllegalStateException("getPendingTask() may only be called from the thread that created the TaskPriorityQueue"); + } + + return super.poll(currentTime); + } + + @Override + public boolean add(SpoutTask task) { + if (task != null) { + if (!task.setQueued()) { + throw new UnsupportedOperationException("Task was dead when adding to the queue"); + } + } + return super.add(task); + } + + @Override + public boolean redirect(SpoutTask task) { + return super.add(task); + } + + @Override + public boolean remove(SpoutTask task) { + task.remove(); + if (!super.remove(task)) { + return false; + } + + task.setUnqueued(); + + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("{"); + boolean first = true; + for (SpoutTask t : getTasks()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append("{").append(t.getTaskId()).append(":").append(t.getNextCallTime()).append("}"); + } + return sb.append("}").toString(); + } + + public List getTasks() { + List list = new ArrayList<>(); + Iterator> iq = queueMap.values().iterator(); + while (iq.hasNext()) { + Iterator i = iq.next().iterator(); + while (i.hasNext()) { + SpoutTask t = i.next(); + list.add(t); + } + } + return list; + } +} + diff --git a/src/main/java/org/spout/engine/util/argument/EnumConverter.java b/src/main/java/org/spout/engine/util/argument/EnumConverter.java new file mode 100644 index 0000000..92cb88b --- /dev/null +++ b/src/main/java/org/spout/engine/util/argument/EnumConverter.java @@ -0,0 +1,45 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.argument; + +import com.beust.jcommander.IStringConverter; + +/** + * Converts an enum field name to an enum type + */ +public class EnumConverter> implements IStringConverter { + private final Class enumClass; + + public EnumConverter(Class enumClass) { + this.enumClass = enumClass; + } + + @Override + public T convert(String s) { + return Enum.valueOf(enumClass, s.toUpperCase()); + } +} diff --git a/src/main/java/org/spout/engine/util/argument/PlatformConverter.java b/src/main/java/org/spout/engine/util/argument/PlatformConverter.java new file mode 100644 index 0000000..c15b0e7 --- /dev/null +++ b/src/main/java/org/spout/engine/util/argument/PlatformConverter.java @@ -0,0 +1,35 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.argument; + +import org.spout.api.Platform; + +public class PlatformConverter extends EnumConverter { + public PlatformConverter() { + super(Platform.class); + } +} diff --git a/src/main/java/org/spout/engine/util/thread/AsyncExecutorUtils.java b/src/main/java/org/spout/engine/util/thread/AsyncExecutorUtils.java new file mode 100644 index 0000000..77c5358 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/AsyncExecutorUtils.java @@ -0,0 +1,94 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Logger; + +import org.spout.api.Spout; + +public class AsyncExecutorUtils { + private static final String LINE = "------------------------------"; + + /** + * Logs all threads, the thread details, and active stack traces + */ + public static void dumpAllStacks() { + Logger log = Spout.getLogger(); + Map traces = Thread.getAllStackTraces(); + Iterator> i = traces.entrySet().iterator(); + while (i.hasNext()) { + Entry entry = i.next(); + Thread thread = entry.getKey(); + log.info(LINE); + + log.info("Current Thread: " + thread.getName()); + log.info(" PID: " + thread.getId() + " | Alive: " + thread.isAlive() + " | State: " + thread.getState()); + log.info(" Stack:"); + StackTraceElement[] stack = entry.getValue(); + for (int line = 0; line < stack.length; line++) { + log.info(" " + stack[line].toString()); + } + } + log.info(LINE); + } + + /** + * Scans for deadlocked threads + */ + public static void checkForDeadlocks() { + Logger log = Spout.getLogger(); + ThreadMXBean tmx = ManagementFactory.getThreadMXBean(); + long[] ids = tmx.findDeadlockedThreads(); + if (ids != null) { + log.info("Checking for deadlocks"); + ThreadInfo[] infos = tmx.getThreadInfo(ids, true, true); + log.info("The following threads are deadlocked:"); + for (ThreadInfo ti : infos) { + log.info(ti.toString()); + } + } + } + + /** + * Dumps the stack for the given Thread + * + * @param t the thread + */ + public static void dumpStackTrace(Thread t) { + StackTraceElement[] stackTrace = t.getStackTrace(); + Spout.getLogger().info("Stack trace for Thread " + t.getName()); + for (StackTraceElement e : stackTrace) { + Spout.getLogger().info("\tat " + e); + } + } +} diff --git a/src/main/java/org/spout/engine/util/thread/AsyncManager.java b/src/main/java/org/spout/engine/util/thread/AsyncManager.java new file mode 100644 index 0000000..f375f94 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/AsyncManager.java @@ -0,0 +1,102 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread; + +public interface AsyncManager { + /** + * This method is called directly before preSnapshot is called + */ + public void finalizeRun(); + + /** + * This method is called directly before copySnapshotRun and is a MONITOR ONLY stage and no updates should be performed.

It occurs after the finalize stage and before the copy snapshot + * stage. + */ + public void preSnapshotRun(); + + /** + * This method is called in order to update the snapshot at the end of each tick + */ + public void copySnapshotRun(); + + /** + * This method is called in order to start a new tick + * + * @param delta the time since the last tick + */ + public void startTickRun(int stage, long delta); + + /** + * This method is called to execute physics for blocks local to the Region. It might be called multiple times per tick + * + * @param sequence -1 for local, 0 - 26 for which sequence + */ + public void runPhysics(int sequence); + + /** + * This method is called to execute dynamic updates for blocks in the Region. It might be called multiple times per tick, the sequence number indicates which lists to check + * + * @param sequence -1 for local, 0 - 26 for which sequence + */ + public void runDynamicUpdates(long threshold, int sequence); + + /** + * This method is called to update lighting. It might be called multiple times per tick + * + * @param sequence -1 for local, 0 - 26 for which sequence + */ + public void runLighting(int sequence); + + /** + * Gets the sequence number associated with this manager + * + * @return the sequence number, of -1 for none + */ + public int getSequence(); + + /** + * This method is called to determine the earliest available dynamic update time + * + * @return the earliest pending dynamic block update + */ + public long getFirstDynamicUpdateTime(); + + /** + * Gets the execution thread associated with this manager + */ + public Thread getExecutionThread(); + + /** + * Sets the execution thread associated with this manager + */ + public void setExecutionThread(Thread t); + + /** + * Gets the highest stage for the start tick task + */ + public int getMaxStage(); +} diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/CopySnapshotTask.java b/src/main/java/org/spout/engine/util/thread/coretasks/CopySnapshotTask.java new file mode 100644 index 0000000..19aff7a --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/CopySnapshotTask.java @@ -0,0 +1,41 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public class CopySnapshotTask extends GlobalManagerRunnableFactory { + @Override + public ManagerRunnable getTask(final AsyncManager manager, final int sequence) { + return new ManagerRunnable(manager) { + @Override + public void runTask() { + manager.copySnapshotRun(); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/DynamicUpdatesTask.java b/src/main/java/org/spout/engine/util/thread/coretasks/DynamicUpdatesTask.java new file mode 100644 index 0000000..c4a46e1 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/DynamicUpdatesTask.java @@ -0,0 +1,47 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public class DynamicUpdatesTask extends SequencedManagerRunnableFactory { + private long threshold = 0; + + @Override + public ManagerRunnable getTask(final AsyncManager manager, final int sequence) { + return new ManagerRunnable(manager) { + @Override + public void runTask() { + manager.runDynamicUpdates(threshold, sequence); + } + }; + } + + public void setThreshold(long threshold) { + this.threshold = threshold; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/FinalizeTask.java b/src/main/java/org/spout/engine/util/thread/coretasks/FinalizeTask.java new file mode 100644 index 0000000..f8cb252 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/FinalizeTask.java @@ -0,0 +1,41 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public class FinalizeTask extends GlobalManagerRunnableFactory { + @Override + public ManagerRunnable getTask(final AsyncManager manager, final int sequence) { + return new ManagerRunnable(manager) { + @Override + public void runTask() { + manager.finalizeRun(); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/GlobalManagerRunnableFactory.java b/src/main/java/org/spout/engine/util/thread/coretasks/GlobalManagerRunnableFactory.java new file mode 100644 index 0000000..6cfd575 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/GlobalManagerRunnableFactory.java @@ -0,0 +1,39 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +public abstract class GlobalManagerRunnableFactory implements ManagerRunnableFactory { + @Override + public int getMaxSequence() { + return -1; + } + + @Override + public int getMinSequence() { + return -1; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/LightingTask.java b/src/main/java/org/spout/engine/util/thread/coretasks/LightingTask.java new file mode 100644 index 0000000..bab4f69 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/LightingTask.java @@ -0,0 +1,46 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public class LightingTask extends SequencedManagerRunnableFactory { + @Override + public ManagerRunnable getTask(final AsyncManager manager, final int sequence) { + return new ManagerRunnable(manager) { + @Override + public void runTask() { + manager.runLighting(sequence); + } + }; + } + + @Override + public int getMinSequence() { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/ManagerRunnable.java b/src/main/java/org/spout/engine/util/thread/coretasks/ManagerRunnable.java new file mode 100644 index 0000000..d3a81a7 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/ManagerRunnable.java @@ -0,0 +1,45 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public abstract class ManagerRunnable implements Runnable { + private final AsyncManager manager; + + public ManagerRunnable(AsyncManager manager) { + this.manager = manager; + } + + @Override + public final void run() { + manager.setExecutionThread(Thread.currentThread()); + runTask(); + } + + protected abstract void runTask(); +} diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/ManagerRunnableFactory.java b/src/main/java/org/spout/engine/util/thread/coretasks/ManagerRunnableFactory.java new file mode 100644 index 0000000..c24a5f5 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/ManagerRunnableFactory.java @@ -0,0 +1,37 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public interface ManagerRunnableFactory { + public ManagerRunnable getTask(AsyncManager manager, int sequence); + + public int getMaxSequence(); + + public int getMinSequence(); +} diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/PhysicsTask.java b/src/main/java/org/spout/engine/util/thread/coretasks/PhysicsTask.java new file mode 100644 index 0000000..ce774a1 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/PhysicsTask.java @@ -0,0 +1,41 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public class PhysicsTask extends SequencedManagerRunnableFactory { + @Override + public ManagerRunnable getTask(final AsyncManager manager, final int sequence) { + return new ManagerRunnable(manager) { + @Override + public void runTask() { + manager.runPhysics(sequence); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/PreSnapshotTask.java b/src/main/java/org/spout/engine/util/thread/coretasks/PreSnapshotTask.java new file mode 100644 index 0000000..89c2520 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/PreSnapshotTask.java @@ -0,0 +1,41 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public class PreSnapshotTask extends GlobalManagerRunnableFactory { + @Override + public ManagerRunnable getTask(final AsyncManager manager, final int sequence) { + return new ManagerRunnable(manager) { + @Override + public void runTask() { + manager.preSnapshotRun(); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/SequencedManagerRunnableFactory.java b/src/main/java/org/spout/engine/util/thread/coretasks/SequencedManagerRunnableFactory.java new file mode 100644 index 0000000..25afb50 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/SequencedManagerRunnableFactory.java @@ -0,0 +1,39 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +public abstract class SequencedManagerRunnableFactory implements ManagerRunnableFactory { + @Override + public int getMaxSequence() { + return 27; + } + + @Override + public int getMinSequence() { + return -1; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/coretasks/StartTickTask.java b/src/main/java/org/spout/engine/util/thread/coretasks/StartTickTask.java new file mode 100644 index 0000000..27b3aac --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/coretasks/StartTickTask.java @@ -0,0 +1,56 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.coretasks; + +import org.spout.engine.util.thread.AsyncManager; + +public class StartTickTask extends GlobalManagerRunnableFactory { + private final int stage; + private long delta; + + public StartTickTask(int stage) { + this.stage = stage; + } + + @Override + public ManagerRunnable getTask(final AsyncManager manager, final int sequence) { + final long delta = this.delta; + if (manager.getMaxStage() < stage) { + return null; + } + return new ManagerRunnable(manager) { + @Override + public void runTask() { + manager.startTickRun(stage, delta); + } + }; + } + + public void setDelta(long delta) { + this.delta = delta; + } +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotManager.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotManager.java new file mode 100644 index 0000000..9b91b8e --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotManager.java @@ -0,0 +1,48 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.ArrayList; +import java.util.List; + +public class SnapshotManager { + private List managed = new ArrayList<>(); + + public synchronized void add(Snapshotable s) { + synchronized (managed) { + managed.add(s); + } + } + + public void copyAllSnapshots() { + synchronized (managed) { + for (int i = 0; i < managed.size(); i++) { + managed.get(i).copySnapshot(); + } + } + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotUpdate.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotUpdate.java new file mode 100644 index 0000000..d23f9c6 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotUpdate.java @@ -0,0 +1,98 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +public class SnapshotUpdate { + private final boolean add; + private final int index; + private final T object; + + public SnapshotUpdate(T object, boolean add) { + this.object = object; + this.add = add; + this.index = -1; + } + + public SnapshotUpdate(int index, boolean add) { + if (index < 0) { + throw new IllegalArgumentException("Negative indexs are not supported"); + } else if (add) { + throw new IllegalArgumentException("An object must be provided when adding an object"); + } + this.object = null; + this.add = add; + this.index = index; + } + + public SnapshotUpdate(T object, int index, boolean add) { + if (index < 0) { + throw new IllegalArgumentException("Negative indexs are not supported"); + } else if (!add) { + throw new IllegalStateException("Removal of objects does not require both an index and an object"); + } + this.object = object; + this.add = add; + this.index = index; + } + + /** + * Indicates if this update is an addition or removal + * + * @return true for additions + */ + public boolean isAdd() { + return add; + } + + /** + * Indicates if this is an indexed operation + * + * @return true for indexed operations + */ + public boolean isIndexed() { + return index >= 0; + } + + /** + * Gets the object + * + * @return the object + */ + public T getObject() { + return object; + } + + /** + * Gets the index + */ + public int getIndex() { + if (!isIndexed()) { + throw new IllegalStateException("Cannot get the index of a non-indexed operation"); + } + return index; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/Snapshotable.java b/src/main/java/org/spout/engine/util/thread/snapshotable/Snapshotable.java new file mode 100644 index 0000000..146f25b --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/Snapshotable.java @@ -0,0 +1,34 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +public interface Snapshotable { + /** + * Copies the next value to the snapshot value + */ + public void copySnapshot(); +} \ No newline at end of file diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableArrayList.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableArrayList.java new file mode 100644 index 0000000..f869949 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableArrayList.java @@ -0,0 +1,153 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object for ArrayLists + */ +public class SnapshotableArrayList implements Snapshotable { + private final ConcurrentLinkedQueue dirty = new ConcurrentLinkedQueue<>(); + private final List snapshot; + private final List live; + + public SnapshotableArrayList(SnapshotManager manager) { + this(manager, null); + } + + public SnapshotableArrayList(SnapshotManager manager, ArrayList initial) { + if (initial != null) { + snapshot = new ArrayList<>(initial); + } else { + snapshot = new ArrayList<>(); + } + live = Collections.synchronizedList(new ArrayList<>(snapshot)); + manager.add(this); + } + + /** + * Adds an object to the list + */ + @DelayedWrite + public boolean add(T object) { + boolean success = live.add(object); + + if (success) { + dirty.add(object); + } + + return success; + } + + @DelayedWrite + public void addAll(Collection values) { + for (T object : values) { + boolean success = live.add(object); + + if (success) { + dirty.add(object); + } + } + } + + /** + * Removes an object from the list + */ + @DelayedWrite + public boolean remove(T object) { + boolean success = live.remove(object); + + if (success) { + dirty.add(object); + } + + return success; + } + + /** + * Removes the object from the list at a particular index + */ + @DelayedWrite + public void remove(int index) { + dirty.add(live.remove(index)); + } + + /** + * Gets the snapshot value + * + * @return the stable snapshot value + */ + @SnapshotRead + public List get() { + return Collections.unmodifiableList(snapshot); + } + + /** + * Gets the live value + * + * @return the live value + */ + @LiveRead + public List getLive() { + return Collections.unmodifiableList(live); + } + + /** + * Gets the dirty object list + * + * @return the dirty list + */ + @LiveRead + public List getDirtyList() { + return Collections.unmodifiableList(new ArrayList<>(dirty)); + } + + /** + * Copies the next values to the snapshot + */ + @Override + public void copySnapshot() { + if (dirty.size() > 0) { + snapshot.clear(); + synchronized (live) { + for (T o : live) { + snapshot.add(o); + } + } + } + dirty.clear(); + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableBoolean.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableBoolean.java new file mode 100644 index 0000000..cbe7634 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableBoolean.java @@ -0,0 +1,96 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports primitive booleans + */ +public class SnapshotableBoolean implements Snapshotable { + private AtomicBoolean next; + private boolean snapshot; + + public SnapshotableBoolean(SnapshotManager manager, boolean initial) { + next = new AtomicBoolean(initial); + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(boolean next) { + this.next.set(next); + } + + /** + * Sets the next value but only if the current next value is the given value + * + * @return true on success + */ + public boolean compareAndSet(boolean expect, boolean next) { + return this.next.compareAndSet(expect, next); + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public boolean get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public boolean getLive() { + return next.get(); + } + + public boolean isDirty() { + return snapshot != next.get(); + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next.get(); + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableByte.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableByte.java new file mode 100644 index 0000000..d75acc5 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableByte.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports primitive bytes + */ +public class SnapshotableByte implements Snapshotable { + private volatile byte next; + private byte snapshot; + + public SnapshotableByte(SnapshotManager manager, byte initial) { + next = initial; + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(byte next) { + this.next = next; + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public byte get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public byte getLive() { + return next; + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableByteArray.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableByteArray.java new file mode 100644 index 0000000..8e990f4 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableByteArray.java @@ -0,0 +1,115 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable array of type byte + */ +public class SnapshotableByteArray implements Snapshotable { + private final byte[] snapshot; + private final byte[] live; + private final int[] dirtyArray; + private final AtomicInteger dirtyIndex = new AtomicInteger(0); + + public SnapshotableByteArray(SnapshotManager manager, byte[] initial) { + this(manager, initial, 100); + } + + public SnapshotableByteArray(SnapshotManager manager, byte[] initial, int dirtySize) { + snapshot = new byte[initial.length]; + live = new byte[initial.length]; + dirtyArray = new int[dirtySize]; + for (int i = 0; i < initial.length; i++) { + snapshot[i] = initial[i]; + live[i] = initial[i]; + } + } + + /** + * Gets the snapshot value in the array + * + * @param index to lookup + * @return snapshot value + */ + @SnapshotRead + public byte get(int index) { + return snapshot[index]; + } + + /** + * Gets the live value in the array + * + * @param index to lookup + * @return live value + */ + @LiveRead + public byte getLive(int index) { + synchronized (live) { + return live[index]; + } + } + + /** + * Sets the value for the next snapshot + * + * @param index to set at + * @param value to set to + */ + @DelayedWrite + public byte set(int index, byte value) { + synchronized (live) { + live[index] = value; + } + int localDirtyIndex = dirtyIndex.getAndIncrement(); + if (localDirtyIndex < dirtyArray.length) { + dirtyArray[localDirtyIndex] = index; + } + return snapshot[index]; + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + int length = dirtyIndex.get(); + if (length <= dirtyArray.length) { + for (int i = 0; i < length; i++) { + int index = dirtyArray[i]; + snapshot[index] = live[index]; + } + } else { + System.arraycopy(live, 0, snapshot, 0, live.length); + } + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableDouble.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableDouble.java new file mode 100644 index 0000000..681c8e8 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableDouble.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports primitive doubles + */ +public class SnapshotableDouble implements Snapshotable { + private volatile double next; + private double snapshot; + + public SnapshotableDouble(SnapshotManager manager, double initial) { + next = initial; + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(double next) { + this.next = next; + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public double get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public double getLive() { + return next; + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableFloat.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableFloat.java new file mode 100644 index 0000000..bb0c759 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableFloat.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports primitive floats + */ +public class SnapshotableFloat implements Snapshotable { + private volatile float next; + private float snapshot; + + public SnapshotableFloat(SnapshotManager manager, float initial) { + next = initial; + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(float next) { + this.next = next; + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public float get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public float getLive() { + return next; + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableHashMap.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableHashMap.java new file mode 100644 index 0000000..ec31d5d --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableHashMap.java @@ -0,0 +1,184 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; + +import org.spout.api.scheduler.TickStage; +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable class for HashMaps + */ +public class SnapshotableHashMap implements Snapshotable { + private final Map snapshot = new LinkedHashMap<>(); + private final Map unmodifySnapshot = Collections.unmodifiableMap(snapshot); + private final ConcurrentMap live = new ConcurrentHashMap<>(); + private final Map unmodifyLive = Collections.unmodifiableMap(live); + private final ConcurrentLinkedQueue dirtyKeys = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue dirtyValues = new ConcurrentLinkedQueue<>(); + + public SnapshotableHashMap(SnapshotManager manager) { + manager.add(this); + } + + /** + * Adds a key/value pair to the map + * + * @param key the key + * @param value the value + * @return the old value + */ + @DelayedWrite + @LiveRead + public V put(K key, V value) { + V oldValue = live.put(key, value); + dirtyKeys.add(key); + dirtyValues.add(value); + return oldValue; + } + + /** + * Adds a key/value pair to the map, if no value exists for the key + * + * @param key the key + * @param value the value + * @return the old value + */ + @DelayedWrite + @LiveRead + public V putIfAbsent(K key, V value) { + V oldValue = live.putIfAbsent(key, value); + if (oldValue == null) { + dirtyKeys.add(key); + dirtyValues.add(value); + } + return oldValue; + } + + /** + * Removes a key/value pair from the list + * + * @param key the key + * @return the old value + */ + @DelayedWrite + @LiveRead + public V remove(K key) { + V oldValue = live.remove(key); + if (oldValue != null) { + dirtyKeys.add(key); + dirtyValues.add(oldValue); + } + return oldValue; + } + + /** + * Removes a key/value pair from the list + * + * @param key the key + * @param value the value + * @return true if the key/value pair was removed + */ + @DelayedWrite + @LiveRead + public boolean remove(K key, V value) { + boolean success = live.remove(key, value); + if (success) { + dirtyKeys.add(key); + dirtyValues.add(value); + } + return success; + } + + /** + * Gets the snapshot value + * + * @return the stable snapshot value + */ + @SnapshotRead + public Map get() { + return unmodifySnapshot; + } + + /** + * Gets the live value + * + * @return the live set + */ + public Map getLive() { + return unmodifyLive; + } + + /** + * Creates a list of keys that have been changed since the last snapshot copy.

This method may only be called during the pre-snapshot stage and the list only remains valid during that + * stage. + * + * @return the list of elements that have been updated + */ + public List getDirtyKeyList() { + TickStage.checkStage(TickStage.PRESNAPSHOT); + return Collections.unmodifiableList(new ArrayList<>(dirtyKeys)); + } + + /** + * Creates a list of values that have been changed since the last snapshot copy.

This method may only be called during the pre-snapshot stage and the list only remains valid during that + * stage. + * + * @return the list of elements that have been updated + */ + public List getDirtyValueList() { + TickStage.checkStage(TickStage.PRESNAPSHOT); + return Collections.unmodifiableList(new ArrayList<>(dirtyValues)); + } + + /** + * Copies the next values to the snapshot + */ + @Override + public void copySnapshot() { + for (K key : dirtyKeys) { + V value = live.get(key); + if (value == null) { + snapshot.remove(key); + } else { + snapshot.put(key, value); + } + } + dirtyKeys.clear(); + dirtyValues.clear(); + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableHashSet.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableHashSet.java new file mode 100644 index 0000000..3bfc435 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableHashSet.java @@ -0,0 +1,147 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.spout.api.scheduler.TickStage; +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable class for HashSets + */ +public class SnapshotableHashSet implements Snapshotable { + private final Set snapshot = new HashSet<>(); + private final Set unmodifySnapshot = Collections.unmodifiableSet(snapshot); + private final Set live = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set unmodifyLive = Collections.unmodifiableSet(live); + private final ConcurrentLinkedQueue dirty = new ConcurrentLinkedQueue<>(); + private final ArrayList dirtyList = new ArrayList<>(); + + public SnapshotableHashSet(SnapshotManager manager) { + this(manager, null); + } + + public SnapshotableHashSet(SnapshotManager manager, HashSet initial) { + if (initial != null) { + for (T o : initial) { + add(o); + } + } + manager.add(this); + } + + /** + * Adds an object to the list + * + * @return true if the object was successfully added + */ + @DelayedWrite + @LiveRead + public boolean add(T object) { + boolean success = live.add(object); + if (success) { + dirty.add(object); + } + return success; + } + + /** + * Removes an object from the list + */ + @DelayedWrite + public boolean remove(T object) { + boolean success = live.remove(object); + if (success) { + dirty.add(object); + } + return success; + } + + /** + * Gets the snapshot value + * + * @return the stable snapshot value + */ + @SnapshotRead + public Set get() { + return unmodifySnapshot; + } + + /** + * Gets the live value + * + * @return the live set + */ + public Set getLive() { + return unmodifyLive; + } + + /** + * Creates a list of elements that have been changed since the last snapshot copy.

This method may only be called during the pre-snapshot stage and the list only remains valid during that + * stage. + * + * @return the list of elements that have been updated + */ + public List getDirtyList() { + TickStage.checkStage(TickStage.PRESNAPSHOT); + return Collections.unmodifiableList(new ArrayList<>(dirty)); + } + + /** + * Tests if the set is empty + * + * @return true if the set is empty + */ + public boolean isEmptyLive() { + return live.isEmpty(); + } + + /** + * Copies the next values to the snapshot + */ + @Override + public void copySnapshot() { + for (T o : dirty) { + if (live.contains(o)) { + snapshot.add(o); + } else { + snapshot.remove(o); + } + } + dirty.clear(); + dirtyList.clear(); + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableInt.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableInt.java new file mode 100644 index 0000000..dcd6b67 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableInt.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports primitive ints + */ +public class SnapshotableInt implements Snapshotable { + private volatile int next; + private int snapshot; + + public SnapshotableInt(SnapshotManager manager, int initial) { + next = initial; + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(int next) { + this.next = next; + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public int get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public int getLive() { + return next; + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableLinkedHashMap.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableLinkedHashMap.java new file mode 100644 index 0000000..66bd9f1 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableLinkedHashMap.java @@ -0,0 +1,215 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; + +import org.spout.api.scheduler.TickStage; +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable class for LinkedHashMaps + */ +public class SnapshotableLinkedHashMap implements Snapshotable { + private final Map snapshot = new LinkedHashMap<>(); + private final Map unmodifySnapshot = Collections.unmodifiableMap(snapshot); + private final Collection unmodifyValues = Collections.unmodifiableCollection(snapshot.values()); + private final ConcurrentMap live = new ConcurrentHashMap<>(); + private final Map unmodifyLive = Collections.unmodifiableMap(live); + private final ConcurrentLinkedQueue dirty = new ConcurrentLinkedQueue<>(); + private final ArrayList dirtyList = new ArrayList<>(); + private final HashSet dirtyListTemp = new HashSet<>(); + private final List unmodifyDirty = Collections.unmodifiableList(dirtyList); + private boolean dirtyListGenerated = false; + + public SnapshotableLinkedHashMap(SnapshotManager manager) { + manager.add(this); + } + + /** + * Adds a key/value pair to the map + * + * @param key the key + * @param value the value + * @return the old value + */ + @DelayedWrite + @LiveRead + public V put(K key, V value) { + V oldValue = live.put(key, value); + dirty.add(key); + return oldValue; + } + + /** + * Adds a key/value pair to the map, if no value exists for the key + * + * @param key the key + * @param value the value + * @return the old value + */ + @DelayedWrite + @LiveRead + public V putIfAbsent(K key, V value) { + V oldValue = live.putIfAbsent(key, value); + if (oldValue == null) { + dirty.add(key); + } + return oldValue; + } + + /** + * Removes a key/value pair from the list + * + * @param key the key + * @return the old value + */ + @DelayedWrite + @LiveRead + public V remove(K key) { + V oldValue = live.remove(key); + if (oldValue != null) { + dirty.add(key); + } + return oldValue; + } + + /** + * Removes a key/value pair from the list + * + * @param key the key + * @param value the value + * @return true if the key/value pair was removed + */ + @DelayedWrite + @LiveRead + public boolean remove(K key, V value) { + boolean success = live.remove(key, value); + if (success) { + dirty.add(key); + } + return success; + } + + /** + * Gets the snapshot value + * + * @return the stable snapshot value + */ + @SnapshotRead + public Map get() { + return unmodifySnapshot; + } + + /** + * Gets the live value + * + * @return the live set + */ + @LiveRead + public Map getLive() { + return unmodifyLive; + } + + /** + * Get the values in the map, in order + * + * @return the values + */ + @SnapshotRead + public Collection getValues() { + return unmodifyValues; + } + + /** + * Get the values in the map, in order + * + * @return the values + */ + @SnapshotRead + public Collection getValuesLive() { + throw new UnsupportedOperationException("Iterating over the live values is not possible, since they are not stored LinkedHashSet"); + } + + /** + * Creates a list of keys that have been changed since the last snapshot copy.

This method may only be called during the pre-snapshot stage and the list only remains valid during that + * stage. + * + * @return the list of elements that have been updated + */ + public List getDirtyList() { + TickStage.checkStage(TickStage.PRESNAPSHOT); + if (!dirtyListGenerated) { + for (K o : dirty) { + if (dirtyListTemp.add(o)) { + dirtyList.add(o); + } + } + dirtyListTemp.clear(); + dirtyListGenerated = true; + } + return unmodifyDirty; + } + + /** + * Tests if the set is empty + * + * @return true if the set is empty + */ + public boolean isEmptyLive() { + return live.isEmpty(); + } + + /** + * Copies the next values to the snapshot + */ + @Override + public void copySnapshot() { + for (K key : dirty) { + V value = live.get(key); + if (value == null) { + snapshot.remove(key); + } else { + snapshot.put(key, value); + } + } + dirty.clear(); + dirtyList.clear(); + dirtyListGenerated = false; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableLong.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableLong.java new file mode 100644 index 0000000..24ea0d1 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableLong.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports primitive longs + */ +public class SnapshotableLong implements Snapshotable { + private volatile long next; + private long snapshot; + + public SnapshotableLong(SnapshotManager manager, long initial) { + next = initial; + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(long next) { + this.next = next; + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public long get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public long getLive() { + return next; + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableReference.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableReference.java new file mode 100644 index 0000000..0f35a88 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableReference.java @@ -0,0 +1,101 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.concurrent.atomic.AtomicReference; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports basic class types.

This class should be used for immutable types that are updated by replacing with a new immutable object + * + * @param the underlying type + */ +public class SnapshotableReference implements Snapshotable { + private AtomicReference next = new AtomicReference<>(); + private T snapshot; + + public SnapshotableReference(SnapshotManager manager, T initial) { + next.set(initial); + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(T next) { + this.next.set(next); + } + + /** + * Sets the live value to update, if the live value is equal to expect. + * + * @param expect the expected value + * @param update the new value + * @return true on success + */ + @DelayedWrite + public boolean compareAndSet(T expect, T update) { + return next.compareAndSet(expect, update); + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public T get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public T getLive() { + return next.get(); + } + + public boolean isDirty() { + return snapshot != next.get(); + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next.get(); + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableShort.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableShort.java new file mode 100644 index 0000000..5d4d5e8 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableShort.java @@ -0,0 +1,81 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable object that supports primitive shorts + */ +public class SnapshotableShort implements Snapshotable { + private volatile short next; + private short snapshot; + + public SnapshotableShort(SnapshotManager manager, short initial) { + next = initial; + snapshot = initial; + manager.add(this); + } + + /** + * Sets the next value for the Snapshotable + */ + @DelayedWrite + public void set(short next) { + this.next = next; + } + + /** + * Gets the snapshot value for + * + * @return the stable snapshot value + */ + @SnapshotRead + public short get() { + return snapshot; + } + + /** + * Gets the live value + * + * @return the unstable Live "next" value + */ + @LiveRead + public short getLive() { + return next; + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + snapshot = next; + } +} diff --git a/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableShortArray.java b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableShortArray.java new file mode 100644 index 0000000..93c08f7 --- /dev/null +++ b/src/main/java/org/spout/engine/util/thread/snapshotable/SnapshotableShortArray.java @@ -0,0 +1,182 @@ +/* + * This file is part of Spout. + * + * Copyright (c) 2011 Spout LLC + * Spout is licensed under the Spout License Version 1. + * + * Spout is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * In addition, 180 days after any changes are published, you can use the + * software, incorporating those changes, under the terms of the MIT license, + * as described in the Spout License Version 1. + * + * Spout is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License, + * the MIT license and the Spout License Version 1 along with this program. + * If not, see for the GNU Lesser General Public + * License and see for the full license, including + * the MIT license. + */ +package org.spout.engine.util.thread.snapshotable; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerArray; + +import org.spout.api.util.thread.annotation.DelayedWrite; +import org.spout.api.util.thread.annotation.LiveRead; +import org.spout.api.util.thread.annotation.SnapshotRead; + +/** + * A snapshotable array of type short + */ +public class SnapshotableShortArray implements Snapshotable { + private final short[] snapshot; + private final AtomicIntegerArray live; + private final AtomicIntegerArray dirtyArray; + private final int dirtySize; + private final AtomicInteger dirtyIndex = new AtomicInteger(0); + + public SnapshotableShortArray(SnapshotManager manager, short[] initial) { + this(manager, initial, 50); + } + + public SnapshotableShortArray(SnapshotManager manager, short[] initial, int dirtySize) { + snapshot = new short[initial.length]; + live = new AtomicIntegerArray(initial.length >> 1); + this.dirtySize = dirtySize; + dirtyArray = new AtomicIntegerArray(dirtySize); + for (int i = 0; i < initial.length; i++) { + snapshot[i] = initial[i]; + set(i, initial[i]); + } + } + + /** + * Gets a copy of the snapshot short array + * + * @return copy of the snapshot short array + */ + public short[] get() { + return Arrays.copyOf(snapshot, snapshot.length); + } + + /** + * Gets a copy of the live short array + * + * @return copy of the live short array + */ + public short[] getLive() { + short[] live = new short[snapshot.length]; + for (int i = 0; i < this.live.length(); i++) { + int value = this.live.get(i); + live[(i << 1)] = (short) (value & 0xFFFF); + live[(i << 1) + 1] = (short) (value >> 16 & 0xFFFF); + } + return live; + } + + /** + * Gets the snapshot value in the array + * + * @param index to lookup + * @return snapshot value + */ + @SnapshotRead + public short get(int index) { + return snapshot[index]; + } + + /** + * Gets the live value in the array + * + * @param index to lookup + * @return live value + */ + @LiveRead + public short getLive(int index) { + int packed = live.get(index >> 1); + if ((index & 0x1) == 0) { + return unpackZero(packed); + } else { + return unpackOne(packed); + } + } + + /** + * Sets the value for the next snapshot + * + * @param index to set at + * @param value to set to + * @return the old value + */ + @DelayedWrite + public short set(int index, short value) { + boolean success = false; + int divIndex = index >> 1; + boolean isZero = (index & 0x1) == 0; + short one; + short zero; + short old = 0; + + while (!success) { + int packed = live.get(divIndex); + if (isZero) { + old = unpackZero(packed); + one = unpackOne(packed); + zero = value; + } else { + old = unpackOne(packed); + one = value; + zero = unpackZero(packed); + } + success = live.compareAndSet(divIndex, packed, pack(zero, one)); + } + markDirty(index); + return old; + } + + private void markDirty(int index) { + int localDirtyIndex = dirtyIndex.getAndIncrement(); + if (localDirtyIndex < dirtySize) { + dirtyArray.set(localDirtyIndex, index); + } + } + + /** + * Copies the next value to the snapshot value + */ + @Override + public void copySnapshot() { + int length = dirtyIndex.get(); + if (length <= dirtySize) { + for (int i = 0; i < length; i++) { + int index = dirtyArray.get(i); + snapshot[index] = getLive(i); + } + } else { + for (int i = 0; i < snapshot.length; i++) { + snapshot[i] = getLive(i); + } + } + } + + private int pack(short zero, short one) { + return (one & 0xFFFF) << 16 | zero & 0xFFFF; + } + + private short unpackZero(int value) { + return (short) value; + } + + private short unpackOne(int value) { + return (short) (value >> 16); + } +} diff --git a/src/main/resources/fonts/ubuntu-r.ttf b/src/main/resources/fonts/ubuntu-r.ttf new file mode 100644 index 0000000..45a038b Binary files /dev/null and b/src/main/resources/fonts/ubuntu-r.ttf differ diff --git a/src/main/resources/shaders/gl20/blur.frag b/src/main/resources/shaders/gl20/blur.frag new file mode 100644 index 0000000..8b8b304 --- /dev/null +++ b/src/main/resources/shaders/gl20/blur.frag @@ -0,0 +1,33 @@ +// $shader_type: fragment + +// $texture_layout: occlusions = 0 +// $texture_layout: shadows = 1 + +#version 120 + +varying vec2 textureUV; + +uniform sampler2D occlusions; +uniform sampler2D shadows; +uniform int blurSize; +uniform vec2 texelSize; + +void main() { + float blurredOcclusion = 0; + float blurredShadow = 0; + float halfBlurSize = float(blurSize) / 2; + int blurStart = int(-floor(halfBlurSize)); + int blurEnd = int(ceil(halfBlurSize)); + for (int x = blurStart; x < blurEnd; x++) { + for (int y = blurStart; y < blurEnd; y++) { + vec2 adjacentUV = textureUV + vec2(x * texelSize.x, y * texelSize.y); + blurredOcclusion += texture2D(occlusions, adjacentUV).r; + blurredShadow += texture2D(shadows, adjacentUV).r; + } + } + float blurSizeSquared = blurSize * blurSize; + blurredOcclusion /= blurSizeSquared; + blurredShadow /= blurSizeSquared; + gl_FragData[0] = vec4(blurredOcclusion, blurredOcclusion, blurredOcclusion, 1); + gl_FragData[1] = vec4(blurredShadow, blurredShadow, blurredShadow, 1); +} diff --git a/src/main/resources/shaders/gl20/blur.vert b/src/main/resources/shaders/gl20/blur.vert new file mode 100644 index 0000000..dc26f39 --- /dev/null +++ b/src/main/resources/shaders/gl20/blur.vert @@ -0,0 +1,15 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/edaa.frag b/src/main/resources/shaders/gl20/edaa.frag new file mode 100644 index 0000000..80e65f3 --- /dev/null +++ b/src/main/resources/shaders/gl20/edaa.frag @@ -0,0 +1,80 @@ +// Edge detection anti aliasing +// Adapted from: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter09.html (Example 9-2) + +// $shader_type: fragment + +// $texture_layout: diffuse = 0 +// $texture_layout: normals = 1 +// $texture_layout: depths = 2 + +#version 120 + +const vec2 LT = vec2(-1, 1); +const vec2 RB = vec2(1, -1); +const vec2 RT = vec2(1, 1); +const vec2 LB = vec2(-1, -1); +const vec2 L = vec2(-1, 0); +const vec2 R = vec2(1, 0); +const vec2 T = vec2(0, 1); +const vec2 B = vec2(0, -1); + +varying vec2 textureUV; + +uniform sampler2D diffuse; +uniform sampler2D normals; +uniform sampler2D depths; +uniform vec2 projection; +uniform vec2 resolution; +uniform vec2 barriers; // x = normal, y = depth +uniform vec2 weights; // x = normal, y = depth +uniform float kernel; // 0 = no aa, 1 = full aa + +float linearizeDepth(float depth) { + return -projection.y / (depth - projection.x); +} + +void main() { + // fragment and its neighbours + vec2 tc0 = textureUV; + vec2 tc1 = textureUV + LT / resolution; + vec2 tc2 = textureUV + RB / resolution; + vec2 tc3 = textureUV + RT / resolution; + vec2 tc4 = textureUV + LB / resolution; + vec2 tc5 = textureUV + L / resolution; + vec2 tc6 = textureUV + R / resolution; + vec2 tc7 = textureUV + T / resolution; + vec2 tc8 = textureUV + B / resolution; + + // normal discontinuity filter + vec3 nc = texture2D(normals, tc0).xyz * 2 - 1; + vec4 nd; + nd.x = dot(nc, texture2D(normals, tc1).xyz * 2 - 1); + nd.y = dot(nc, texture2D(normals, tc2).xyz * 2 - 1); + nd.z = dot(nc, texture2D(normals, tc3).xyz * 2 - 1); + nd.w = dot(nc, texture2D(normals, tc4).xyz * 2 - 1); + nd -= barriers.x; + nd = step(vec4(0, 0, 0, 0), nd); + float ne = clamp(dot(nd, vec4(weights.x, weights.x, weights.x, weights.x)), 0, 1); + + // depth gradient difference filter + float dc = linearizeDepth(texture2D(depths, tc0).x); + vec4 dd; + dd.x = linearizeDepth(texture2D(depths, tc1).x) + linearizeDepth(texture2D(depths, tc2).x); + dd.y = linearizeDepth(texture2D(depths, tc3).x) + linearizeDepth(texture2D(depths, tc4).x); + dd.z = linearizeDepth(texture2D(depths, tc5).x) + linearizeDepth(texture2D(depths, tc6).x); + dd.w = linearizeDepth(texture2D(depths, tc7).x) + linearizeDepth(texture2D(depths, tc8).x); + dd = abs(2 * dc - dd) - barriers.y; + dd = step(dd, vec4(0, 0, 0, 0)); + float de = clamp(dot(dd, vec4(weights.y, weights.y, weights.y, weights.y)), 0, 1); + + // combined weight + float w = (1 - de * ne) * kernel; + + // smoothed color + vec2 offset = tc0 * (1 - w); + vec4 s0 = texture2D(diffuse, offset + tc1 * w); + vec4 s1 = texture2D(diffuse, offset + tc2 * w); + vec4 s2 = texture2D(diffuse, offset + tc3 * w); + vec4 s3 = texture2D(diffuse, offset + tc4 * w); + gl_FragColor = (s0 + s1 + s2 + s3) / 4; +} diff --git a/src/main/resources/shaders/gl20/edaa.vert b/src/main/resources/shaders/gl20/edaa.vert new file mode 100644 index 0000000..dc26f39 --- /dev/null +++ b/src/main/resources/shaders/gl20/edaa.vert @@ -0,0 +1,15 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/fxaa.frag b/src/main/resources/shaders/gl20/fxaa.frag new file mode 100644 index 0000000..f4790a9 --- /dev/null +++ b/src/main/resources/shaders/gl20/fxaa.frag @@ -0,0 +1,60 @@ +// $shader_type: fragment + +// $texture_layout: diffuse = 0 + +#version 120 + +const float FXAA_REDUCE_MUL = 1.0 / 8.0; +const float FXAA_REDUCE_MIN = 1.0 / 128.0; +const vec2 NW = vec2(-1, -1); +const vec2 NE = vec2(1, -1); +const vec2 SW = vec2(-1, 1); +const vec2 SE = vec2(1, 1); +const vec3 LUMA = vec3(0.299, 0.587, 0.114); +const float ZERO_THIRDS_MINUS_HALF = 0.0 / 3.0 - 0.5; +const float ONE_THIRD_MINUS_HALF = 1.0 / 3.0 - 0.5; +const float TWO_THIRDS_MINUS_HALF = 2.0 / 3.0 - 0.5; +const float THREE_THIRDS_MINUS_HALF = 3.0 / 3.0 - 0.5; + +varying vec2 textureUV; + +uniform sampler2D diffuse; +uniform vec2 resolution; +uniform float maxSpan; + +void main() { + vec3 rgbNW = texture2D(diffuse, textureUV + NW / resolution).xyz; + vec3 rgbNE = texture2D(diffuse, textureUV + NE / resolution).xyz; + vec3 rgbSW = texture2D(diffuse, textureUV + SW / resolution).xyz; + vec3 rgbSE = texture2D(diffuse, textureUV + SE / resolution).xyz; + vec3 rgbM = texture2D(diffuse, textureUV).xyz; + + float lumaNW = dot(rgbNW, LUMA); + float lumaNE = dot(rgbNE, LUMA); + float lumaSW = dot(rgbSW, LUMA); + float lumaSE = dot(rgbSE, LUMA); + float lumaM = dot(rgbM, LUMA); + + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + vec2 dir; + dir.x = -lumaNW - lumaNE + lumaSW + lumaSE; + dir.y = lumaNW + lumaSW - lumaNE - lumaSE; + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 0.25 * FXAA_REDUCE_MUL, FXAA_REDUCE_MIN); + + float rcpDirMin = 1 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + + dir = min(vec2(maxSpan, maxSpan), max(vec2(-maxSpan, -maxSpan), dir * rcpDirMin)) / resolution; + + vec4 rgbA = 0.5 * (texture2D(diffuse, textureUV.xy + dir * ONE_THIRD_MINUS_HALF) + texture2D(diffuse, textureUV.xy + dir * TWO_THIRDS_MINUS_HALF)); + vec4 rgbB = rgbA * 0.5 + 0.25 * (texture2D(diffuse, textureUV.xy + dir * ZERO_THIRDS_MINUS_HALF) + texture2D(diffuse, textureUV.xy + dir * THREE_THIRDS_MINUS_HALF)); + float lumaB = dot(rgbB, LUMA); + + if (lumaB < lumaMin || lumaB > lumaMax) { + gl_FragColor = rgbA; + } else { + gl_FragColor = rgbB; + } +} diff --git a/src/main/resources/shaders/gl20/fxaa.vert b/src/main/resources/shaders/gl20/fxaa.vert new file mode 100644 index 0000000..dc26f39 --- /dev/null +++ b/src/main/resources/shaders/gl20/fxaa.vert @@ -0,0 +1,15 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/lighting.frag b/src/main/resources/shaders/gl20/lighting.frag new file mode 100644 index 0000000..6fa9152 --- /dev/null +++ b/src/main/resources/shaders/gl20/lighting.frag @@ -0,0 +1,54 @@ +// $shader_type: fragment + +// $texture_layout: colors = 0 +// $texture_layout: normals = 1 +// $texture_layout: depths = 2 +// $texture_layout: materials = 3 +// $texture_layout: occlusions = 4 +// $texture_layout: shadows = 5 + +#version 120 + +varying vec2 textureUV; +varying vec3 viewRay; +varying vec3 lightDirectionView; + +uniform sampler2D colors; +uniform sampler2D normals; +uniform sampler2D depths; +uniform sampler2D materials; +uniform sampler2D occlusions; +uniform sampler2D shadows; +uniform vec2 projection; +uniform float lightAttenuation; +uniform float spotCutoff; + +void main() { + gl_FragColor = texture2D(colors, textureUV); + + vec4 rawNormalView = texture2D(normals, textureUV); + if (rawNormalView.a <= 0) { + return; + } + + float occlusion = texture2D(occlusions, textureUV).r; + float shadow = texture2D(shadows, textureUV).r; + vec3 material = texture2D(materials, textureUV).rgb; + + float ambientTerm = material.z * occlusion; + float diffuseTerm = 0; + float specularTerm = 0; + + if (shadow > 0) { + vec3 normalView = normalize(rawNormalView.xyz * 2 - 1); + float normalDotLight = max(0, dot(normalView, -lightDirectionView)); + + diffuseTerm = material.x * shadow * normalDotLight; + + if (normalDotLight > 0) { + specularTerm = material.y * shadow * pow(max(0, dot(reflect(-lightDirectionView, normalView), normalize(viewRay))), 20); + } + } + + gl_FragColor.rgb *= (diffuseTerm + specularTerm + ambientTerm); +} diff --git a/src/main/resources/shaders/gl20/lighting.vert b/src/main/resources/shaders/gl20/lighting.vert new file mode 100644 index 0000000..b5359ef --- /dev/null +++ b/src/main/resources/shaders/gl20/lighting.vert @@ -0,0 +1,27 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; +varying vec3 viewRay; +varying vec3 lightDirectionView; + +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform vec3 lightDirection; +uniform float tanHalfFOV; +uniform float aspectRatio; + +void main() { + textureUV = (position.xy + 1) / 2; + + viewRay = vec3(position.x * tanHalfFOV * aspectRatio, position.y * tanHalfFOV, -1); + + lightDirectionView = normalize((normalMatrix * vec4(lightDirection, 1)).xyz); + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/motionBlur.frag b/src/main/resources/shaders/gl20/motionBlur.frag new file mode 100644 index 0000000..87d30f1 --- /dev/null +++ b/src/main/resources/shaders/gl20/motionBlur.frag @@ -0,0 +1,33 @@ +// $shader_type: fragment + +// $texture_layout: colors = 0 +// $texture_layout: velocities = 1 + +#version 120 + +varying vec2 textureUV; + +uniform sampler2D colors; +uniform sampler2D velocities; +uniform vec2 resolution; +uniform int sampleCount; +uniform float blurStrength; + +void main() { + vec4 color = texture2D(colors, textureUV); + if (color.a <= 0) { + gl_FragColor = color; + return; + } + + vec2 velocity = texture2D(velocities, textureUV).rg * blurStrength; + + float speed = length(resolution / velocity); + int samples = int(clamp(speed, 1, sampleCount)); + + for (int i = 1; i < samples; i++) { + color += texture2D(colors, textureUV + velocity * (float(i) / (samples - 1) - 0.5)); + } + + gl_FragColor = color / samples; +} diff --git a/src/main/resources/shaders/gl20/motionBlur.vert b/src/main/resources/shaders/gl20/motionBlur.vert new file mode 100644 index 0000000..dc26f39 --- /dev/null +++ b/src/main/resources/shaders/gl20/motionBlur.vert @@ -0,0 +1,15 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/screen.frag b/src/main/resources/shaders/gl20/screen.frag new file mode 100644 index 0000000..7d82761 --- /dev/null +++ b/src/main/resources/shaders/gl20/screen.frag @@ -0,0 +1,13 @@ +// $shader_type: fragment + +// $texture_layout: diffuse = 0 + +#version 120 + +varying vec2 textureUV; + +uniform sampler2D diffuse; + +void main() { + gl_FragColor = texture2D(diffuse, textureUV); +} diff --git a/src/main/resources/shaders/gl20/screen.vert b/src/main/resources/shaders/gl20/screen.vert new file mode 100644 index 0000000..dc26f39 --- /dev/null +++ b/src/main/resources/shaders/gl20/screen.vert @@ -0,0 +1,15 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/shadow.frag b/src/main/resources/shaders/gl20/shadow.frag new file mode 100644 index 0000000..201e6a0 --- /dev/null +++ b/src/main/resources/shaders/gl20/shadow.frag @@ -0,0 +1,70 @@ +// $shader_type: fragment + +// $texture_layout: normals = 0 +// $texture_layout: depths = 1 +// $texture_layout: lightDepths = 2 +// $texture_layout: noise = 3 + +#version 120 + +const int MAX_KERNEL_SIZE = 32; + +varying vec2 textureUV; +varying vec3 viewRay; +varying vec3 lightDirectionView; + +uniform sampler2D normals; +uniform sampler2D depths; +uniform sampler2DShadow lightDepths; +uniform sampler2D noise; +uniform mat4 inverseViewMatrix; +uniform mat4 lightViewMatrix; +uniform mat4 lightProjectionMatrix; +uniform vec2 projection; +uniform int kernelSize; +uniform vec2[MAX_KERNEL_SIZE] kernel; +uniform vec2 noiseScale; +uniform float bias; +uniform float radius; + +float linearizeDepth(float depth) { + return projection.y / (depth - projection.x); +} + +void main() { + vec4 rawNormalView = texture2D(normals, textureUV); + if (rawNormalView.a <= 0) { + return; + } + vec3 normalView = normalize(rawNormalView.xyz * 2 - 1); + + vec3 positionView = viewRay * linearizeDepth(texture2D(depths, textureUV).r); + + float normalDotLight = dot(normalView, -lightDirectionView); + + vec4 positionLightClip = lightProjectionMatrix * lightViewMatrix * inverseViewMatrix * vec4(positionView, 1); + positionLightClip.xyz = positionLightClip.xyz / positionLightClip.w * 0.5 + 0.5; + + if (positionLightClip.x < radius || positionLightClip.x > 1 - radius + || positionLightClip.y < radius || positionLightClip.y > 1 - radius + || positionLightClip.z < radius || positionLightClip.z > 1 - radius) { + gl_FragColor = vec4(1, 1, 1, 1); + return; + } + + float slopedBias = clamp(tan(acos(normalDotLight)) * bias, bias / 2, bias * 2); + + vec2 noiseVector = texture2D(noise, textureUV * noiseScale).xy * 2 - 1; + vec2 orthogonalVector = vec2(noiseVector.y, -noiseVector.x); + mat2 basis = mat2(noiseVector, orthogonalVector); + + float shadow; + for (int i = 0; i < kernelSize; i++) { + vec2 offsetPosition = positionLightClip.xy + basis * kernel[i] * radius; + shadow += shadow2D(lightDepths, vec3(offsetPosition, positionLightClip.z - slopedBias)).r; + } + + shadow /= kernelSize; + + gl_FragColor = vec4(shadow, shadow, shadow, 1); +} diff --git a/src/main/resources/shaders/gl20/shadow.vert b/src/main/resources/shaders/gl20/shadow.vert new file mode 100644 index 0000000..a35f051 --- /dev/null +++ b/src/main/resources/shaders/gl20/shadow.vert @@ -0,0 +1,26 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; +varying vec3 viewRay; +varying vec3 lightDirectionView; + +uniform mat4 normalMatrix; +uniform vec3 lightDirection; +uniform float tanHalfFOV; +uniform float aspectRatio; + +void main() { + textureUV = (position.xy + 1) / 2; + + viewRay = vec3(position.x * tanHalfFOV * aspectRatio, position.y * tanHalfFOV, -1); + + lightDirectionView = normalize((normalMatrix * vec4(lightDirection, 1)).xyz); + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/solid.frag b/src/main/resources/shaders/gl20/solid.frag new file mode 100644 index 0000000..a127d32 --- /dev/null +++ b/src/main/resources/shaders/gl20/solid.frag @@ -0,0 +1,24 @@ +// $shader_type: fragment + +#version 120 + +varying vec4 positionClip; +varying vec4 previousPositionClip; +varying vec3 normalView; + +uniform vec4 modelColor; +uniform float diffuseIntensity; +uniform float specularIntensity; +uniform float ambientIntensity; + +void main() { + gl_FragData[0] = modelColor; + + gl_FragData[1] = vec4((normalView + 1) / 2, 1); + + gl_FragData[2] = gl_FragData[1]; + + gl_FragData[3] = vec4(diffuseIntensity, specularIntensity, ambientIntensity, 1); + + gl_FragData[4] = vec4((positionClip.xy / positionClip.w - previousPositionClip.xy / previousPositionClip.w) * 0.5, 0, 1); +} diff --git a/src/main/resources/shaders/gl20/solid.vert b/src/main/resources/shaders/gl20/solid.vert new file mode 100644 index 0000000..825af8d --- /dev/null +++ b/src/main/resources/shaders/gl20/solid.vert @@ -0,0 +1,31 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 +// $attrib_layout: normal = 1 + +#version 120 + +attribute vec3 position; +attribute vec3 normal; + +varying vec4 positionClip; +varying vec4 previousPositionClip; +varying vec3 normalView; + +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform mat4 projectionMatrix; +uniform mat4 previousModelMatrix; +uniform mat4 previousViewMatrix; +uniform mat4 previousProjectionMatrix; + +void main() { + positionClip = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1); + + previousPositionClip = previousProjectionMatrix * previousViewMatrix * previousModelMatrix * vec4(position, 1); + + normalView = (normalMatrix * vec4(normal, 0)).xyz; + + gl_Position = positionClip; +} diff --git a/src/main/resources/shaders/gl20/ssao.frag b/src/main/resources/shaders/gl20/ssao.frag new file mode 100644 index 0000000..23cd233 --- /dev/null +++ b/src/main/resources/shaders/gl20/ssao.frag @@ -0,0 +1,77 @@ +// $shader_type: fragment + +// $texture_layout: normals = 0 +// $texture_layout: depths = 1 +// $texture_layout: noise = 2 + +#version 120 + +const int MAX_KERNEL_SIZE = 32; + +varying vec2 textureUV; +varying vec3 viewRay; + +uniform sampler2D normals; +uniform sampler2D depths; +uniform sampler2D noise; +uniform vec2 projection; +uniform mat4 projectionMatrix; +uniform int kernelSize; +uniform vec3[MAX_KERNEL_SIZE] kernel; +uniform float radius; +uniform float threshold; +uniform vec2 noiseScale; +uniform float power; + +float linearizeDepth(float depth) { + return projection.y / (depth - projection.x); +} + +void main() { + // Get the fragment's normal + vec4 rawNormal = texture2D(normals, textureUV); + if (rawNormal.a <= 0) { + gl_FragColor = vec4(1, 1, 1, 1); + return; + } + vec3 normal = normalize(rawNormal.xyz * 2 - 1); + + // Reconstruct the position of the fragment from the depth + float depth = linearizeDepth(texture2D(depths, textureUV).r); + vec3 origin = viewRay * depth; + + // Construct a change of basis matrix to reorient our sample kernel along the object's normal + // Extract the random vector from the noise texture + vec3 noiseVector = texture2D(noise, textureUV * noiseScale).xyz * 2 - 1; + + // Calculate the tangent and bi-tangent using Gram-Schmidt + vec3 tangent = normalize(noiseVector - normal * dot(noiseVector, normal)); + vec3 biTangent = cross(normal, tangent); + + // Create the kernel basis matrix + mat3 tbn = mat3(tangent, biTangent, normal); + + float occlusion = 0; + for (int i = 0; i < kernelSize; i++) { + // Get the sample position + vec3 sample = tbn * kernel[i]; + sample = sample * radius + origin; + + // Project the sample + vec4 offset = projectionMatrix * vec4(sample, 1); + offset.xy /= offset.w; + offset.xy = offset.xy * 0.5 + 0.5; + + // Get the sample depth + float sampleDepth = -linearizeDepth(texture2D(depths, offset.xy).r); + + // Range check and accumulate + float rangeCheck = smoothstep(0, 1, radius / abs(origin.z - sampleDepth)); + occlusion += rangeCheck * (sampleDepth - sample.z >= threshold ? 1 : 0); + } + + // Average and invert occlusion + occlusion = pow(1 - occlusion / kernelSize, power); + + gl_FragColor = vec4(occlusion, occlusion, occlusion, 1); +} diff --git a/src/main/resources/shaders/gl20/ssao.vert b/src/main/resources/shaders/gl20/ssao.vert new file mode 100644 index 0000000..3a953e1 --- /dev/null +++ b/src/main/resources/shaders/gl20/ssao.vert @@ -0,0 +1,21 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; +varying vec3 viewRay; + +uniform float tanHalfFOV; +uniform float aspectRatio; + +void main() { + textureUV = (position.xy + 1) / 2; + + viewRay = vec3(position.x * tanHalfFOV * aspectRatio, position.y * tanHalfFOV, -1); + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/textured.frag b/src/main/resources/shaders/gl20/textured.frag new file mode 100644 index 0000000..fd190df --- /dev/null +++ b/src/main/resources/shaders/gl20/textured.frag @@ -0,0 +1,33 @@ +// $shader_type: fragment + +// $texture_layout: diffuse = 0 +// $texture_layout: normals = 1 +// $texture_layout: specular = 2 + +#version 120 + +varying vec4 positionClip; +varying vec4 previousPositionClip; +varying vec3 normalView; +varying vec2 textureUV; +varying mat3 tangentMatrix; + +uniform sampler2D diffuse; +uniform sampler2D normals; +uniform sampler2D specular; +uniform float diffuseIntensity; +uniform float ambientIntensity; + +void main() { + gl_FragData[0] = texture2D(diffuse, textureUV); + + vec3 textureNormalView = tangentMatrix * (texture2D(normals, textureUV).xyz * 2 - 1); + gl_FragData[1] = vec4((textureNormalView + 1) / 2, 1); + + gl_FragData[2] = vec4((normalView + 1) / 2, 1); + + float specularIntensity = texture2D(specular, textureUV).r; + gl_FragData[3] = vec4(diffuseIntensity, specularIntensity, ambientIntensity, 1); + + gl_FragData[4] = vec4((positionClip.xy / positionClip.w - previousPositionClip.xy / previousPositionClip.w) * 0.5, 0, 1); +} diff --git a/src/main/resources/shaders/gl20/textured.vert b/src/main/resources/shaders/gl20/textured.vert new file mode 100644 index 0000000..29bfe30 --- /dev/null +++ b/src/main/resources/shaders/gl20/textured.vert @@ -0,0 +1,42 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 +// $attrib_layout: normal = 1 +// $attrib_layout: textureCoords = 2 +// $attrib_layout: tangent = 3 + +#version 120 + +attribute vec3 position; +attribute vec3 normal; +attribute vec2 textureCoords; +attribute vec4 tangent; + +varying vec4 positionClip; +varying vec4 previousPositionClip; +varying vec3 normalView; +varying vec2 textureUV; +varying mat3 tangentMatrix; + +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform mat4 projectionMatrix; +uniform mat4 previousModelMatrix; +uniform mat4 previousViewMatrix; +uniform mat4 previousProjectionMatrix; + +void main() { + positionClip = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1); + + previousPositionClip = previousProjectionMatrix * previousViewMatrix * previousModelMatrix * vec4(position, 1); + + textureUV = textureCoords; + + normalView = (normalMatrix * vec4(normal, 0)).xyz; + vec3 tangentView = (normalMatrix * vec4(tangent.xyz, 0)).xyz; + vec3 biTangentView = cross(normalView, tangentView) * tangent.w; + tangentMatrix = mat3(tangentView, biTangentView, normalView); + + gl_Position = positionClip; +} diff --git a/src/main/resources/shaders/gl20/transparencyBlending.frag b/src/main/resources/shaders/gl20/transparencyBlending.frag new file mode 100644 index 0000000..3f3cb52 --- /dev/null +++ b/src/main/resources/shaders/gl20/transparencyBlending.frag @@ -0,0 +1,28 @@ +// $shader_type: fragment + +// $texture_layout: weightedColor = 0 +// $texture_layout: weightedVelocity = 1 +// $texture_layout: layerCount = 2 + +#version 120 + +varying vec2 textureUV; + +uniform sampler2D weightedColor; +uniform sampler2D weightedVelocity; +uniform sampler2D layerCount; + +void main() { + vec4 colorSum = texture2D(weightedColor, textureUV); + vec2 velocitySum = texture2D(weightedVelocity, textureUV).rg; + float count = texture2D(layerCount, textureUV).r; + + if (count < 0.00001 || colorSum.a < 0.00001) { + discard; + } + + float destinationAlpha = pow(max(0, 1 - colorSum.a / count), count); + + gl_FragData[0] = vec4(colorSum.rgb / colorSum.a, destinationAlpha); + gl_FragData[1] = vec4(velocitySum.rg / colorSum.a, 0, destinationAlpha); +} diff --git a/src/main/resources/shaders/gl20/transparencyBlending.vert b/src/main/resources/shaders/gl20/transparencyBlending.vert new file mode 100644 index 0000000..dc26f39 --- /dev/null +++ b/src/main/resources/shaders/gl20/transparencyBlending.vert @@ -0,0 +1,15 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 + +#version 120 + +attribute vec3 position; + +varying vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl20/weightedSum.frag b/src/main/resources/shaders/gl20/weightedSum.frag new file mode 100644 index 0000000..6ce0dfc --- /dev/null +++ b/src/main/resources/shaders/gl20/weightedSum.frag @@ -0,0 +1,40 @@ +// $shader_type: fragment + +#version 120 + +varying vec3 positionView; +varying vec3 normalView; +varying vec3 lightDirectionView; +varying vec4 positionClip; +varying vec4 previousPositionClip; + +uniform vec4 modelColor; +uniform float diffuseIntensity; +uniform float specularIntensity; +uniform float ambientIntensity; + +void main() { + vec3 forwardNormalView = faceforward(normalView, lightDirectionView, normalView); + + float ambientTerm = ambientIntensity; + + float diffuseTerm; + float normalDotLight = max(0, dot(forwardNormalView, -lightDirectionView)); + diffuseTerm = normalDotLight; + + float specularTerm; + if (normalDotLight > 0) { + specularTerm = pow(max(0, dot(reflect(-lightDirectionView, forwardNormalView), normalize(positionView))), 20); + } else { + specularTerm = 0; + } + + vec4 color = modelColor; + color.rgb *= (diffuseTerm + specularTerm + ambientTerm) * color.a; + + vec2 velocity = (positionClip.xy / positionClip.w - previousPositionClip.xy / previousPositionClip.w) * 0.5 * color.a; + + gl_FragData[0] = color; + gl_FragData[1] = vec4(velocity, 0, 1); + gl_FragData[2] = vec4(1, 1, 1, 1); +} diff --git a/src/main/resources/shaders/gl20/weightedSum.vert b/src/main/resources/shaders/gl20/weightedSum.vert new file mode 100644 index 0000000..de63137 --- /dev/null +++ b/src/main/resources/shaders/gl20/weightedSum.vert @@ -0,0 +1,38 @@ +// $shader_type: vertex + +// $attrib_layout: position = 0 +// $attrib_layout: normal = 1 + +#version 120 + +attribute vec3 position; +attribute vec3 normal; + +varying vec3 positionView; +varying vec3 normalView; +varying vec3 lightDirectionView; +varying vec4 positionClip; +varying vec4 previousPositionClip; + +uniform vec3 lightDirection; +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform mat4 projectionMatrix; +uniform mat4 previousModelMatrix; +uniform mat4 previousViewMatrix; +uniform mat4 previousProjectionMatrix; + +void main() { + normalView = (normalMatrix * vec4(normal, 0)).xyz; + + lightDirectionView = (normalMatrix * vec4(lightDirection, 0)).xyz; + + positionView = (viewMatrix * modelMatrix * vec4(position, 1)).xyz; + + positionClip = projectionMatrix * vec4(positionView, 1); + + previousPositionClip = previousProjectionMatrix * previousViewMatrix * previousModelMatrix * vec4(position, 1); + + gl_Position = positionClip; +} diff --git a/src/main/resources/shaders/gl30/blur.frag b/src/main/resources/shaders/gl30/blur.frag new file mode 100644 index 0000000..a98ffa0 --- /dev/null +++ b/src/main/resources/shaders/gl30/blur.frag @@ -0,0 +1,34 @@ +// $shader_type: fragment + +// $texture_layout: occlusions = 0 +// $texture_layout: shadows = 1 + +#version 330 + +in vec2 textureUV; + +layout(location = 0) out float outputOcclusion; +layout(location = 1) out float outputShadow; + +uniform sampler2D occlusions; +uniform sampler2D shadows; +uniform int blurSize; +uniform vec2 texelSize; + +void main() { + float blurredOcclusion = 0; + float blurredShadow = 0; + float halfBlurSize = float(blurSize) / 2; + int blurStart = int(-floor(halfBlurSize)); + int blurEnd = int(ceil(halfBlurSize)); + for (int x = blurStart; x < blurEnd; x++) { + for (int y = blurStart; y < blurEnd; y++) { + vec2 adjacentUV = textureUV + vec2(x * texelSize.x, y * texelSize.y); + blurredOcclusion += texture(occlusions, adjacentUV).r; + blurredShadow += texture(shadows, adjacentUV).r; + } + } + float blurSizeSquared = blurSize * blurSize; + outputOcclusion = blurredOcclusion / blurSizeSquared; + outputShadow = blurredShadow / blurSizeSquared; +} diff --git a/src/main/resources/shaders/gl30/blur.vert b/src/main/resources/shaders/gl30/blur.vert new file mode 100644 index 0000000..9f71ce6 --- /dev/null +++ b/src/main/resources/shaders/gl30/blur.vert @@ -0,0 +1,13 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/edaa.frag b/src/main/resources/shaders/gl30/edaa.frag new file mode 100644 index 0000000..cd47e15 --- /dev/null +++ b/src/main/resources/shaders/gl30/edaa.frag @@ -0,0 +1,82 @@ +// Edge detection anti aliasing +// Adapted from: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter09.html (Example 9-2) + +// $shader_type: fragment + +// $texture_layout: diffuse = 0 +// $texture_layout: normals = 1 +// $texture_layout: depths = 2 + +#version 330 + +const vec2 LT = vec2(-1, 1); +const vec2 RB = vec2(1, -1); +const vec2 RT = vec2(1, 1); +const vec2 LB = vec2(-1, -1); +const vec2 L = vec2(-1, 0); +const vec2 R = vec2(1, 0); +const vec2 T = vec2(0, 1); +const vec2 B = vec2(0, -1); + +in vec2 textureUV; + +layout(location = 0) out vec4 outputColor; + +uniform sampler2D diffuse; +uniform sampler2D normals; +uniform sampler2D depths; +uniform vec2 projection; +uniform vec2 resolution; +uniform vec2 barriers; // x = normal, y = depth +uniform vec2 weights; // x = normal, y = depth +uniform float kernel; // 0 = no aa, 1 = full aa + +float linearizeDepth(float depth) { + return -projection.y / (depth - projection.x); +} + +void main() { + // fragment and its neighbours + vec2 tc0 = textureUV; + vec2 tc1 = textureUV + LT / resolution; + vec2 tc2 = textureUV + RB / resolution; + vec2 tc3 = textureUV + RT / resolution; + vec2 tc4 = textureUV + LB / resolution; + vec2 tc5 = textureUV + L / resolution; + vec2 tc6 = textureUV + R / resolution; + vec2 tc7 = textureUV + T / resolution; + vec2 tc8 = textureUV + B / resolution; + + // normal discontinuity filter + vec3 nc = texture(normals, tc0).xyz * 2 - 1; + vec4 nd; + nd.x = dot(nc, texture(normals, tc1).xyz * 2 - 1); + nd.y = dot(nc, texture(normals, tc2).xyz * 2 - 1); + nd.z = dot(nc, texture(normals, tc3).xyz * 2 - 1); + nd.w = dot(nc, texture(normals, tc4).xyz * 2 - 1); + nd -= barriers.x; + nd = step(vec4(0, 0, 0, 0), nd); + float ne = clamp(dot(nd, vec4(weights.x, weights.x, weights.x, weights.x)), 0, 1); + + // depth gradient difference filter + float dc = linearizeDepth(texture(depths, tc0).x); + vec4 dd; + dd.x = linearizeDepth(texture(depths, tc1).x) + linearizeDepth(texture(depths, tc2).x); + dd.y = linearizeDepth(texture(depths, tc3).x) + linearizeDepth(texture(depths, tc4).x); + dd.z = linearizeDepth(texture(depths, tc5).x) + linearizeDepth(texture(depths, tc6).x); + dd.w = linearizeDepth(texture(depths, tc7).x) + linearizeDepth(texture(depths, tc8).x); + dd = abs(2 * dc - dd) - barriers.y; + dd = step(dd, vec4(0, 0, 0, 0)); + float de = clamp(dot(dd, vec4(weights.y, weights.y, weights.y, weights.y)), 0, 1); + + // combined weight + float w = (1 - de * ne) * kernel; + + // smoothed color + vec2 offset = tc0 * (1 - w); + vec4 s0 = texture(diffuse, offset + tc1 * w); + vec4 s1 = texture(diffuse, offset + tc2 * w); + vec4 s2 = texture(diffuse, offset + tc3 * w); + vec4 s3 = texture(diffuse, offset + tc4 * w); + outputColor = (s0 + s1 + s2 + s3) / 4; +} diff --git a/src/main/resources/shaders/gl30/edaa.vert b/src/main/resources/shaders/gl30/edaa.vert new file mode 100644 index 0000000..9f71ce6 --- /dev/null +++ b/src/main/resources/shaders/gl30/edaa.vert @@ -0,0 +1,13 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/fxaa.frag b/src/main/resources/shaders/gl30/fxaa.frag new file mode 100644 index 0000000..78ec725 --- /dev/null +++ b/src/main/resources/shaders/gl30/fxaa.frag @@ -0,0 +1,62 @@ +// $shader_type: fragment + +// $texture_layout: diffuse = 0 + +#version 330 + +const float FXAA_REDUCE_MUL = 1.0 / 8.0; +const float FXAA_REDUCE_MIN = 1.0 / 128.0; +const vec2 NW = vec2(-1, -1); +const vec2 NE = vec2(1, -1); +const vec2 SW = vec2(-1, 1); +const vec2 SE = vec2(1, 1); +const vec3 LUMA = vec3(0.299, 0.587, 0.114); +const float ZERO_THIRDS_MINUS_HALF = 0.0 / 3.0 - 0.5; +const float ONE_THIRD_MINUS_HALF = 1.0 / 3.0 - 0.5; +const float TWO_THIRDS_MINUS_HALF = 2.0 / 3.0 - 0.5; +const float THREE_THIRDS_MINUS_HALF = 3.0 / 3.0 - 0.5; + +in vec2 textureUV; + +layout(location = 0) out vec4 outputColor; + +uniform sampler2D diffuse; +uniform vec2 resolution; +uniform float maxSpan; + +void main() { + vec3 rgbNW = texture(diffuse, textureUV + NW / resolution).xyz; + vec3 rgbNE = texture(diffuse, textureUV + NE / resolution).xyz; + vec3 rgbSW = texture(diffuse, textureUV + SW / resolution).xyz; + vec3 rgbSE = texture(diffuse, textureUV + SE / resolution).xyz; + vec3 rgbM = texture(diffuse, textureUV).xyz; + + float lumaNW = dot(rgbNW, LUMA); + float lumaNE = dot(rgbNE, LUMA); + float lumaSW = dot(rgbSW, LUMA); + float lumaSE = dot(rgbSE, LUMA); + float lumaM = dot(rgbM, LUMA); + + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + vec2 dir; + dir.x = -lumaNW - lumaNE + lumaSW + lumaSE; + dir.y = lumaNW + lumaSW - lumaNE - lumaSE; + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 0.25 * FXAA_REDUCE_MUL, FXAA_REDUCE_MIN); + + float rcpDirMin = 1 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + + dir = min(vec2(maxSpan, maxSpan), max(vec2(-maxSpan, -maxSpan), dir * rcpDirMin)) / resolution; + + vec4 rgbA = 0.5 * (texture(diffuse, textureUV.xy + dir * ONE_THIRD_MINUS_HALF) + texture(diffuse, textureUV.xy + dir * TWO_THIRDS_MINUS_HALF)); + vec4 rgbB = rgbA * 0.5 + 0.25 * (texture(diffuse, textureUV.xy + dir * ZERO_THIRDS_MINUS_HALF) + texture(diffuse, textureUV.xy + dir * THREE_THIRDS_MINUS_HALF)); + float lumaB = dot(rgbB, LUMA); + + if (lumaB < lumaMin || lumaB > lumaMax) { + outputColor = rgbA; + } else { + outputColor = rgbB; + } +} diff --git a/src/main/resources/shaders/gl30/fxaa.vert b/src/main/resources/shaders/gl30/fxaa.vert new file mode 100644 index 0000000..9f71ce6 --- /dev/null +++ b/src/main/resources/shaders/gl30/fxaa.vert @@ -0,0 +1,13 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/lighting.frag b/src/main/resources/shaders/gl30/lighting.frag new file mode 100644 index 0000000..6ee42cb --- /dev/null +++ b/src/main/resources/shaders/gl30/lighting.frag @@ -0,0 +1,53 @@ +// $shader_type: fragment + +// $texture_layout: colors = 0 +// $texture_layout: normals = 1 +// $texture_layout: depths = 2 +// $texture_layout: materials = 3 +// $texture_layout: occlusions = 4 +// $texture_layout: shadows = 5 + +#version 330 + +in vec2 textureUV; +noperspective in vec3 viewRay; +in vec3 lightDirectionView; + +layout(location = 0) out vec4 outputColor; + +uniform sampler2D colors; +uniform sampler2D normals; +uniform sampler2D depths; +uniform sampler2D materials; +uniform sampler2D occlusions; +uniform sampler2D shadows; + +void main() { + outputColor = texture(colors, textureUV); + + vec4 rawNormalView = texture(normals, textureUV); + if (rawNormalView.a <= 0) { + return; + } + + float occlusion = texture(occlusions, textureUV).r; + float shadow = texture(shadows, textureUV).r; + vec3 material = texture(materials, textureUV).rgb; + + float ambientTerm = material.z * occlusion; + float diffuseTerm = 0; + float specularTerm = 0; + + if (shadow > 0) { + vec3 normalView = normalize(rawNormalView.xyz * 2 - 1); + float normalDotLight = max(0, dot(normalView, -lightDirectionView)); + + diffuseTerm = material.x * shadow * normalDotLight; + + if (normalDotLight > 0) { + specularTerm = material.y * shadow * pow(max(0, dot(reflect(-lightDirectionView, normalView), normalize(viewRay))), 20); + } + } + + outputColor.rgb *= (diffuseTerm + specularTerm + ambientTerm); +} diff --git a/src/main/resources/shaders/gl30/lighting.vert b/src/main/resources/shaders/gl30/lighting.vert new file mode 100644 index 0000000..73fdce7 --- /dev/null +++ b/src/main/resources/shaders/gl30/lighting.vert @@ -0,0 +1,25 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; +noperspective out vec3 viewRay; +out vec3 lightDirectionView; + +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform vec3 lightDirection; +uniform float tanHalfFOV; +uniform float aspectRatio; + +void main() { + textureUV = (position.xy + 1) / 2; + + viewRay = vec3(position.x * tanHalfFOV * aspectRatio, position.y * tanHalfFOV, -1); + + lightDirectionView = normalize((normalMatrix * vec4(lightDirection, 1)).xyz); + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/motionBlur.frag b/src/main/resources/shaders/gl30/motionBlur.frag new file mode 100644 index 0000000..19a03d0 --- /dev/null +++ b/src/main/resources/shaders/gl30/motionBlur.frag @@ -0,0 +1,35 @@ +// $shader_type: fragment + +// $texture_layout: colors = 0 +// $texture_layout: velocities = 1 + +#version 330 + +in vec2 textureUV; + +layout(location = 0) out vec4 outputColor; + +uniform sampler2D colors; +uniform sampler2D velocities; +uniform vec2 resolution; +uniform int sampleCount; +uniform float blurStrength; + +void main() { + vec4 color = texture(colors, textureUV); + if (color.a <= 0) { + outputColor = color; + return; + } + + vec2 velocity = texture(velocities, textureUV).rg * blurStrength; + + float speed = length(resolution / velocity); + int samples = clamp(int(speed), 1, sampleCount); + + for (int i = 1; i < samples; i++) { + color += texture(colors, textureUV + velocity * (float(i) / (samples - 1) - 0.5)); + } + + outputColor = color / samples; +} diff --git a/src/main/resources/shaders/gl30/motionBlur.vert b/src/main/resources/shaders/gl30/motionBlur.vert new file mode 100644 index 0000000..9f71ce6 --- /dev/null +++ b/src/main/resources/shaders/gl30/motionBlur.vert @@ -0,0 +1,13 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/screen.frag b/src/main/resources/shaders/gl30/screen.frag new file mode 100644 index 0000000..8eb6b26 --- /dev/null +++ b/src/main/resources/shaders/gl30/screen.frag @@ -0,0 +1,15 @@ +// $shader_type: fragment + +// $texture_layout: diffuse = 0 + +#version 330 + +in vec2 textureUV; + +layout(location = 0) out vec4 outputColor; + +uniform sampler2D diffuse; + +void main() { + outputColor = texture(diffuse, textureUV); +} diff --git a/src/main/resources/shaders/gl30/screen.vert b/src/main/resources/shaders/gl30/screen.vert new file mode 100644 index 0000000..9f71ce6 --- /dev/null +++ b/src/main/resources/shaders/gl30/screen.vert @@ -0,0 +1,13 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/shadow.frag b/src/main/resources/shaders/gl30/shadow.frag new file mode 100644 index 0000000..d400e05 --- /dev/null +++ b/src/main/resources/shaders/gl30/shadow.frag @@ -0,0 +1,70 @@ +// $shader_type: fragment + +// $texture_layout: normals = 0 +// $texture_layout: depths = 1 +// $texture_layout: lightDepths = 2 +// $texture_layout: noise = 3 + +#version 330 + +const int MAX_KERNEL_SIZE = 32; + +in vec2 textureUV; +noperspective in vec3 viewRay; +in vec3 lightDirectionView; + +layout(location = 0) out float outputShadow; + +uniform sampler2D normals; +uniform sampler2D depths; +uniform sampler2DShadow lightDepths; +uniform sampler2D noise; +uniform mat4 inverseViewMatrix; +uniform mat4 lightViewMatrix; +uniform mat4 lightProjectionMatrix; +uniform vec2 projection; +uniform int kernelSize; +uniform vec2[MAX_KERNEL_SIZE] kernel; +uniform vec2 noiseScale; +uniform float bias; +uniform float radius; + +float linearizeDepth(float depth) { + return projection.y / (depth - projection.x); +} + +void main() { + vec4 rawNormalView = texture(normals, textureUV); + if (rawNormalView.a <= 0) { + return; + } + vec3 normalView = normalize(rawNormalView.xyz * 2 - 1); + + vec3 positionView = viewRay * linearizeDepth(texture(depths, textureUV).r); + + float normalDotLight = dot(normalView, -lightDirectionView); + + vec4 positionLightClip = lightProjectionMatrix * lightViewMatrix * inverseViewMatrix * vec4(positionView, 1); + positionLightClip.xyz = positionLightClip.xyz / positionLightClip.w * 0.5 + 0.5; + + if (positionLightClip.x < radius || positionLightClip.x > 1 - radius + || positionLightClip.y < radius || positionLightClip.y > 1 - radius + || positionLightClip.z < radius || positionLightClip.z > 1 - radius) { + outputShadow = 1; + return; + } + + float slopedBias = clamp(tan(acos(normalDotLight)) * bias, bias / 2, bias * 2); + + vec2 noiseVector = texture(noise, textureUV * noiseScale).xy * 2 - 1; + vec2 orthogonalVector = vec2(noiseVector.y, -noiseVector.x); + mat2 basis = mat2(noiseVector, orthogonalVector); + + float shadow; + for (int i = 0; i < kernelSize; i++) { + vec2 offsetPosition = positionLightClip.xy + basis * kernel[i] * radius; + shadow += texture(lightDepths, vec3(offsetPosition, positionLightClip.z - slopedBias)); + } + + outputShadow = shadow / kernelSize; +} diff --git a/src/main/resources/shaders/gl30/shadow.vert b/src/main/resources/shaders/gl30/shadow.vert new file mode 100644 index 0000000..7217168 --- /dev/null +++ b/src/main/resources/shaders/gl30/shadow.vert @@ -0,0 +1,24 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; +noperspective out vec3 viewRay; +out vec3 lightDirectionView; + +uniform mat4 normalMatrix; +uniform vec3 lightDirection; +uniform float tanHalfFOV; +uniform float aspectRatio; + +void main() { + textureUV = (position.xy + 1) / 2; + + viewRay = vec3(position.x * tanHalfFOV * aspectRatio, position.y * tanHalfFOV, -1); + + lightDirectionView = normalize((normalMatrix * vec4(lightDirection, 1)).xyz); + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/solid.frag b/src/main/resources/shaders/gl30/solid.frag new file mode 100644 index 0000000..1278297 --- /dev/null +++ b/src/main/resources/shaders/gl30/solid.frag @@ -0,0 +1,30 @@ +// $shader_type: fragment + +#version 330 + +in vec4 positionClip; +in vec4 previousPositionClip; +in vec3 normalView; + +layout(location = 0) out vec4 outputColor; +layout(location = 1) out vec4 outputNormal; +layout(location = 2) out vec4 outputVertexNormal; +layout(location = 3) out vec3 outputMaterial; +layout(location = 4) out vec2 outputVelocity; + +uniform vec4 modelColor; +uniform float diffuseIntensity; +uniform float specularIntensity; +uniform float ambientIntensity; + +void main() { + outputColor = modelColor; + + outputNormal = vec4((normalView + 1) / 2, 1); + + outputVertexNormal = outputNormal; + + outputMaterial = vec3(diffuseIntensity, specularIntensity, ambientIntensity); + + outputVelocity = (positionClip.xy / positionClip.w - previousPositionClip.xy / previousPositionClip.w) * 0.5; +} diff --git a/src/main/resources/shaders/gl30/solid.vert b/src/main/resources/shaders/gl30/solid.vert new file mode 100644 index 0000000..5532c56 --- /dev/null +++ b/src/main/resources/shaders/gl30/solid.vert @@ -0,0 +1,28 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; + +out vec4 positionClip; +out vec4 previousPositionClip; +out vec3 normalView; + +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform mat4 projectionMatrix; +uniform mat4 previousModelMatrix; +uniform mat4 previousViewMatrix; +uniform mat4 previousProjectionMatrix; + +void main() { + positionClip = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1); + + previousPositionClip = previousProjectionMatrix * previousViewMatrix * previousModelMatrix * vec4(position, 1); + + normalView = (normalMatrix * vec4(normal, 0)).xyz; + + gl_Position = positionClip; +} diff --git a/src/main/resources/shaders/gl30/ssao.frag b/src/main/resources/shaders/gl30/ssao.frag new file mode 100644 index 0000000..2e176de --- /dev/null +++ b/src/main/resources/shaders/gl30/ssao.frag @@ -0,0 +1,77 @@ +// $shader_type: fragment + +// $texture_layout: normals = 0 +// $texture_layout: depths = 1 +// $texture_layout: noise = 2 + +#version 330 + +const int MAX_KERNEL_SIZE = 32; + +in vec2 textureUV; +noperspective in vec3 viewRay; + +layout(location = 0) out float outputOcclusion; + +uniform sampler2D normals; +uniform sampler2D depths; +uniform sampler2D noise; +uniform vec2 projection; +uniform mat4 projectionMatrix; +uniform int kernelSize; +uniform vec3[MAX_KERNEL_SIZE] kernel; +uniform float radius; +uniform float threshold; +uniform vec2 noiseScale; +uniform float power; + +float linearizeDepth(float depth) { + return projection.y / (depth - projection.x); +} + +void main() { + // Get the fragment's normal + vec4 rawNormal = texture(normals, textureUV); + if (rawNormal.a <= 0) { + outputOcclusion = 1; + return; + } + vec3 normal = normalize(rawNormal.xyz * 2 - 1); + + // Reconstruct the position of the fragment from the depth + float depth = linearizeDepth(texture(depths, textureUV).r); + vec3 origin = viewRay * depth; + + // Construct a change of basis matrix to reorient our sample kernel along the object's normal + // Extract the random vector from the noise texture + vec3 noiseVector = texture(noise, textureUV * noiseScale).xyz * 2 - 1; + + // Calculate the tangent and bi-tangent using Gram-Schmidt + vec3 tangent = normalize(noiseVector - normal * dot(noiseVector, normal)); + vec3 biTangent = cross(normal, tangent); + + // Create the kernel basis matrix + mat3 tbn = mat3(tangent, biTangent, normal); + + float occlusion = 0; + for (int i = 0; i < kernelSize; i++) { + // Get the sample position + vec3 sample = tbn * kernel[i]; + sample = sample * radius + origin; + + // Project the sample + vec4 offset = projectionMatrix * vec4(sample, 1); + offset.xy /= offset.w; + offset.xy = offset.xy * 0.5 + 0.5; + + // Get the sample depth + float sampleDepth = -linearizeDepth(texture(depths, offset.xy).r); + + // Range check and accumulate + float rangeCheck = smoothstep(0, 1, radius / abs(origin.z - sampleDepth)); + occlusion += rangeCheck * (sampleDepth - sample.z >= threshold ? 1 : 0); + } + + // Average and invert occlusion + outputOcclusion = pow(1 - occlusion / kernelSize, power); +} diff --git a/src/main/resources/shaders/gl30/ssao.vert b/src/main/resources/shaders/gl30/ssao.vert new file mode 100644 index 0000000..e8b8b08 --- /dev/null +++ b/src/main/resources/shaders/gl30/ssao.vert @@ -0,0 +1,19 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; +noperspective out vec3 viewRay; + +uniform float tanHalfFOV; +uniform float aspectRatio; + +void main() { + textureUV = (position.xy + 1) / 2; + + viewRay = vec3(position.x * tanHalfFOV * aspectRatio, position.y * tanHalfFOV, -1); + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/textured.frag b/src/main/resources/shaders/gl30/textured.frag new file mode 100644 index 0000000..a67408a --- /dev/null +++ b/src/main/resources/shaders/gl30/textured.frag @@ -0,0 +1,39 @@ +// $shader_type: fragment + +// $texture_layout: diffuse = 0 +// $texture_layout: normals = 1 +// $texture_layout: specular = 2 + +#version 330 + +in vec4 positionClip; +in vec4 previousPositionClip; +in vec3 normalView; +in vec2 textureUV; +in mat3 tangentMatrix; + +layout(location = 0) out vec4 outputColor; +layout(location = 1) out vec4 outputNormal; +layout(location = 2) out vec4 outputVertexNormal; +layout(location = 3) out vec3 outputMaterial; +layout(location = 4) out vec2 outputVelocity; + +uniform sampler2D diffuse; +uniform sampler2D normals; +uniform sampler2D specular; +uniform float diffuseIntensity; +uniform float ambientIntensity; + +void main() { + outputColor = texture(diffuse, textureUV); + + vec3 textureNormalView = tangentMatrix * (texture(normals, textureUV).xyz * 2 - 1); + outputNormal = vec4((textureNormalView + 1) / 2, 1); + + outputVertexNormal = vec4((normalView + 1) / 2, 1); + + float specularIntensity = texture(specular, textureUV).r; + outputMaterial = vec3(diffuseIntensity, specularIntensity, ambientIntensity); + + outputVelocity = (positionClip.xy / positionClip.w - previousPositionClip.xy / previousPositionClip.w) * 0.5; +} diff --git a/src/main/resources/shaders/gl30/textured.vert b/src/main/resources/shaders/gl30/textured.vert new file mode 100644 index 0000000..c9ded3f --- /dev/null +++ b/src/main/resources/shaders/gl30/textured.vert @@ -0,0 +1,37 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec2 textureCoords; +layout(location = 3) in vec4 tangent; + +out vec4 positionClip; +out vec4 previousPositionClip; +out vec3 normalView; +out vec2 textureUV; +out mat3 tangentMatrix; + +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform mat4 projectionMatrix; +uniform mat4 previousModelMatrix; +uniform mat4 previousViewMatrix; +uniform mat4 previousProjectionMatrix; + +void main() { + positionClip = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1); + + previousPositionClip = previousProjectionMatrix * previousViewMatrix * previousModelMatrix * vec4(position, 1); + + textureUV = textureCoords; + + normalView = (normalMatrix * vec4(normal, 0)).xyz; + vec3 tangentView = (normalMatrix * vec4(tangent.xyz, 0)).xyz; + vec3 biTangentView = cross(normalView, tangentView) * tangent.w; + tangentMatrix = mat3(tangentView, biTangentView, normalView); + + gl_Position = positionClip; +} diff --git a/src/main/resources/shaders/gl30/transparencyBlending.frag b/src/main/resources/shaders/gl30/transparencyBlending.frag new file mode 100644 index 0000000..e7c4644 --- /dev/null +++ b/src/main/resources/shaders/gl30/transparencyBlending.frag @@ -0,0 +1,31 @@ +// $shader_type: fragment + +// $texture_layout: weightedColor = 0 +// $texture_layout: weightedVelocity = 1 +// $texture_layout: layerCount = 2 + +#version 330 + +in vec2 textureUV; + +layout(location = 0) out vec4 outputColor; +layout(location = 1) out vec4 outputVelocity; + +uniform sampler2D weightedColor; +uniform sampler2D weightedVelocity; +uniform sampler2D layerCount; + +void main() { + vec4 colorSum = texture(weightedColor, textureUV); + vec2 velocitySum = texture(weightedVelocity, textureUV).rg; + float count = texture(layerCount, textureUV).r; + + if (count < 0.00001 || colorSum.a < 0.00001) { + discard; + } + + float destinationAlpha = pow(max(0, 1 - colorSum.a / count), count); + + outputColor = vec4(colorSum.rgb / colorSum.a, destinationAlpha); + outputVelocity = vec4(velocitySum.rg / colorSum.a, 0, destinationAlpha); +} diff --git a/src/main/resources/shaders/gl30/transparencyBlending.vert b/src/main/resources/shaders/gl30/transparencyBlending.vert new file mode 100644 index 0000000..9f71ce6 --- /dev/null +++ b/src/main/resources/shaders/gl30/transparencyBlending.vert @@ -0,0 +1,13 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; + +out vec2 textureUV; + +void main() { + textureUV = (position.xy + 1) / 2; + + gl_Position = vec4(position, 1); +} diff --git a/src/main/resources/shaders/gl30/weightedSum.frag b/src/main/resources/shaders/gl30/weightedSum.frag new file mode 100644 index 0000000..c98137a --- /dev/null +++ b/src/main/resources/shaders/gl30/weightedSum.frag @@ -0,0 +1,44 @@ +// $shader_type: fragment + +#version 330 + +in vec3 positionView; +in vec3 normalView; +in vec3 lightDirectionView; +in vec4 positionClip; +in vec4 previousPositionClip; + +layout(location = 0) out vec4 outputWeightedColor; +layout(location = 1) out vec2 outputWeightedVelocity; +layout(location = 2) out float outputLayerCount; + +uniform vec4 modelColor; +uniform float diffuseIntensity; +uniform float specularIntensity; +uniform float ambientIntensity; + +void main() { + vec3 forwardNormalView = faceforward(normalView, lightDirectionView, normalView); + + float ambientTerm = ambientIntensity; + + float diffuseTerm; + float normalDotLight = max(0, dot(forwardNormalView, -lightDirectionView)); + diffuseTerm = normalDotLight; + + float specularTerm; + if (normalDotLight > 0) { + specularTerm = pow(max(0, dot(reflect(-lightDirectionView, forwardNormalView), normalize(positionView))), 20); + } else { + specularTerm = 0; + } + + vec4 color = modelColor; + color.rgb *= (diffuseTerm + specularTerm + ambientTerm) * color.a; + + vec2 velocity = (positionClip.xy / positionClip.w - previousPositionClip.xy / previousPositionClip.w) * 0.5 * color.a; + + outputWeightedColor = color; + outputWeightedVelocity = velocity; + outputLayerCount = 1; +} diff --git a/src/main/resources/shaders/gl30/weightedSum.vert b/src/main/resources/shaders/gl30/weightedSum.vert new file mode 100644 index 0000000..e14419d --- /dev/null +++ b/src/main/resources/shaders/gl30/weightedSum.vert @@ -0,0 +1,35 @@ +// $shader_type: vertex + +#version 330 + +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; + +out vec3 positionView; +out vec3 normalView; +out vec3 lightDirectionView; +out vec4 positionClip; +out vec4 previousPositionClip; + +uniform vec3 lightDirection; +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform mat4 normalMatrix; +uniform mat4 projectionMatrix; +uniform mat4 previousModelMatrix; +uniform mat4 previousViewMatrix; +uniform mat4 previousProjectionMatrix; + +void main() { + normalView = (normalMatrix * vec4(normal, 0)).xyz; + + lightDirectionView = (normalMatrix * vec4(lightDirection, 0)).xyz; + + positionView = (viewMatrix * modelMatrix * vec4(position, 1)).xyz; + + positionClip = projectionMatrix * vec4(positionView, 1); + + previousPositionClip = previousProjectionMatrix * previousViewMatrix * previousModelMatrix * vec4(position, 1); + + gl_Position = positionClip; +} diff --git a/src/test/java/org/spout/spout2/AppTest.java b/src/test/java/org/spout/spout2/AppTest.java new file mode 100644 index 0000000..86c8dc9 --- /dev/null +++ b/src/test/java/org/spout/spout2/AppTest.java @@ -0,0 +1,38 @@ +package org.spout.spout2; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +}