org.spout.engine.SpoutClient.java Source code

Java tutorial

Introduction

Here is the source code for org.spout.engine.SpoutClient.java

Source

/*
 * This file is part of Spout.
 *
 * Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
 * Spout is licensed under the SpoutDev 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 SpoutDev 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 SpoutDev License Version 1 along with this program.
 * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
 * License and see <http://www.spout.org/SpoutDevLicenseV1.txt> for the full license,
 * including the MIT license.
 */
package org.spout.engine;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.PixelFormat;

import org.spout.api.Client;
import org.spout.api.FileSystem;
import org.spout.api.Spout;
import org.spout.api.audio.SoundManager;
import org.spout.api.chat.ChatArguments;
import org.spout.api.chat.style.ChatStyle;
import org.spout.api.command.CommandRegistrationsFactory;
import org.spout.api.command.CommandSource;
import org.spout.api.command.annotated.AnnotatedCommandRegistrationFactory;
import org.spout.api.command.annotated.SimpleInjector;
import org.spout.api.component.components.CameraComponent;
import org.spout.api.component.components.HitBlockComponent;
import org.spout.api.component.components.PhysicsComponent;
import org.spout.api.component.components.PredictableTransformComponent;
import org.spout.api.datatable.SerializableMap;
import org.spout.api.entity.Entity;
import org.spout.api.entity.state.PlayerInputState;
import org.spout.api.event.server.ClientEnableEvent;
import org.spout.api.geo.World;
import org.spout.api.geo.cuboid.Chunk;
import org.spout.api.geo.discrete.Point;
import org.spout.api.geo.discrete.Transform;
import org.spout.api.gui.FullScreen;
import org.spout.api.gui.Screen;
import org.spout.api.gui.ScreenStack;
import org.spout.api.gui.Widget;
import org.spout.api.input.Keyboard;
import org.spout.api.math.MathHelper;
import org.spout.api.math.Vector2;
import org.spout.api.math.Vector3;
import org.spout.api.model.Model;
import org.spout.api.plugin.Platform;
import org.spout.api.plugin.PluginStore;
import org.spout.api.protocol.CommonPipelineFactory;
import org.spout.api.protocol.PortBinding;
import org.spout.api.protocol.Protocol;
import org.spout.api.protocol.Session;
import org.spout.api.render.Camera;
import org.spout.api.render.RenderMode;

import org.spout.engine.audio.SpoutSoundManager;
import org.spout.engine.batcher.SpriteBatch;
import org.spout.engine.command.InputManagementCommands;
import org.spout.engine.entity.SpoutClientPlayer;
import org.spout.engine.entity.SpoutPlayer;
import org.spout.engine.entity.component.ClientTextModelComponent;
import org.spout.engine.entity.component.EntityRendererComponent;
import org.spout.engine.filesystem.ClientFileSystem;
import org.spout.engine.input.SpoutInputConfiguration;
import org.spout.engine.input.SpoutInputManager;
import org.spout.engine.listener.SpoutClientListener;
import org.spout.engine.listener.channel.SpoutClientConnectListener;
import org.spout.engine.mesh.BaseMesh;
import org.spout.engine.protocol.SpoutClientSession;
import org.spout.engine.renderer.BatchVertexRenderer;
import org.spout.engine.renderer.WorldRenderer;
import org.spout.engine.resources.ClientEntityPrefab;
import org.spout.engine.resources.ClientFont;
import org.spout.engine.util.MacOSXUtils;
import org.spout.engine.util.thread.lock.SpoutSnapshotLock;
import org.spout.engine.util.thread.threadfactory.NamedThreadFactory;
import org.spout.engine.world.SpoutClientWorld;

import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.glClear;

public class SpoutClient extends SpoutEngine implements Client {
    private final SoundManager soundManager = new SpoutSoundManager();
    private final SpoutInputManager inputManager = new SpoutInputManager();
    private final String name = "Spout Client";
    private final Vector2 resolution = new Vector2(1024, 768);
    private final boolean[] sides = { true, true, true, true, true, true };
    private final float aspectRatio = resolution.getX() / resolution.getY();
    private final FileSystem filesystem;
    private Camera activeCamera;
    private WorldRenderer worldRenderer;
    private final AtomicReference<SpoutClientSession> session = new AtomicReference<SpoutClientSession>();
    private SpoutPlayer activePlayer;
    private final AtomicReference<SpoutClientWorld> activeWorld = new AtomicReference<SpoutClientWorld>();
    private final AtomicReference<PortBinding> potentialBinding = new AtomicReference<PortBinding>();
    private boolean ccoverride = false;
    // Handle stopping
    private volatile boolean rendering = true;
    private String stopMessage = null;
    private final ClientBootstrap bootstrap = new ClientBootstrap();
    private boolean wireframe = false;
    // Gui
    private SpriteBatch gui;
    private ScreenStack screenStack;
    private ClientFont font;
    private boolean showDebugInfos = true;
    private ConcurrentLinkedQueue<Runnable> renderTaskQueue = new ConcurrentLinkedQueue<Runnable>();

    public SpoutClient() {
        this.filesystem = new ClientFileSystem();
    }

    @Override
    public void init(SpoutApplication args) {
        boolean inJar = false;

        try {
            CodeSource cs = SpoutClient.class.getProtectionDomain().getCodeSource();
            inJar = cs.getLocation().toURI().getPath().endsWith(".jar");
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        if (inJar) {
            unpackLwjgl();
        }

        ExecutorService executorBoss = Executors
                .newCachedThreadPool(new NamedThreadFactory("SpoutServer - Boss", true));
        ExecutorService executorWorker = Executors
                .newCachedThreadPool(new NamedThreadFactory("SpoutServer - Worker", true));
        ChannelFactory factory = new NioClientSocketChannelFactory(executorBoss, executorWorker);
        bootstrap.setFactory(factory);

        ChannelPipelineFactory pipelineFactory = new CommonPipelineFactory(this, true);
        bootstrap.setPipelineFactory(pipelineFactory);
        super.init(args);

        this.ccoverride = args.ccoverride;

        inputManager.bind(Keyboard.get(SpoutInputConfiguration.FORWARD.getString()), "forward");
        inputManager.bind(Keyboard.get(SpoutInputConfiguration.BACKWARD.getString()), "backward");
        inputManager.bind(Keyboard.get(SpoutInputConfiguration.LEFT.getString()), "left");
        inputManager.bind(Keyboard.get(SpoutInputConfiguration.RIGHT.getString()), "right");
        inputManager.bind(Keyboard.get(SpoutInputConfiguration.UP.getString()), "jump");
        inputManager.bind(Keyboard.get(SpoutInputConfiguration.DOWN.getString()), "crouch");
        inputManager.bind(Keyboard.KEY_F3, "debug_info");
        inputManager.bind(org.spout.api.input.Mouse.MOUSE_SCROLLDOWN, "select_down");
        inputManager.bind(org.spout.api.input.Mouse.MOUSE_SCROLLUP, "select_up");
        inputManager.bind(org.spout.api.input.Mouse.MOUSE_BUTTON0, "left_click");
        inputManager.bind(org.spout.api.input.Mouse.MOUSE_BUTTON1, "interact");
        inputManager.bind(org.spout.api.input.Mouse.MOUSE_BUTTON2, "fire_2");
    }

    @Override
    public void start() {
        start(true);
    }

    @Override
    public void start(boolean checkWorlds) {
        // Building the screenStack
        FullScreen mainScreen = new FullScreen();
        mainScreen.setTakesInput(false);
        screenStack = new ScreenStack(mainScreen);

        super.start(checkWorlds);

        getEventManager().registerEvents(new SpoutClientListener(this), this);
        CommandRegistrationsFactory<Class<?>> commandRegFactory = new AnnotatedCommandRegistrationFactory(
                new SimpleInjector(this));

        // Register commands
        getRootCommand().addSubCommands(this, InputManagementCommands.class, commandRegFactory);

        while (super.getDefaultWorld() == null) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // TODO : Wait until the world is fully loaded
        }
        font = (ClientFont) Spout.getFilesystem()
                .getResource("font://Spout/resources/resources/fonts/ubuntu/Ubuntu-M.ttf");
        activePlayer = new SpoutClientPlayer("Spouty", super.getDefaultWorld().getSpawnPoint(),
                SpoutConfiguration.VIEW_DISTANCE.getInt() * Chunk.BLOCKS.SIZE);
        activeCamera = activePlayer.add(CameraComponent.class);
        activePlayer.add(HitBlockComponent.class);
        super.getDefaultWorld().spawnEntity(activePlayer);

        getScheduler().startRenderThread();
        getScheduler().startGuiThread();

        //TODO Maybe a better way of alerting plugins the client is done?
        if (ClientEnableEvent.getHandlerList().getRegisteredListeners().length != 0) {
            Spout.getEventManager().callEvent(new ClientEnableEvent());
        }
    }

    @Override
    public SpoutPlayer getActivePlayer() {
        return activePlayer;
    }

    @Override
    public CommandSource getCommandSource() {
        if (session.get() != null) {
            return activePlayer;
        } else {
            return super.getCommandSource();
        }
    }

    @Override
    public Camera getActiveCamera() {
        return activeCamera;
    }

    @Override
    public void setActiveCamera(Camera activeCamera) {
        this.activeCamera = activeCamera;
    }

    @Override
    public PluginStore getPluginStore() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Platform getPlatform() {
        return Platform.CLIENT;
    }

    @Override
    public RenderMode getRenderMode() {
        return getArguments().renderMode;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public SoundManager getSoundManager() {
        return soundManager;
    }

    @Override
    public SpoutInputManager getInputManager() {
        return inputManager;
    }

    public void doInput(float dt) {
        // TODO move this a plugin

        if (activePlayer == null) {
            return;
        }

        inputManager.pollInput(activePlayer);

        PlayerInputState inputState = activePlayer.input();
        Transform ts = activePlayer.getTransform().getTransformLive();
        ts.setRotation(MathHelper.rotation(inputState.pitch(), inputState.yaw(), ts.getRotation().getRoll()));

        Point point = ts.getPosition();
        if (inputState.getForward()) {
            point = point.subtract(ts.forwardVector().multiply(activeCamera.getSpeed()).multiply(dt));
        }
        if (inputState.getBackward()) {
            point = point.add(ts.forwardVector().multiply(activeCamera.getSpeed()).multiply(dt));
        }
        if (inputState.getLeft()) {
            point = point.subtract(ts.rightVector().multiply(activeCamera.getSpeed()).multiply(dt));
        }
        if (inputState.getRight()) {
            point = point.add(ts.rightVector().multiply(activeCamera.getSpeed()).multiply(dt));
        }
        if (inputState.getJump()) {
            point = point.add(ts.upVector().multiply(activeCamera.getSpeed()).multiply(dt));
        }
        if (inputState.getCrouch()) {
            point = point.subtract(ts.upVector().multiply(activeCamera.getSpeed()).multiply(dt));
        }
        ts.setPosition(point);

        activePlayer.getTransform().setTransform(ts);
    }

    @Override
    public PortBinding getAddress() {
        return session.get().getActiveAddress();
    }

    @Override
    public boolean stop(String message) {
        if (!super.stop(message, false)) {
            return false;
        }
        rendering = false;
        stopMessage = message;
        Runnable finalTask = new Runnable() {
            @Override
            public void run() {
                bootstrap.getFactory().releaseExternalResources();
                boundProtocols.clear();
            }
        };
        getScheduler().submitFinalTask(finalTask, true);
        getScheduler().stop();
        return true;
    }

    public boolean isRendering() {
        return rendering;
    }

    public void stopEngine() {
        if (rendering) {
            throw new IllegalStateException("Client is still rendering!");
        }
        super.stop(stopMessage);
    }

    /*@Override //Because there is a conflict when the spout engine tries to load the world
    public SpoutClientWorld getWorld(String name, boolean exact) {
       SpoutClientWorld world = activeWorld.get();
       if (world == null) {
     return null;
       }
        
       if ((exact && world.getName().equals(name))
        || world.getName().startsWith(name)) {
     return world;
       } else {
     return null;
       }
    }*/

    @Override
    public SpoutClientWorld getWorld(UUID uid) {
        SpoutClientWorld world = activeWorld.get();
        if (world != null && world.getUID().equals(uid)) {
            return world;
        } else {
            return null;
        }
    }

    @Override
    public Collection<World> getWorlds() {
        return Collections.<World>singletonList(activeWorld.get());
    }

    /*@Override
    public SpoutClientWorld getDefaultWorld() {
       return activeWorld.get();
    }*/

    @Override
    public SpoutClientWorld worldChanged(String name, UUID uuid, byte[] data) {
        SpoutClientWorld world = new SpoutClientWorld(name, uuid, this, getEngineItemMap());

        SerializableMap map = world.getComponentHolder().getData();
        try {
            map.deserialize(data);
        } catch (IOException e) {
            throw new RuntimeException("Unable to deserialize data", e);
        }

        SpoutClientWorld oldWorld = activeWorld.getAndSet(world);
        if (oldWorld != null) {
            if (!oldWorld.getExecutor().haltExecutor()) {
                throw new IllegalStateException("Executor was already halted when halting was attempted");
            }
            oldWorld.unload(false);
        }
        if (!world.getExecutor().startExecutor()) {
            activeWorld.compareAndSet(world, null);
            throw new IllegalStateException("Unable to start executor for new world");
        }
        return world;
    }

    public ClientBootstrap getBootstrap() {
        return bootstrap;
    }

    @Override
    public SpoutClientSession newSession(Channel channel) {
        Protocol protocol = potentialBinding.getAndSet(null).getProtocol();
        return new SpoutClientSession(this, channel, protocol);
    }

    public void connect(final PortBinding binding) {
        potentialBinding.set(binding);
        getBootstrap().connect(binding.getAddress()).addListener(new SpoutClientConnectListener(this, binding));
    }

    public void disconnected() {
        Session sess = this.session.getAndSet(null);
        if (sess != null) {
            getSessionRegistry().remove(sess);
        }
    }

    public void setSession(SpoutClientSession session) {
        this.session.set(session);
        getSessionRegistry().add(session);
        activePlayer.connect(session, activePlayer.getTransform().getTransform());
        session.setPlayer(activePlayer);
        players.putIfAbsent(activePlayer.getName(), activePlayer);
    }

    public void initRenderer() {
        createWindow();

        getLogger().info("SpoutClient Information");
        getLogger().info("Operating System: " + System.getProperty("os.name"));
        getLogger().info("Renderer Mode: " + this.getRenderMode().toString());
        getLogger().info("OpenGL Information");
        getLogger().info("Vendor: " + GL11.glGetString(GL11.GL_VENDOR));
        getLogger().info("OpenGL Version: " + GL11.glGetString(GL11.GL_VERSION));
        getLogger().info("GLSL Version: " + GL11.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION));
        getLogger().info("Max Textures: " + GL11.glGetString(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS));
        String extensions = "Extensions Supported: ";
        if (getArguments().renderMode == RenderMode.GL30) {
            for (int i = 0; i < GL11.glGetInteger(GL30.GL_NUM_EXTENSIONS); i++) {
                extensions += GL30.glGetStringi(GL11.GL_EXTENSIONS, i) + " ";
            }
        } else {
            extensions += GL11.glGetString(GL11.GL_EXTENSIONS);
        }
        getLogger().info(extensions);
        //soundManager.init();
        Spout.getFilesystem().postStartup();

        GL11.glEnable(GL11.GL_DEPTH_TEST);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glEnable(GL11.GL_CULL_FACE);
        GL11.glFrontFace(GL11.GL_CW);
        GL11.glCullFace(GL11.GL_BACK);

        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        GL11.glClearColor((135.f / 255.0f), 206.f / 255.f, 250.f / 255.f, 1);

        //Init pool of BatchVertexRenderer
        BatchVertexRenderer.initPool(GL11.GL_TRIANGLES, 10000);

        worldRenderer = new WorldRenderer(this);

        gui = SpriteBatch.createSpriteBatch(getRenderMode(), resolution.getX(), resolution.getY());

        // Test
        ClientEntityPrefab spoutyType = (ClientEntityPrefab) Spout.getFilesystem()
                .getResource("entity://Spout/resources/resources/entities/Spouty/spouty.sep");

        Entity e = spoutyType.createEntity(super.getDefaultWorld().getSpawnPoint().getPosition());
        e.setSavable(false); // To prevent entity duplication
        ClientTextModelComponent tmc = e.add(ClientTextModelComponent.class);
        tmc.setText(new ChatArguments(ChatStyle.BLUE, "Sp", ChatStyle.WHITE, "ou", ChatStyle.RED, "ty"));
        tmc.setSize(0.5f);
        tmc.setTranslation(new Vector3(0, 3f, 0));
        tmc.setFont(font);

        super.getDefaultWorld().spawnEntity(e);
    }

    public void updateRender(long limit) {
        worldRenderer.update(limit);
    }

    public void render(float dt) {

        while (renderTaskQueue.peek() != null) {
            Runnable task = renderTaskQueue.poll();
            task.run();
        }

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        Model skydome = (Model) super.getDefaultWorld().getDataMap().get("Skydome");
        if (skydome != null) {
            skydome.getRenderMaterial().getShader().setUniform("View", MathHelper.createIdentity());
            skydome.getRenderMaterial().getShader().setUniform("Projection", getActiveCamera().getProjection());
            BaseMesh skydomeMesh = (BaseMesh) skydome.getMesh();
            if (!skydomeMesh.isBatched()) {
                skydomeMesh.batch();
            }
            skydomeMesh.render(skydome.getRenderMaterial());
        }

        doInput(dt);

        for (Entity e : super.getDefaultWorld().getAll()) {
            ((PredictableTransformComponent) e.getTransform()).updateRender(dt);
        }

        activeCamera.updateView();

        Mouse.setGrabbed(screenStack.getVisibleScreens().getLast().grabsMouse());

        worldRenderer.render();

        //TODO Remove this when we use SpoutClientWorld
        SpoutSnapshotLock lock = (SpoutSnapshotLock) getScheduler().getSnapshotLock();
        lock.coreReadLock("Render Thread - Render Entities");
        for (Entity e : super.getDefaultWorld().getAll()) {
            EntityRendererComponent r = e.get(EntityRendererComponent.class);
            if (r != null) {
                r.update();
                r.render(activeCamera);
            }
        }
        lock.coreReadUnlock("Render Thread - Render Entities");

        if (wireframe) {
            GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
        }
        gui.begin();
        if (showDebugInfos) {
            Point position = activePlayer.getTransform().getPosition();
            gui.drawText(
                    new ChatArguments("Spout client! Logged as ", ChatStyle.RED, activePlayer.getDisplayName(),
                            ChatStyle.RESET, " in world: ", ChatStyle.RED, getDefaultWorld().getName()),
                    font, -0.95f, 0.9f, 10f);
            gui.drawText(new ChatArguments(ChatStyle.BLUE, "x: ", position.getX()), font, -0.95f, 0.8f, 8f);
            gui.drawText(new ChatArguments(ChatStyle.BLUE, "y: ", position.getY()), font, -0.95f, 0.7f, 8f);
            gui.drawText(new ChatArguments(ChatStyle.BLUE, "z: ", position.getZ()), font, -0.95f, 0.6f, 8f);
            gui.drawText(new ChatArguments(ChatStyle.BLUE, "fps: ", getScheduler().getFps(), " (",
                    getScheduler().isRendererOverloaded(), ")"), font, -0.95f, 0.5f, 8f);
            gui.drawText(
                    new ChatArguments(ChatStyle.BLUE, "batch: ",
                            worldRenderer.getRended() + "/" + worldRenderer.getBatchWaiting()),
                    font, -0.95f, 0.4f, 8f);
            gui.drawText(
                    new ChatArguments(ChatStyle.BLUE, "ocluded: ",
                            (int) ((float) worldRenderer.getOcluded() / worldRenderer.getRended() * 100) + "%"),
                    font, -0.95f, 0.3f, 8f);
            gui.drawText(
                    new ChatArguments(ChatStyle.BLUE, "culled: ",
                            (int) ((float) worldRenderer.getCulled() / worldRenderer.getRended() * 100), "%"),
                    font, -0.95f, 0.2f, 8f);
            gui.drawText(
                    new ChatArguments(ChatStyle.BLUE, "Update: ",
                            worldRenderer.minUpdate + " / " + worldRenderer.maxUpdate + " / "
                                    + (worldRenderer.sumUpdate / Math.max(1, worldRenderer.count))),
                    font, -0.95f, 0.1f, 8f);
            gui.drawText(
                    new ChatArguments(ChatStyle.BLUE, "Render: ",
                            worldRenderer.minRender + " / " + worldRenderer.maxRender + " / "
                                    + (worldRenderer.sumRender / Math.max(1, worldRenderer.count))),
                    font, -0.95f, 0.0f, 8f);
        }
        for (Screen screen : screenStack.getVisibleScreens()) {
            for (Widget widget : screen.getWidgets()) {
                gui.draw(widget.getRenderParts());
            }
        }
        gui.render();
        if (wireframe) {
            GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
        }
    }

    public void toggleDebugInfos() {
        showDebugInfos = !showDebugInfos;
    }

    public WorldRenderer getWorldRenderer() {
        return worldRenderer;
    }

    public ScreenStack getScreenStack() {
        return screenStack;
    }

    @Override
    public Vector2 getResolution() {
        return resolution;
    }

    @Override
    public float getAspectRatio() {
        return aspectRatio;
    }

    private void createWindow() {
        try {
            Display.setDisplayMode(new DisplayMode((int) resolution.getX(), (int) resolution.getY()));

            //Override using ContextAttribs for some videocards that don't support ARB_CREATE_CONTEXT
            if (ccoverride) {
                Display.create(new PixelFormat(8, 24, 0));
                Display.setTitle("Spout Client");
                return;
            }

            if (MacOSXUtils.isMac()) {
                createMacWindow();
            } else {
                if (getRenderMode() == RenderMode.GL11) {
                    ContextAttribs ca = new ContextAttribs(1, 5);
                    Display.create(new PixelFormat(8, 24, 0), ca);
                } else if (getRenderMode() == RenderMode.GL20) {
                    ContextAttribs ca = new ContextAttribs(2, 1);
                    Display.create(new PixelFormat(8, 24, 0), ca);
                } else if (getRenderMode() == RenderMode.GL30) {
                    ContextAttribs ca = new ContextAttribs(3, 2).withForwardCompatible(false);
                    Display.create(new PixelFormat(8, 24, 0), ca);
                }
            }

            Display.setTitle("Spout Client");
        } catch (LWJGLException e) {
            e.printStackTrace();
        }
    }

    private void createMacWindow() throws LWJGLException {
        if (getRenderMode() == RenderMode.GL30) {
            if (MacOSXUtils.getOSXVersion() >= 7) {
                ContextAttribs ca = new ContextAttribs(3, 2).withProfileCore(true);
                Display.create(new PixelFormat(8, 24, 0), ca);
            } else {
                throw new UnsupportedOperationException("Cannot create a 3.0 context without OSX 10.7_");
            }
        } else {
            Display.create();
        }
    }

    private static void unpackLwjgl() {
        String[] files;
        String osPath;

        if (SystemUtils.IS_OS_WINDOWS) {
            files = 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) {
            files = new String[] { "libjinput-osx.jnilib", "liblwjgl.jnilib", "openal.dylib", };
            osPath = "mac/";
        } else if (SystemUtils.IS_OS_LINUX) {
            files = new String[] { "liblwjgl.so", "liblwjgl64.so", "libopenal.so", "libopenal64.so",
                    "libjinput-linux.so", "libjinput-linux64.so" };
            osPath = "linux/";
        } else {
            Spout.getEngine().getLogger().log(Level.SEVERE,
                    "Error loading natives of operating system type: " + SystemUtils.OS_NAME);
            return;
        }

        File cacheDir = new File(System.getProperty("user.dir"), "natives/" + osPath);
        cacheDir.mkdirs();
        for (String f : files) {
            File outFile = new File(cacheDir, f);
            if (!outFile.exists()) {
                try {
                    FileUtils.copyInputStreamToFile(SpoutClient.class.getResourceAsStream("/" + f), outFile);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        String nativePath = cacheDir.getAbsolutePath();
        System.setProperty("org.lwjgl.librarypath", nativePath);
        System.setProperty("net.java.games.input.librarypath", nativePath);
    }

    @Override
    public FileSystem getFilesystem() {
        return filesystem;
    }

    public void enqueueTask(Runnable task) {
        renderTaskQueue.add(task);
    }

    public void toggleWireframe() {
        if (wireframe) {
            GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
            wireframe = false;
        } else {
            GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
            wireframe = true;
        }
    }
}