diff --git a/dlangui-msvc.sln b/dlangui-msvc.sln index 0ed91bba..c5a5adc3 100644 --- a/dlangui-msvc.sln +++ b/dlangui-msvc.sln @@ -50,6 +50,11 @@ Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "ircclient", "examples\irccl {52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5} = {52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5} EndProjectSection EndProject +Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dminer", "examples\dminer\dminer.visualdproj", "{5F443F6A-6612-4404-B89E-D0D0205DC8E5}" + ProjectSection(ProjectDependencies) = postProject + {52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5} = {52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -173,6 +178,16 @@ Global {20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|Win32.Build.0 = Release|Win32 {20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|x64.ActiveCfg = Release|Win32 {20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|x64.Build.0 = Release|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Debug|Win32.ActiveCfg = Debug|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Debug|Win32.Build.0 = Debug|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Debug|x64.ActiveCfg = Debug|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Release|Win32.ActiveCfg = Release|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Release|Win32.Build.0 = Release|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Release|x64.ActiveCfg = Release|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Unittest|Win32.ActiveCfg = Release|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Unittest|Win32.Build.0 = Release|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Unittest|x64.ActiveCfg = Release|Win32 + {5F443F6A-6612-4404-B89E-D0D0205DC8E5}.Unittest|x64.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dub.json b/dub.json index 8365a6ed..e4fd8c63 100644 --- a/dub.json +++ b/dub.json @@ -36,6 +36,7 @@ "./examples/example1/", "./examples/dmledit/", "./examples/d3d/", + "./examples/dminer/", "./examples/tetris/", "./examples/opengl/", "./examples/ircclient/", diff --git a/examples/dminer/dminer.visualdproj b/examples/dminer/dminer.visualdproj new file mode 100644 index 00000000..e2fe6214 --- /dev/null +++ b/examples/dminer/dminer.visualdproj @@ -0,0 +1,220 @@ + + {5F443F6A-6612-4404-B89E-D0D0205DC8E5} + + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2.043 + 0 + 0 + 0 + 0 + 0 + $(CC) -c + 1 + $(DMDInstallDir)windows\bin\dmd.exe + $(SolutionDir)/src $(SolutionDir)/3rdparty $(SolutionDir)/deps/DerelictGL3/source $(SolutionDir)/deps/DerelictUtil/source $(SolutionDir)/deps/DerelictFT/source $(SolutionDir)/deps/DerelictSDL2/source + views views/res views/res/i18n views/res/mdpi views/res/hdpi + $(ConfigurationName) + $(OutDir) + + + 0 + + + + + 0 + + + 1 + $(IntDir)\$(TargetName).json + 0 + + 0 + + 0 + 0 + 0 + + + + 0 + + 1 + $(VisualDInstallDir)cv2pdb\cv2pdb.exe + 0 + 0 + 0 + + + + ole32.lib kernel32.lib user32.lib comctl32.lib comdlg32.lib + + + + $(OutDir)\$(ProjectName).exe + 1 + 2 + 0 + + + + *.obj;*.cmd;*.build;*.json;*.dep + + + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 2.043 + 0 + 0 + 0 + 0 + 0 + $(CC) -c + 1 + $(DMDInstallDir)windows\bin\dmd.exe + $(SolutionDir)/src $(SolutionDir)/3rdparty $(SolutionDir)/deps/DerelictGL3/source $(SolutionDir)/deps/DerelictUtil/source $(SolutionDir)/deps/DerelictFT/source $(SolutionDir)/deps/DerelictSDL2/source + views views/res views/res/i18n views/res/mdpi views/res/hdpi + $(ConfigurationName) + $(OutDir) + + + 0 + + + + + 0 + + + 1 + $(IntDir)\$(TargetName).json + 0 + + 0 + + 0 + 0 + 0 + + + + 0 + + 0 + $(VisualDInstallDir)cv2pdb\cv2pdb.exe + 0 + 0 + 0 + + + + ole32.lib kernel32.lib user32.lib comctl32.lib comdlg32.lib + + + + $(OutDir)\$(ProjectName).exe + 1 + 1 + 0 + + + + *.obj;*.cmd;*.build;*.json;*.dep + + + + + + + + + + + + + + + diff --git a/examples/dminer/dub.json b/examples/dminer/dub.json new file mode 100644 index 00000000..730836db --- /dev/null +++ b/examples/dminer/dub.json @@ -0,0 +1,21 @@ +{ + "name": "dminer", + "description": "dlangui library example DMiner", + "homepage": "https://github.com/buggins/dlangui", + "license": "Boost", + "authors": ["Vadim Lopatin"], + + "stringImportPaths": ["views", "views/res", "views/res/i18n", "views/res/mdpi"], + + "targetPath": "bin", + "targetName": "dminer", + "targetType": "executable", + + "sourceFiles-windows": ["$PACKAGE_DIR/src/win_app.def"], + + "versions": ["EmbedStandardResources"], + + "dependencies": { + "dlangui": {"path": "../../"} + } +} diff --git a/examples/dminer/src/dminer/core/blocks.d b/examples/dminer/src/dminer/core/blocks.d new file mode 100644 index 00000000..3ce10bf4 --- /dev/null +++ b/examples/dminer/src/dminer/core/blocks.d @@ -0,0 +1,287 @@ +module dminer.core.blocks; + +import dminer.core.minetypes; +import dminer.core.world; +import dlangui.graphics.scene.mesh; + +immutable string BLOCK_TEXTURE_FILENAME = "blocks"; +immutable int BLOCK_TEXTURE_DX = 1024; +immutable int BLOCK_TEXTURE_DY = 1024; +immutable int BLOCK_SPRITE_SIZE = 16; +immutable int BLOCK_SPRITE_STEP = 16; +immutable int BLOCK_SPRITE_OFFSET = 0; +immutable int BLOCK_TEXTURE_SPRITES_PER_LINE = 1024/16; +immutable int VERTEX_COMPONENTS = 12; + +enum BlockVisibility { + INVISIBLE, + OPAQUE, // completely opaque (cells covered by this block are invisible) + OPAQUE_SEPARATE_TX, + HALF_OPAQUE, // partially paque, cells covered by this block can be visible, render as normal block + HALF_OPAQUE_SEPARATE_TX, + HALF_TRANSPARENT, // should be rendered last (semi transparent texture) +} + +class BlockDef { +public: + cell_t id; + string name; + BlockVisibility visibility = BlockVisibility.INVISIBLE; + int txIndex; + this() { + } + this(cell_t blockId, string blockName, BlockVisibility v, int tx) { + id = blockId; + name = blockName; + visibility = v; + txIndex = tx; + } + ~this() { + } + // blocks behind this block can be visible + @property bool canPass() { + return visibility == BlockVisibility.INVISIBLE + || visibility == BlockVisibility.HALF_OPAQUE + || visibility == BlockVisibility.HALF_OPAQUE_SEPARATE_TX + || visibility == BlockVisibility.HALF_TRANSPARENT; + } + // block is fully opaque (all blocks behind are invisible) + @property bool isOpaque() { + return visibility == BlockVisibility.OPAQUE + || visibility == BlockVisibility.OPAQUE_SEPARATE_TX; + } + // block is visible + @property bool isVisible() { + return visibility != BlockVisibility.INVISIBLE; + } + + @property bool terrainSmoothing() { + return false; + } + + /// create cube face + void createFace(World world, ref Position camPosition, Vector3d pos, Dir face, Mesh mesh) { + // default implementation + ushort startVertexIndex = cast(ushort)mesh.vertexCount; + float[VERTEX_COMPONENTS * 4] vptr; + ushort[6] iptr; + createFaceMesh(vptr.ptr, face, pos.x, pos.y, pos.z, txIndex); + for (int i = 0; i < 6; i++) + iptr[i] = cast(ushort)(startVertexIndex + face_indexes[i]); + //if (HIGHLIGHT_GRID && ((pos.x & 7) == 0 || (pos.z & 7) == 0)) { + // for (int i = 0; i < 4; i++) { + // vptr[11 * i + 6 + 0] = 1.4f; + // vptr[11 * i + 6 + 1] = 1.4f; + // vptr[11 * i + 6 + 2] = 1.4f; + // } + //} + mesh.addVertexes(vptr); + mesh.addPart(PrimitiveType.triangles, iptr); + } + /// create faces + void createFaces(World world, ref Position camPosition, Vector3d pos, int visibleFaces, Mesh mesh) { + for (int i = 0; i < 6; i++) + if (visibleFaces & (1 << i)) + createFace(world, camPosition, pos, cast(Dir)i, mesh); + } +} + +// pos, normal, color, tx +static const float[VERTEX_COMPONENTS * 4] face_vertices_north = +[ + -0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, + 0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, + -0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, + 0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, +]; + +static const float[VERTEX_COMPONENTS * 4] face_vertices_south = +[ + -0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, + 0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, + -0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, + 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, +]; + +static const float[VERTEX_COMPONENTS * 4] face_vertices_west = +[ + -0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, + -0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, + -0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, + -0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 +]; + +static const float[VERTEX_COMPONENTS * 4] face_vertices_east = +[ + 0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, + 0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, + 0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, +]; + +static const float[VERTEX_COMPONENTS * 4] face_vertices_up = +[ + -0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, + 0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, + -0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, + 0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, +]; + +static const float[VERTEX_COMPONENTS * 4] face_vertices_down = +[ + -0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, + 0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, + -0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, + 0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, +]; + +static const ushort[6] face_indexes = +[ + 0, 1, 2, 2, 1, 3 +]; + +static const ushort[6] face_indexes_back = +[ + 0, 2, 1, 2, 3, 1 +]; + +static void fillFaceMesh(float * data, const float * src, float x0, float y0, float z0, int tileX, int tileY) { + for (int i = 0; i < 4; i++) { + const float * srcvertex = src + i * VERTEX_COMPONENTS; + float * dstvertex = data + i * VERTEX_COMPONENTS; + for (int j = 0; j < VERTEX_COMPONENTS; j++) { + float v = srcvertex[j]; + switch (j) { + case 0: // x + v += x0; + break; + case 1: // y + v += y0; + break; + case 2: // z + v += z0; + break; + case 10: // tx.u + v = ((tileX + v * BLOCK_SPRITE_SIZE)) / cast(float)BLOCK_TEXTURE_DX; + break; + case 11: // tx.v + //v = (BLOCK_TEXTURE_DY - (tileY + v * BLOCK_SPRITE_SIZE)) / cast(float)BLOCK_TEXTURE_DY; + v = ((tileY + v * BLOCK_SPRITE_SIZE)) / cast(float)BLOCK_TEXTURE_DY; + break; + default: + break; + } + dstvertex[j] = v; + } + } +} + +static void createFaceMesh(float * data, Dir face, float x0, float y0, float z0, int tileIndex) { + + int tileX = (tileIndex % BLOCK_TEXTURE_SPRITES_PER_LINE) * BLOCK_SPRITE_STEP + BLOCK_SPRITE_OFFSET; + int tileY = (tileIndex / BLOCK_TEXTURE_SPRITES_PER_LINE) * BLOCK_SPRITE_STEP + BLOCK_SPRITE_OFFSET; + // data is 11 comp * 4 vert floats + switch (face) with(Dir) { + default: + case NORTH: + fillFaceMesh(data, face_vertices_north.ptr, x0, y0, z0, tileX, tileY); + break; + case SOUTH: + fillFaceMesh(data, face_vertices_south.ptr, x0, y0, z0, tileX, tileY); + break; + case WEST: + fillFaceMesh(data, face_vertices_west.ptr, x0, y0, z0, tileX, tileY); + break; + case EAST: + fillFaceMesh(data, face_vertices_east.ptr, x0, y0, z0, tileX, tileY); + break; + case UP: + fillFaceMesh(data, face_vertices_up.ptr, x0, y0, z0, tileX, tileY); + break; + case DOWN: + fillFaceMesh(data, face_vertices_down.ptr, x0, y0, z0, tileX, tileY); + break; + } +} + + + +// block type definitions +__gshared BlockDef[256] BLOCK_DEFS; +// faster check for block->canPass() +__gshared bool[256] BLOCK_TYPE_CAN_PASS; +// faster check for block->isOpaque() +__gshared bool[256] BLOCK_TYPE_OPAQUE; +// faster check for block->isVisible() +__gshared bool[256] BLOCK_TYPE_VISIBLE; +// faster check for block->isVisible() +__gshared bool[256] BLOCK_TERRAIN_SMOOTHING; + +/// registers new block type +void registerBlockType(BlockDef def) { + if (BLOCK_DEFS[def.id]) { + if (BLOCK_DEFS[def.id] is def) + return; + destroy(BLOCK_DEFS[def.id]); + } + BLOCK_DEFS[def.id] = def; + // init property shortcuts + BLOCK_TYPE_CAN_PASS[def.id] = def.canPass; + BLOCK_TYPE_OPAQUE[def.id] = def.isOpaque; + BLOCK_TYPE_VISIBLE[def.id] = def.isVisible; + BLOCK_TERRAIN_SMOOTHING[def.id] = def.terrainSmoothing; +} + +enum BlockImage : int { + stone, + grass_top, + grass_side, + grass_top_footsteps, + dirt, + bedrock, + sand, + gravel, + sandstone, + clay, + cobblestone, + cobblestone_mossy, + brick, + stonebrick, + red_sand, +} + +/// init block types array +__gshared static this() { + import std.string; + for (int i = 0; i < 256; i++) { + if (!BLOCK_DEFS[i]) { + registerBlockType(new BlockDef(cast(cell_t)i, "undef%d".format(i), BlockVisibility.INVISIBLE, 0)); + } + } + BLOCK_TYPE_CAN_PASS[BOUND_SKY] = false; + BLOCK_TYPE_VISIBLE[BOUND_SKY] = false; + BLOCK_TYPE_CAN_PASS[BOUND_BOTTOM] = false; + BLOCK_TYPE_VISIBLE[BOUND_BOTTOM] = true; + + // empty cell + registerBlockType(new BlockDef(0, "empty", BlockVisibility.INVISIBLE, 0)); + // standard block types + registerBlockType(new BlockDef(1, "gray_brick", BlockVisibility.OPAQUE, BlockImage.stonebrick)); + registerBlockType(new BlockDef(2, "brick", BlockVisibility.OPAQUE, BlockImage.brick)); + registerBlockType(new BlockDef(3, "bedrock", BlockVisibility.OPAQUE, BlockImage.bedrock)); + registerBlockType(new BlockDef(4, "clay", BlockVisibility.OPAQUE, BlockImage.clay)); + registerBlockType(new BlockDef(5, "cobblestone", BlockVisibility.OPAQUE, BlockImage.cobblestone)); + registerBlockType(new BlockDef(6, "gravel", BlockVisibility.OPAQUE, BlockImage.gravel)); + registerBlockType(new BlockDef(7, "red_sand", BlockVisibility.OPAQUE, BlockImage.red_sand)); + registerBlockType(new BlockDef(8, "sand", BlockVisibility.OPAQUE, BlockImage.sand)); + + registerBlockType(new BlockDef(50, "box", BlockVisibility.HALF_OPAQUE, 50)); + + //registerBlockType(new TerrainBlock(100, "terrain_bedrock", 2)); + //registerBlockType(new TerrainBlock(101, "terrain_clay", 3)); + //registerBlockType(new TerrainBlock(102, "terrain_cobblestone", 4)); + //registerBlockType(new TerrainBlock(103, "terrain_gravel", 5)); + //registerBlockType(new TerrainBlock(104, "terrain_red_sand", 6)); + //registerBlockType(new TerrainBlock(105, "terrain_sand", 7)); + +} diff --git a/examples/dminer/src/dminer/core/minetypes.d b/examples/dminer/src/dminer/core/minetypes.d new file mode 100644 index 00000000..6f326f40 --- /dev/null +++ b/examples/dminer/src/dminer/core/minetypes.d @@ -0,0 +1,731 @@ +module dminer.core.minetypes; + +alias cell_t = ubyte; + +immutable cell_t NO_CELL = 0; +immutable cell_t END_OF_WORLD = 253; +immutable cell_t VISITED_CELL = 255; +immutable cell_t VISITED_OCCUPIED = 254; + +immutable cell_t BOUND_BOTTOM = 253; +immutable cell_t BOUND_SKY = 252; + +enum Dir : ubyte { + NORTH = 0, + SOUTH, + EAST, + WEST, + UP, + DOWN, +} + +// 26 direction masks based on Dir +enum DirMask : ubyte { + MASK_NORTH = (1 << Dir.NORTH), + MASK_SOUTH = (1 << Dir.SOUTH), + MASK_EAST = (1 << Dir.EAST), + MASK_WEST = (1 << Dir.WEST), + MASK_UP = (1 << Dir.UP), + MASK_DOWN = (1 << Dir.DOWN), + MASK_ALL = 0x3F, + //MASK_WEST_UP = (1 << Dir.WEST) | MASK_UP, + //MASK_EAST_UP = (1 << Dir.EAST) | MASK_UP, + //MASK_WEST_DOWN = (1 << Dir.WEST) | MASK_DOWN, + //MASK_EAST_DOWN = (1 << Dir.EAST) | MASK_DOWN, + //MASK_NORTH_WEST = MASK_NORTH | MASK_WEST, + //MASK_NORTH_EAST = MASK_NORTH | MASK_EAST, + //MASK_NORTH_UP = MASK_NORTH | MASK_UP, + //MASK_NORTH_DOWN = MASK_NORTH | MASK_DOWN, + //MASK_NORTH_WEST_UP = MASK_NORTH | MASK_WEST | MASK_UP, + //MASK_NORTH_EAST_UP = MASK_NORTH | MASK_EAST | MASK_UP, + //MASK_NORTH_WEST_DOWN = MASK_NORTH | MASK_WEST | MASK_DOWN, + //MASK_NORTH_EAST_DOWN = MASK_NORTH | MASK_EAST | MASK_DOWN, + //MASK_SOUTH_WEST = MASK_SOUTH | MASK_WEST, + //MASK_SOUTH_EAST = MASK_SOUTH | MASK_EAST, + //MASK_SOUTH_UP = MASK_SOUTH | MASK_UP, + //MASK_SOUTH_DOWN = MASK_SOUTH | MASK_DOWN, + //MASK_SOUTH_WEST_UP = MASK_SOUTH | MASK_WEST | MASK_UP, + //MASK_SOUTH_EAST_UP = MASK_SOUTH | MASK_EAST | MASK_UP, + //MASK_SOUTH_WEST_DOWN = MASK_SOUTH | MASK_WEST | MASK_DOWN, + //MASK_SOUTH_EAST_DOWN = MASK_SOUTH | MASK_EAST | MASK_DOWN, +} + +struct Vector2d { + int x; + int y; + this(int xx, int yy) { + x = xx; + y = yy; + } + //bool opEqual(Vector2d v) const { + // return x == v.x && y == v.y; + //} +} + +immutable Vector2d ZERO2 = Vector2d(0, 0); + +struct Vector3d { + int x; + int y; + int z; + this(int xx, int yy, int zz) { + x = xx; + y = yy; + z = zz; + } + //bool opEqual(const Vector3d v) const { + // return x == v.x && y == v.y && z == v.z; + //} + + /// returns vector with all components which are negative of components for this vector + Vector3d opUnary(string op : "-")() const { + return Vector3d(-x, -y, -z); + } + /// subtract vectors + Vector3d opBinary(string op : "-")(const Vector3d v) const { + return Vector3d(x - v.x, y - v.y, z - v.z); + } + /// add vectors + Vector3d opBinary(string op : "+")(const Vector3d v) const { + return Vector3d(x + v.x, y + v.y, z + v.z); + } + /// + int opBinary(string op : "*")(const Vector3d v) const { + return x*v.x + y*v.y + z*v.z; + } + /// multiply vector elements by constant + Vector3d opBinary(string op : "*")(int n) const { + return Vector3d(x * n, y * n, z * n); + } + + /// + ref Vector3d opOpAssign(string op : "+")(const Vector3d v) { + x += v.x; + y += v.y; + z += v.z; + return this; + } + /// + ref Vector3d opOpAssign(string op : "-")(const Vector3d v) { + x -= v.x; + y -= v.y; + z -= v.z; + return this; + } + /// + ref Vector3d opOpAssign(string op : "*")(int n) { + x *= n; + y *= n; + z *= n; + return this; + } + Vector3d turnLeft() { + return Vector3d(z, y, -x); + } + Vector3d turnRight() { + return Vector3d(-z, y, x); + } + Vector3d turnUp() { + return Vector3d(x, -z, y); + } + Vector3d turnDown() { + return Vector3d(x, z, -y); + } + Vector3d move(Dir dir) { + Vector3d res = this; + switch (dir) with(Dir) { + case NORTH: + res.z--; + break; + case SOUTH: + res.z++; + break; + case WEST: + res.x--; + break; + case EAST: + res.x++; + break; + case UP: + res.y++; + break; + case DOWN: + res.y--; + break; + default: + break; + } + return res; + } +} + +const Vector3d ZERO3 = Vector3d(0, 0, 0); + +struct Array(T) { +private: + int _length; + T[] _data; +public: + T * ptr(int index = 0) { + return _data.ptr + index; + } + void swap(ref Array v) { + int tmp; + tmp = _length; _length = v._length; v._length = tmp; + T[] ptmp; + ptmp = _data; _data = v._data; v._data = ptmp; + } + /// ensure capacity is enough to fit sz items + void reserve(int sz) { + sz += _length; + if (_data.length < sz) { + int oldsize = cast(int)_data.length; + int newsize = 1024; + while (newsize < sz) + newsize <<= 1; + _data.length = newsize; + for (int i = oldsize; i < newsize; i++) + _data.ptr[i] = T.init; + _data.assumeSafeAppend(); + } + } + @property int length() { + return _length; + } + /// append single item by ref + void append(ref const T value) { + if (_length >= _data.length) + reserve(cast(int)(_data.length == 0 ? 64 : _data.length * 2 - _length)); + _data.ptr[_length++] = value; + } + /// append single item by value + void append(T value) { + if (_length >= _data.length) + reserve(cast(int)(_data.length == 0 ? 64 : _data.length * 2 - _length)); + _data.ptr[_length++] = value; + } + /// append single item w/o check + void appendNoCheck(ref const T value) { + _data.ptr[_length++] = value; + } + /// append single item w/o check + void appendNoCheck(T value) { + _data.ptr[_length++] = value; + } + /// appends same value several times, return pointer to appended items + T* append(ref const T value, int count) { + reserve(count); + int startLen = _length; + for (int i = 0; i < count; i++) + _data.ptr[_length++] = value; + return _data.ptr + startLen; + } + /// appends same value several times, return pointer to appended items + T* append(T value, int count) { + reserve(count); + int startLen = _length; + for (int i = 0; i < count; i++) + _data.ptr[_length++] = value; + return _data.ptr + startLen; + } + void clear() { + _length = 0; + } + T get(int index) { + return _data.ptr[index]; + } + void set(int index, T value) { + _data.ptr[index] = value; + } + ref T opIndex(int index) { + return _data.ptr[index]; + } +} + +alias FloatArray = Array!(float); +alias IntArray = Array!(int); +alias CellArray = Array!(cell_t); +alias Vector2dArray = Array!(Vector2d); +alias Vector3dArray = Array!(Vector3d); + +/// array with support of both positive and negative indexes +struct InfiniteArray(T) { +private: + T[] dataPlus; + T[] dataMinus; + int minIdx; + int maxIdx; +public: + @property int minIndex() { return minIdx; } + @property int maxIndex() { return maxIdx; } + void disposeFunction(T p) { + destroy(p); + } + ~this() { + foreach(p; dataPlus) + if (p !is T.init) + disposeFunction(p); + foreach(p; dataMinus) + if (p !is T.init) + disposeFunction(p); + } + T get(int index) { + if (index >= 0) { + if (index >= maxIdx) + return T.init; + return dataPlus[index]; + } else { + if (index <= minIdx) + return T.init; + return dataMinus[-index]; + } + } + void set(int index, T value) { + if (index >= 0) { + if (index >= maxIdx) { + // extend array + if (index <= dataPlus.length) { + int oldsize = dataPlus.length; + int newsize = 1024; + while (newsize <= index) + newsize <<= 1; + dataPlus.length = newsize; + dataPlus.assumeSafeAppend; + for(int i = oldsize; i < newsize; i++) + dataPlus[i] = T.init; + } + maxIdx = index + 1; + } + if (dataPlus[index] !is T.init && dataPlus[index] !is value) + disposeFunction(dataPlus[index]); + dataPlus[index] = value; + } else { + if (index <= minIdx) { + // extend array + if (-index <= dataMinus.length) { + int oldsize = dataMinus.length; + int newsize = 1024; + while (newsize <= -index) + newsize <<= 1; + dataMinus.length = newsize; + dataMinus.assumeSafeAppend; + for(int i = oldsize; i < newsize; i++) + dataMinus[i] = T.init; + } + maxIdx = index - 1; + } + if (dataMinus[-index] !is T.init && dataMinus[-index] !is value) + disposeFunction(dataMinus[-index]); + dataMinus[-index] = value; + } + } +} + +struct InfiniteMatrix(T) { +private: + int _size = 0; + int _sizeShift = 0; + int _sizeShiftMul2 = 0; + int _sizeMask = 0; + int _invSizeMask = 0; + T[] _data; + void resize(int newSizeShift) { + int newSize = (1<= newSize || y < -newSize || y >= newSize) { + // destory: // outside new size + destroy(v); + } else { + // move + newdata[((y + newSize) << (newSizeShift + 1)) | (x + newSize)] = v; + } + } + } + _data = newdata; + _size = newSize; + _sizeShift = newSizeShift; + _sizeShiftMul2 = _sizeShift + 1; + _sizeMask = (1 << _sizeShiftMul2) - 1; + _invSizeMask = ~_sizeMask; + } + int calcIndex(int x, int y) { + return (y << _sizeShiftMul2) + x; + } +public: + @property int size() { return _size; } + T get(int x, int y) { + if (!_data) + return null; + x += _size; + y += _size; + if (!((x | y) & ~_sizeMask)) { + return _data.ptr[(y << _sizeShiftMul2) + x]; //calcIndex(x, y) + } + return null; + } + void set(int x, int y, T v) { + if (x < -_size || x >= _size || y < -_size || y >= _size) { + int newSizeShift = _sizeShift < 6 ? 6 : _sizeShift + 1; + for (; ;newSizeShift++) { + int sz = 1 << newSizeShift; + if (x < -sz || x >= sz || y < -sz || y >= sz) + continue; + break; + } + resize(newSizeShift); + } + x += _size; + y += _size; + int index = calcIndex(x, y); + if (_data.ptr[index]) + destroy(_data.ptr[index]); + _data.ptr[index] = v; + } + ~this() { + foreach(ref v; _data) + if (v) + destroy(v); + } +} + +struct Position { + Vector3d pos; + Direction direction; + this(ref Position p) { + pos = p.pos; + direction = p.direction; + } + this(Vector3d position, Vector3d dir) { + pos = position; + direction = dir; + } + Vector2d calcPlaneCoords(Vector3d v) { + v = v - pos; + switch (direction.dir) with(Dir) { + default: + case NORTH: + return Vector2d(v.x, v.y); + case SOUTH: + return Vector2d(-v.x, v.y); + case EAST: + return Vector2d(v.z, v.y); + case WEST: + return Vector2d(-v.z, v.y); + case UP: + return Vector2d(-v.z, v.x); + case DOWN: + return Vector2d(v.z, v.x); + } + } + void turnLeft() { + direction.turnLeft(); + } + void turnRight() { + direction.turnRight(); + } + void shiftLeft(int step = 1) { + pos += direction.left * step; + } + void shiftRight(int step = 1) { + pos += direction.right * step; + } + void turnUp() { + direction.turnUp(); + } + void turnDown() { + direction.turnDown(); + } + void forward(int step = 1) { + pos += direction.forward * step; + } + void backward(int step = 1) { + pos -= direction.forward * step; + } + void moveUp(int step = 1) { + pos += direction.up * step; + } + void moveDown(int step = 1) { + pos += direction.down * step; + } + void moveLeft(int step = 1) { + pos += direction.left * step; + } + void moveRight(int step = 1) { + pos += direction.right * step; + } +} + + +/// returns opposite direction to specified direction +Dir opposite(Dir d) { + return cast(Dir)(d ^ 1); +} + +Dir turnLeft(Dir d) { + switch (d) with (Dir) { + case WEST: + return SOUTH; + case EAST: + return NORTH; + default: + case NORTH: + return WEST; + case SOUTH: + return EAST; + case UP: + return SOUTH; + case DOWN: + return NORTH; + } +} + +Dir turnRight(Dir d) { + switch (d) with (Dir) { + case WEST: + return NORTH; + case EAST: + return SOUTH; + default: + case NORTH: + return EAST; + case SOUTH: + return WEST; + case UP: + return NORTH; + case DOWN: + return SOUTH; + } +} + +Dir turnUp(Dir d) { + switch (d) with (Dir) { + case WEST: + return UP; + case EAST: + return UP; + default: + case NORTH: + return UP; + case SOUTH: + return UP; + case UP: + return SOUTH; + case DOWN: + return NORTH; + } +} + +Dir turnDown(Dir d) { + switch (d) with (Dir) { + case WEST: + return DOWN; + case EAST: + return DOWN; + default: + case NORTH: + return DOWN; + case SOUTH: + return DOWN; + case UP: + return NORTH; + case DOWN: + return SOUTH; + } +} + + +struct Direction { + this(int x, int y, int z) { + set(x, y, z); + } + this(Vector3d v) { + set(v); + } + this(Dir d) { + set(d); + } + /// returns Y axis rotation angle in degrees (0, 90, 180, 270) + @property float angle() { + switch (dir) with (Dir) { + default: + case NORTH: + return 0; + case SOUTH: + return 180; + case WEST: + return 90; + case EAST: + return 270; + case UP: + case DOWN: + return 0; + } + } + /// set by direction code + void set(Dir d) { + switch (d) with (Dir) { + default: + case NORTH: + set(0, 0, -1); + break; + case SOUTH: + set(0, 0, 1); + break; + case WEST: + set(-1, 0, 0); + break; + case EAST: + set(1, 0, 0); + break; + case UP: + set(0, 1, 0); + break; + case DOWN: + set(0, -1, 0); + break; + } + } + /// set by vector + void set(Vector3d v) { set(v.x, v.y, v.z); } + /// set by vector + void set(int x, int y, int z) { + forward = Vector3d(x, y, z); + if (x) { + dir = (x > 0) ? Dir.EAST : Dir.WEST; + } + else if (y) { + dir = (y > 0) ? Dir.UP : Dir.DOWN; + } + else { + dir = (z > 0) ? Dir.SOUTH : Dir.NORTH; + } + switch (dir) with (Dir) { + case UP: + up = Vector3d(1, 0, 0); + left = Vector3d(0, 0, 1); + break; + case DOWN: + up = Vector3d(1, 0, 0); + left = Vector3d(0, 0, -1); + break; + default: + case NORTH: + up = Vector3d(0, 1, 0); + left = Vector3d(-1, 0, 0); + break; + case SOUTH: + up = Vector3d(0, 1, 0); + left = Vector3d(1, 0, 0); + break; + case EAST: + up = Vector3d(0, 1, 0); + left = Vector3d(0, 0, -1); + break; + case WEST: + up = Vector3d(0, 1, 0); + left = Vector3d(0, 0, 1); + break; + } + down = -up; + right = -left; + forwardUp = forward + up; + forwardDown = forward + down; + forwardLeft = forward + left; + forwardLeftUp = forward + left + up; + forwardLeftDown = forward + left + down; + forwardRight = forward + right; + forwardRightUp = forward + right + up; + forwardRightDown = forward + right + down; + } + + void turnLeft() { + set(.turnLeft(dir)); + } + void turnRight() { + set(.turnRight(dir)); + } + void turnUp() { + set(.turnUp(dir)); + } + void turnDown() { + set(.turnDown(dir)); + } + + Dir dir; + Vector3d forward; + Vector3d up; + Vector3d right; + Vector3d left; + Vector3d down; + Vector3d forwardUp; + Vector3d forwardDown; + Vector3d forwardLeft; + Vector3d forwardLeftUp; + Vector3d forwardLeftDown; + Vector3d forwardRight; + Vector3d forwardRightUp; + Vector3d forwardRightDown; +} + +/// returns number of bits to store integer +int bitsFor(int n) { + int res; + for (res = 0; n > 0; res++) + n >>= 1; + return res; +} + +/// returns 0 for 0, 1 for negatives, 2 for positives +int mySign(int n) { + if (n > 0) + return 1; + else if (n < 0) + return -1; + else + return 0; +} + +immutable ulong RANDOM_MULTIPLIER = 0x5DEECE66D; +immutable ulong RANDOM_MASK = ((cast(ulong)1 << 48) - 1); +immutable ulong RANDOM_ADDEND = cast(ulong)0xB; + +struct Random { + ulong seed; + //Random(); + void setSeed(ulong value) { + seed = (value ^ RANDOM_MULTIPLIER) & RANDOM_MASK; + } + + int next(int bits) { + seed = (seed * RANDOM_MULTIPLIER + RANDOM_ADDEND) & RANDOM_MASK; + return cast(int)(seed >> (48 - bits)); + } + + int nextInt() { + return next(31); + } + int nextInt(int n) { + if ((n & -n) == n) // i.e., n is a power of 2 + return cast(int)((n * cast(long)next(31)) >> 31); + int bits, val; + do { + bits = next(31); + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; + } +} + +const Vector3d[6] DIRECTION_VECTORS = [ + Vector3d(0, 0, -1), + Vector3d(0, 0, 1), + Vector3d(-1, 0, 0), + Vector3d(1, 0, 0), + Vector3d(0, 1, 0), + Vector3d(0, -1, 0) +]; diff --git a/examples/dminer/src/dminer/core/terrain.d b/examples/dminer/src/dminer/core/terrain.d new file mode 100644 index 00000000..2310200d --- /dev/null +++ b/examples/dminer/src/dminer/core/terrain.d @@ -0,0 +1,153 @@ +module dminer.core.terrain; + +import dminer.core.minetypes; + + +struct TerrainGen { + private int dx; + private int dy; + private int xpow; + private int ypow; + private short[] data; + private Random rnd; + private void diamond(int x, int y, int size, int offset) { + int avg = (get(x, y - size) + get(x + size, y) + get(x, y + size) + get(x - size, y)) >> 2; + set(x, y, avg + offset); + } + private void square(int x, int y, int size, int offset) { + int avg = (get(x - size, y - size) + get(x + size, y - size) + get(x - size, y + size) + get(x - size, y - size)) >> 2; + set(x, y, avg + offset); + } + + this(int xbits, int zbits) { + xpow = xbits; + ypow = zbits; + dx = (1 << xpow) + 1; + dy = (1 << ypow) + 1; + data = new short[dx * dy]; + } + ~this() { + } + void filter(int range) { + short[] tmp = new short[dx * dy]; + int div = (range * 2 + 1) * (range * 2 + 1); + for (int y = 0; y < dy; y++) { + for (int x = 0; x < dx; x++) { + int s = 0; + for (int yy = -range; yy <= range; yy++) { + for (int xx = -range; xx <= range; xx++) { + s += get(x + xx, y + yy); + } + } + s /= div; + tmp[(y << ypow) + y + x] = cast(short)s; + } + } + int sz = dx * dy; + data[0 .. sz] = tmp[0 .. sz]; + } + + void generate(int seed, short[] initData, int stepBits) { + rnd.setSeed(seed); + int step = 1 << stepBits; + int index = 0; + for (int y = 0; y <= dy; y += step) { + for (int x = 0; x <= dx; x += step) { + set(x, y, initData[index++]); + } + } + int half = step >> 1; + while (half > 0) { + int scale = step; + for (int y = half; y < dy; y += step) { + for (int x = half; x < dx; x++) { + square(x, y, half, rnd.nextInt(scale * 2) - scale); + } + } + for (int y = 0; y <= dy; y += half) { + for (int x = (y + half) % step; x <= dx; x += step) { + diamond(x, y, half, rnd.nextInt(scale * 2) - scale); + } + } + step >>= 1; + half >>= 1; + } + } + void generateWithScale(int seed, short[] initData, int stepBits, TerrainGen scaleMap) { + rnd.setSeed(seed); + int step = 1 << stepBits; + int index = 0; + for (int y = 0; y <= dy; y += step) { + for (int x = 0; x <= dx; x += step) { + set(x, y, initData[index++]); + } + } + int half = step >> 1; + while (half > 0) { + for (int y = half; y < dy; y += step) { + for (int x = half; x < dx; x++) { + int scale = (scaleMap.get(x, y) * step) >> 8; + scale = rnd.nextInt(scale * 2) - scale; + if (step < 4) + scale = 0; + square(x, y, half, scale); + } + } + for (int y = 0; y <= dy; y += half) { + for (int x = (y + half) % step; x <= dx; x += step) { + int scale = (scaleMap.get(x, y) * step) >> 8; + scale = rnd.nextInt(scale * 2) - scale; + if (step < 4) + scale = 0; + diamond(x, y, half, scale); + } + } + step >>= 1; + half >>= 1; + } + } + @property int width() { + return dx - 1; + } + @property int height() { + return dy - 1; + } + int get(int x, int y) { + if (x < 0 || y < 0 || x >= dx || y >= dy) + return 0; + return data[(y << ypow) + y + x]; + } + void set(int x, int y, int value) { + if (x < 0 || y < 0 || x >= dx || y >= dy) + return; + if (value < -32767) + value = -32767; + if (value > 32767) + value = 32767; + data[(y << ypow) + y + x] = cast(short)value; + } + /// ensure that data is in range [minvalue, maxvalue] + void limit(int minvalue, int maxvalue) { + // find actual min/max + int minv, maxv; + minv = maxv = get(0, 0); + for (int y = 0; y <= dy; y++) { + for (int x = 0; x <= dx; x++) { + int v = get(x, y); + if (minv > v) + minv = v; + if (maxv < v) + maxv = v; + } + } + int mul = (maxvalue - minvalue); + int div = (maxv - minv); + if (div > 0) { + for (int y = 0; y <= dy; y++) { + for (int x = 0; x <= dx; x++) { + set(x, y, minvalue + (get(x, y) - minv) * mul / div); + } + } + } + } +} diff --git a/examples/dminer/src/dminer/core/world.d b/examples/dminer/src/dminer/core/world.d new file mode 100644 index 00000000..1bd4312e --- /dev/null +++ b/examples/dminer/src/dminer/core/world.d @@ -0,0 +1,501 @@ +module dminer.core.world; + +import dminer.core.minetypes; +import dminer.core.blocks; + +const int MAX_VIEW_DISTANCE_BITS = 6; +const int MAX_VIEW_DISTANCE = (1 << MAX_VIEW_DISTANCE_BITS); + +// Layer is 16x16 (CHUNK_DX_SHIFT x CHUNK_DX_SHIFT) cells +immutable int CHUNK_DX_SHIFT = 4; +immutable int CHUNK_DX = (1< bottomLayer) + minLayer = bottomLayer; + if (maxLayer == -1 || maxLayer < topLayer) + maxLayer = topLayer; + } + cell_t get(int x, int y, int z) { + //if (!this) + // return NO_CELL; + ChunkLayer * layer = layers[y & CHUNK_DY_MASK]; + if (!layer) + return NO_CELL; + return layer.get(x & CHUNK_DX_MASK, z & CHUNK_DY_MASK); + } + + /// get, x, y, z are already checked for bounds + cell_t getNoCheck(int x, int y, int z) { + ChunkLayer * layer = layers.ptr[y]; + if (!layer) // likely + return NO_CELL; + return layer.cells.ptr[(z << CHUNK_DX_SHIFT) + x]; // inlined return layer.get(x, z); + } + + void set(int x, int y, int z, cell_t cell) { + int layerIndex = y & CHUNK_DY_MASK; + ChunkLayer * layer = layers.ptr[layerIndex]; + if (!layer) { + layer = new ChunkLayer(); + layers.ptr[layerIndex] = layer; + if (topLayer == -1 || topLayer < layerIndex) + topLayer = layerIndex; + if (bottomLayer == -1 || bottomLayer > layerIndex) + bottomLayer = layerIndex; + } + layer.set(x & CHUNK_DX_MASK, z & CHUNK_DY_MASK, cell); + } + + /// srcpos coords x, z are in chunk bounds + //void getCells(Vector3d srcpos, Vector3d dstpos, Vector3d size, VolumeData & buf); +} + +alias ChunkMatrix = InfiniteMatrix!(Chunk *); + +/// Voxel World +class World { +private: + Position _camPosition; + int maxVisibleRange = MAX_VIEW_DISTANCE; + ChunkMatrix chunks; + DiamondVisitor visitorHelper; +public: + this() { + _camPosition = Position(Vector3d(0, 13, 0), Vector3d(0, 0, 1)); + } + ~this() { + } + @property final ref Position camPosition() { return _camPosition; } + + final cell_t getCell(int x, int y, int z) { + if (!(y & CHUNK_DY_INV_MASK)) { + if (Chunk * p = chunks.get(x >> CHUNK_DX_SHIFT, z >> CHUNK_DX_SHIFT)) + return p.getNoCheck(x & CHUNK_DX_MASK, y, z & CHUNK_DX_MASK); + return NO_CELL; + } + // y out of bounds + if (y < 0) + return BOUND_BOTTOM; + //if (y >= CHUNK_DY) + else + return BOUND_SKY; + } + + final bool isOpaque(int x, int y, int z) { + cell_t cell = getCell(x, y, z); + return BLOCK_TYPE_OPAQUE.ptr[cell] && cell != BOUND_SKY; + } + + final void setCell(int x, int y, int z, cell_t value) { + int chunkx = x >> CHUNK_DX_SHIFT; + int chunkz = z >> CHUNK_DX_SHIFT; + Chunk * p = chunks.get(chunkx, chunkz); + if (!p) { + p = new Chunk(); + chunks.set(chunkx, chunkz, p); + } + p.set(x & CHUNK_DX_MASK, y, z & CHUNK_DX_MASK, value); + } + + void setCellRange(Vector3d pos, Vector3d sz, cell_t value) { + for (int x = 0; x < sz.x; x++) + for (int y = 0; y < sz.y; y++) + for (int z = 0; z < sz.z; z++) + setCell(pos.x + x, pos.y + y, pos.z + z, value); + } + + bool canPass(Vector3d pos) { + return canPass(Vector3d(pos.x - 2, pos.y - 3, pos.z - 2), Vector3d(4, 5, 4)); + } + + bool canPass(Vector3d pos, Vector3d size) { + for (int x = 0; x <= size.x; x++) + for (int z = 0; z <= size.z; z++) + for (int y = 0; y < size.y; y++) { + if (isOpaque(pos.x + x, pos.y + y, pos.z + z)) + return false; + } + return true; + } + final void visitVisibleCells(ref Position position, CellVisitor visitor) { + visitorHelper.init(this, + &position, + visitor); + visitorHelper.visitAll(maxVisibleRange); + } +} + +interface CellVisitor { + //void newDirection(ref Position camPosition); + //void visitFace(World world, ref Position camPosition, Vector3d pos, cell_t cell, Dir face); + void visit(World world, ref Position camPosition, Vector3d pos, cell_t cell, int visibleFaces); +} + +struct DiamondVisitor { + int maxDist; + int maxDistBits; + int dist; + World world; + Position * position; + Vector3d pos0; + CellVisitor visitor; + CellArray visited; + cell_t * visited_ptr; + Vector3dArray oldcells; + Vector3dArray newcells; + ubyte visitedId; + //ubyte visitedEmpty; + int m0; + int m0mask; + void init(World w, Position * pos, CellVisitor v) { + world = w; + position = pos; + visitor = v; + pos0 = position.pos; + } + void visitCell(int vx, int vy, int vz) { + //CRLog::trace("visitCell(%d %d %d) dist=%d", v.x, v.y, v.z, myAbs(v.x) + myAbs(v.y) + myAbs(v.z)); + + //int occupied = visitedOccupied; + int index = (vx + m0) + ((vz + m0) << (maxDistBits + 1)); + if (vy < 0) { + // inverse index for lower half + index ^= m0mask; + } + //int index = diamondIndex(v, maxDistBits); + if (visited_ptr[index] == visitedId)// || cell == visitedEmpty) + return; + visitCellNoCheck(vx, vy, vz); + visited_ptr[index] = visitedId; // cell; + } + + void visitCellNoCheck(int vx, int vy, int vz) { + //if (v * position.direction.forward < dist / 3) // limit by visible from cam + // return; + //Vector3d pos = pos0 + v; + int posx = pos0.x + vx; + int posy = pos0.y + vy; + int posz = pos0.z + vz; + cell_t cell = world.getCell(posx, posy, posz); + + // read cell from world + if (BLOCK_TYPE_VISIBLE.ptr[cell]) { + int visibleFaces = 0; + if (vy <= 0 && !world.isOpaque(posx, posy + 1, posz)) + visibleFaces |= DirMask.MASK_UP; + if (vy >= 0 && !world.isOpaque(posx, posy - 1, posz)) + visibleFaces |= DirMask.MASK_DOWN; + if (vx <= 0 && !world.isOpaque(posx + 1, posy, posz)) + visibleFaces |= DirMask.MASK_EAST; + if (vx >= 0 && !world.isOpaque(posx - 1, posy, posz)) + visibleFaces |= DirMask.MASK_WEST; + if (vz <= 0 && !world.isOpaque(posx, posy, posz + 1)) + visibleFaces |= DirMask.MASK_SOUTH; + if (vz >= 0 && !world.isOpaque(posx, posy, posz - 1)) + visibleFaces |= DirMask.MASK_NORTH; + visitor.visit(world, *position, Vector3d(posx, posy, posz), cell, visibleFaces); + } + // mark as visited + if (BLOCK_TYPE_CAN_PASS.ptr[cell]) + newcells.append(Vector3d(vx, vy, vz)); + //cell = BLOCK_TYPE_CAN_PASS[cell] ? visitedEmpty : visitedOccupied; + } + + bool needVisit(int index) { + if (visited_ptr[index] != visitedId) { + visited_ptr[index] = visitedId; + return true; + } + return false; + } + + static int myAbs(int n) { + return n < 0 ? -n : n; + } + + void visitAll(int maxDistance) { + maxDist = maxDistance; + maxDistance *= 2; + maxDistBits = bitsFor(maxDist); + int maxDistMask = ~((1 << maxDistBits) - 1); + maxDistBits++; + + m0 = 1 << maxDistBits; + m0mask = (m0 - 1) + ((m0 - 1) << (maxDistBits + 1)); + + oldcells.clear(); + newcells.clear(); + oldcells.reserve(maxDist * 4 * 4); + newcells.reserve(maxDist * 4 * 4); + + dist = 1; + + int vsize = ((1 << maxDistBits) * (1 << maxDistBits)) << 2; + visited.clear(); + visited.append(cast(ubyte)0, vsize); + visited_ptr = visited.ptr(); + visitedId = 2; + oldcells.clear(); + oldcells.append(Vector3d(0, 0, 0)); + Dir dir = position.direction.dir; + + int zstep = 1 << (maxDistBits + 1); + for (; dist < maxDistance; dist++) { + // for each distance + if (oldcells.length() == 0) { // no cells to pass through + import dlangui.core.logger; + Log.d("No more cells at distance ", dist); + break; + } + newcells.clear(); + visitedId++; + int maxUp = (((dist + 1) * 7) / 8) + 1; + int maxDown = - (dist < 3 ? 3 : (((dist + 1) * 7) / 8)) - 1; + //CRLog::trace("dist: %d cells: %d", dist, oldcells.length()); + for (int i = 0; i < oldcells.length(); i++) { + Vector3d pt = oldcells[i]; + assert(myAbs(pt.x) + myAbs(pt.y) + myAbs(pt.z) == dist - 1); + if (((pt.x + maxDist) | (pt.y + maxDist) | (pt.z + maxDist)) & maxDistMask) + continue; + if (dist > 2) { + // skip some directions + if (pt.y > maxUp || pt.y < maxDown) + continue; + if (dir == Dir.SOUTH) { + if (pt.z < -1) + continue; + } else if (dir == Dir.NORTH) { + if (pt.z > 1) + continue; + } else if (dir == Dir.EAST) { + if (pt.x < -1) + continue; + } else { // WEST + if (pt.x > 1) + continue; + } + } + int mx = pt.x; + int my = pt.y; + int mz = pt.z; + int sx = mx > 0 ? 1 : 0; + int sy = my > 0 ? 1 : 0; + int sz = mz > 0 ? 1 : 0; + if (mx < 0) { + mx = -mx; + sx = -1; + } + if (my < 0) { + my = -my; + sy = -1; + } + if (mz < 0) { + mz = -mz; + sz = -1; + } + int ymask = sy < 0 ? m0mask : 0; + int index = ((pt.x + m0) + ((pt.z + m0) << (maxDistBits + 1))) ^ ymask; + if (sx && sy && sz) { + //bool noStepZ = (mx > mz) || (my > mz); + // 1, 1, 1 + int xindex = index + (sy < 0 ? -sx : sx); + if (visited_ptr[xindex] != visitedId) { + visitCellNoCheck(pt.x + sx, pt.y, pt.z); + visited_ptr[xindex] = visitedId; + } + int zindex = index + (sz * sy > 0 ? zstep : -zstep); + if (visited_ptr[zindex] != visitedId) { + visitCellNoCheck(pt.x, pt.y, pt.z + sz); + visited_ptr[zindex] = visitedId; + } + if (!ymask && sy < 0) + index ^= m0mask; + if (visited_ptr[index] != visitedId) { + visitCellNoCheck(pt.x, pt.y + sy, pt.z); + visited_ptr[index] = visitedId; + } + } else { + // has 0 in one of coords + if (!sx) { + if (!sy) { + if (!sz) { + // 0, 0, 0 + visitCell(pt.x + 1, pt.y, pt.z); + visitCell(pt.x - 1, pt.y, pt.z); + visitCell(pt.x, pt.y + 1, pt.z); + visitCell(pt.x, pt.y - 1, pt.z); + visitCell(pt.x, pt.y, pt.z + 1); + visitCell(pt.x, pt.y, pt.z - 1); + } else { + // 0, 0, 1 + visitCell(pt.x, pt.y, pt.z + sz); + visitCell(pt.x + 1, pt.y, pt.z); + visitCell(pt.x - 1, pt.y, pt.z); + visitCell(pt.x, pt.y + 1, pt.z); + visitCell(pt.x, pt.y - 1, pt.z); + } + } else { + if (!sz) { + // 0, 1, 0 + visitCell(pt.x, pt.y + sy, pt.z); + visitCell(pt.x + 1, pt.y, pt.z); + visitCell(pt.x - 1, pt.y, pt.z); + visitCell(pt.x, pt.y, pt.z + 1); + visitCell(pt.x, pt.y, pt.z - 1); + } else { + // 0, 1, 1 + visitCell(pt.x, pt.y + sy, pt.z); + visitCell(pt.x, pt.y, pt.z + sz); + visitCell(pt.x + 1, pt.y, pt.z); + visitCell(pt.x - 1, pt.y, pt.z); + } + } + } else { + if (!sy) { + if (!sz) { + // 1, 0, 0 + visitCell(pt.x + sx, pt.y, pt.z); + visitCell(pt.x, pt.y + 1, pt.z); + visitCell(pt.x, pt.y - 1, pt.z); + visitCell(pt.x, pt.y, pt.z + 1); + visitCell(pt.x, pt.y, pt.z - 1); + } else { + // 1, 0, 1 + visitCell(pt.x + sx, pt.y, pt.z); + visitCell(pt.x, pt.y, pt.z + sz); + visitCell(pt.x, pt.y + 1, pt.z); + visitCell(pt.x, pt.y - 1, pt.z); + } + } else { + // 1, 1, 0 + visitCell(pt.x + sx, pt.y, pt.z); + visitCell(pt.x, pt.y + sy, pt.z); + visitCell(pt.x, pt.y, pt.z + 1); + visitCell(pt.x, pt.y, pt.z - 1); + } + } + } + } + newcells.swap(oldcells); + } + } +} + +static short[] TERRAIN_INIT_DATA = [ + // V + 10, 10, 10, 10, 30, 30, 30, 30, 30, 30, 30, 30, 10, 10, 10, 10, 10, + 10, 10, 20, 50, 50, 50, 50, 50, 50, 50, 50, 50, 20, 20, 20, 20, 10, + 10, 20, 20, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 20, 20, 10, + 10, 20, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 20, 10, + 10, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 20, 30, + 30, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 30, + 30, 50, 50, 50, 50, 50, 50, 50, 120, 50, 50, 50, 50, 50, 50, 50, 30, + 30, 50, 50, 50, 50, 50, 50, 110, 140, 130, 50, 50, 50, 50, 50, 50, 30, + 30, 50, 50, 50, 50, 50, 50, 140, 150, 140, 50, 50, 50, 50, 50, 50, 30, // <== + 30, 50, 50, 50, 50, 50, 50, 110, 140, 120, 50, 50, 50, 50, 50, 50, 30, + 30, 50, 50, 50, 50, 50, 50, 50, 110, 50, 50, 50, 50, 50, 50, 50, 30, + 30, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 10, + 30, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 10, + 30, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 40, 50, 10, + 30, 20, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 40, 20, 20, 10, + 30, 20, 20, 50, 50, 50, 50, 50, 50, 50, 40, 20, 20, 20, 20, 20, 10, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 10, 10, 10, 10, 10, + // ^ +]; + +static short[] TERRAIN_SCALE_DATA = [ + // V + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 45, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 80, 20, 20, 20, 40, 50, 40, 20, 20, + 20, 20, 20, 20, 20, 20, 90, 20, 80, 20, 30, 20, 20, 30, 20, 20, 20, + 20, 20, 20, 20, 20, 90, 20, 80, 30, 20, 40, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 90, 30, 40, 30, 50, 20, 20, 20, 20, 20, 20, // <== + 20, 20, 20, 20, 20, 20, 50, 20, 30, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 40, 70, 40, 90, 20, 40, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 80, 20, 50, 70, 50, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 60, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + // ^ +]; + +void initWorldTerrain(World world, int terrSizeBits = 10, int x0 = 0, int z0 = 0) { + import dminer.core.terrain; + int terrSize = 1 << terrSizeBits; + TerrainGen scaleterr = TerrainGen(terrSizeBits, terrSizeBits); // 512x512 + scaleterr.generate(4321, TERRAIN_SCALE_DATA, terrSizeBits - 4); // init grid is 16x16 (1 << (9-7)) + scaleterr.filter(1); + //scaleterr.filter(2); + scaleterr.limit(0, 90); + TerrainGen terr = TerrainGen(terrSizeBits, terrSizeBits); // 512x512 + terr.generateWithScale(123456, TERRAIN_INIT_DATA, terrSizeBits - 4, scaleterr); // init grid is 16x16 (1 << (9-7)) + terr.filter(1); + terr.limit(5, CHUNK_DY * 3 / 4); + terr.filter(1); + for (int x = 0; x < terrSize; x++) { + for (int z = 0; z < terrSize; z++) { + int h = terr.get(x, z); + cell_t cell = 1; + //if (h < CHUNK_DY / 10) + // cell = 100; + //else if (h < CHUNK_DY / 5) + // cell = 101; + //else if (h < CHUNK_DY / 4) + // cell = 102; + //else if (h < CHUNK_DY / 3) + // cell = 103; + //else if (h < CHUNK_DY / 2) + // cell = 104; + //else + // cell = 105; + for (int y = 0; y < h; y++) { + world.setCell(x0 + x - terrSize / 2, y, z0 + z - terrSize / 2, cell); + } + } + } +} diff --git a/examples/dminer/src/minermain.d b/examples/dminer/src/minermain.d new file mode 100644 index 00000000..425f9ea9 --- /dev/null +++ b/examples/dminer/src/minermain.d @@ -0,0 +1,380 @@ +module minermain; + +import dlangui; +import dlangui.graphics.scene.scene3d; +import dlangui.graphics.scene.camera; +import dlangui.graphics.scene.mesh; +import dlangui.graphics.scene.material; +import dlangui.graphics.scene.effect; +import dlangui.graphics.scene.model; +import dlangui.graphics.scene.node; +import dlangui.graphics.scene.light; +import dlangui.graphics.glsupport; +import dlangui.graphics.gldrawbuf; +import dlangui.graphics.scene.effect; +import derelict.opengl3.gl3; +import derelict.opengl3.gl; + +import dminer.core.world; +import dminer.core.minetypes; +import dminer.core.blocks; + +mixin APP_ENTRY_POINT; + +/// entry point for dlangui based application +extern (C) int UIAppMain(string[] args) { + // embed resources listed in views/resources.list into executable + embeddedResourceList.addResources(embedResourcesFromList!("resources.list")()); + //embeddedResourceList.dumpEmbeddedResources(); + + // create window + Window window = Platform.instance.createWindow("DlangUI Voxel RPG", null, WindowFlag.Resizable, 600, 500); + window.mainWidget = new UiWidget(); + + //MeshPart part = new MeshPart(); + + // show window + window.show(); + + // run message loop + return Platform.instance.enterMessageLoop(); +} + +class UiWidget : VerticalLayout, CellVisitor { + this() { + super("OpenGLView"); + layoutWidth = FILL_PARENT; + layoutHeight = FILL_PARENT; + alignment = Align.Center; + try { + parseML(q{ + { + margins: 0 + padding: 0 + //backgroundImageId: "tx_fabric.tiled" + backgroundColor: 0x000000; + layoutWidth: fill + layoutHeight: fill + + VerticalLayout { + id: glView + margins: 0 + padding: 0 + layoutWidth: fill + layoutHeight: fill + TextWidget { text: "MinerD example"; textColor: "red"; fontSize: 150%; fontWeight: 800; fontFace: "Arial" } + VSpacer { layoutWeight: 30 } + TextWidget { id: lblPosition; text: ""; backgroundColor: 0x80202020; textColor: 0xFFE0E0 } + } + } + }, "", this); + } catch (Exception e) { + Log.e("Failed to parse dml", e); + } + // assign OpenGL drawable to child widget background + childById("glView").backgroundDrawable = DrawableRef(new OpenGLDrawable(&doDraw)); + + _scene = new Scene3d(); + + _cam = new Camera(); + _cam.translate(vec3(0, 14, -7)); + + _scene.activeCamera = _cam; + + dirLightNode = new Node3d(); + dirLightNode.rotateY(-15); + dirLightNode.translateX(2); + dirLightNode.translateY(3); + dirLightNode.translateZ(0); + dirLightNode.light = Light.createPoint(vec3(2, 2, 2), 15); //Light.createDirectional(vec3(1, 0.5, 0.5)); + dirLightNode.light.enabled = true; + _scene.addChild(dirLightNode); + + + int x0 = 0; + int y0 = 0; + int z0 = 0; + + + _minerMesh = new Mesh(VertexFormat(VertexElementType.POSITION, VertexElementType.NORMAL, VertexElementType.COLOR, VertexElementType.TEXCOORD0)); + _world = new World(); + + initWorldTerrain(_world); + + int cy0 = 3; + for (int y = CHUNK_DY - 1; y > 0; y--) + if (!_world.canPass(Vector3d(0, y, 0))) { + cy0 = y; + break; + } + _world.camPosition = Position(Vector3d(0, cy0, 0), Vector3d(0, 0, 1)); + + _world.setCellRange(Vector3d(3, 11, 5), Vector3d(1, 100, 1), 1); + _world.setCellRange(Vector3d(13, 11, -5), Vector3d(1, 100, 1), 3); + _world.setCellRange(Vector3d(-6, 11, 10), Vector3d(1, 100, 1), 4); + _world.setCellRange(Vector3d(-8, 11, 15), Vector3d(1, 100, 1), 5); + _world.setCellRange(Vector3d(12, 11, -7), Vector3d(1, 100, 1), 6); + _world.setCellRange(Vector3d(5, 11, 9), Vector3d(1, 100, 1), 7); + _world.setCellRange(Vector3d(9, 11, 5), Vector3d(1, 100, 1), 7); + _world.setCellRange(Vector3d(-5, 11, 9), Vector3d(1, 100, 1), 7); + _world.setCellRange(Vector3d(9, 11, -5), Vector3d(1, 100, 1), 7); + _world.setCellRange(Vector3d(5, 11, -9), Vector3d(1, 100, 1), 7); + _world.setCellRange(Vector3d(-9, 11, 5), Vector3d(1, 100, 1), 7); + _world.setCellRange(Vector3d(7, 11, 3), Vector3d(1, 100, 1), 8); + _world.setCellRange(Vector3d(-7, 11, 3), Vector3d(1, 100, 1), 8); + _world.setCellRange(Vector3d(7, 11, -3), Vector3d(1, 100, 1), 8); + _world.setCellRange(Vector3d(-7, 11, 3), Vector3d(1, 100, 1), 8); + + updateCamPosition(false); + updateMinerMesh(); + + Material minerMaterial = new Material(EffectId("textured.vert", "textured.frag", null), "blocks"); + minerMaterial.ambientColor = vec3(0.2,0.2,0.2); + minerMaterial.textureLinear = false; + Model minerDrawable = new Model(minerMaterial, _minerMesh); + Node3d minerNode = new Node3d("miner", minerDrawable); + _scene.addChild(minerNode); + + + focusable = true; + } + + /// process key event, return true if event is processed. + override bool onKeyEvent(KeyEvent event) { + if (event.action == KeyAction.KeyDown) { + switch(event.keyCode) with(KeyCode) { + case KEY_W: + case UP: + _world.camPosition.forward(1); + updateCamPosition(); + return true; + case DOWN: + case KEY_S: + _world.camPosition.backward(1); + updateCamPosition(); + return true; + case KEY_A: + case LEFT: + _world.camPosition.turnLeft(); + updateCamPosition(); + return true; + case KEY_D: + case RIGHT: + _world.camPosition.turnRight(); + updateCamPosition(); + return true; + case HOME: + case KEY_E: + _world.camPosition.moveUp(); + updateCamPosition(); + return true; + case END: + case KEY_Q: + _world.camPosition.moveDown(); + updateCamPosition(); + return true; + case KEY_Z: + _world.camPosition.moveLeft(); + updateCamPosition(); + return true; + case KEY_C: + _world.camPosition.moveRight(); + updateCamPosition(); + return true; + case KEY_F: + flying = !flying; + if (!flying) + _world.camPosition.pos.y = CHUNK_DY - 3; + updateCamPosition(); + return true; + case KEY_U: + enableMeshUpdate = !enableMeshUpdate; + updateCamPosition(); + return true; + default: + return false; + } + } + return false; + } + + Node3d dirLightNode; + + void visit(World world, ref Position camPosition, Vector3d pos, cell_t cell, int visibleFaces) { + BlockDef def = BLOCK_DEFS[cell]; + def.createFaces(world, world.camPosition, pos, visibleFaces, _minerMesh); + } + + bool flying = false; + bool enableMeshUpdate = true; + + void updateCamPosition(bool animateIt = true) { + import std.string; + import std.conv : to; + import std.utf : toUTF32; + import std.format; + + if (!flying) { + while(_world.canPass(_world.camPosition.pos + Vector3d(0, -1, 0))) + _world.camPosition.pos += Vector3d(0, -1, 0); + if(!_world.canPass(_world.camPosition.pos + Vector3d(0, -1, 0))) { + if (_world.canPass(_world.camPosition.pos + Vector3d(0, 1, 0))) + _world.camPosition.pos += Vector3d(0, 1, 0); + else if (_world.canPass(_world.camPosition.pos + Vector3d(1, 0, 0))) + _world.camPosition.pos += Vector3d(1, 0, 0); + else if (_world.canPass(_world.camPosition.pos + Vector3d(-1, 0, 0))) + _world.camPosition.pos += Vector3d(-1, 0, 0); + else if (_world.canPass(_world.camPosition.pos + Vector3d(0, 0, 1))) + _world.camPosition.pos += Vector3d(0, 0, 1); + else if (_world.canPass(_world.camPosition.pos + Vector3d(0, 0, -1))) + _world.camPosition.pos += Vector3d(0, 0, -1); + while(_world.canPass(_world.camPosition.pos + Vector3d(0, -1, 0))) + _world.camPosition.pos += Vector3d(0, -1, 0); + } + } + + setPos(vec3(_world.camPosition.pos.x + 0.5f, _world.camPosition.pos.y + 0.5f, _world.camPosition.pos.z + 0.5f), animateIt); + setAngle(_world.camPosition.direction.angle, animateIt); + Widget w = childById("lblPosition"); + string dir = _world.camPosition.direction.dir.to!string; + dstring s = format("pos(%d,%d) h=%d %s [F]lying: %s [U]pdateMesh: %s", _world.camPosition.pos.x, _world.camPosition.pos.z, _world.camPosition.pos.y, dir, + flying, enableMeshUpdate).toUTF32; + w.text = s; + if (enableMeshUpdate) + updateMinerMesh(); + } + + void updateMinerMesh() { + _minerMesh.reset(); + long ts = currentTimeMillis; + _world.visitVisibleCells(_world.camPosition, this); + long duration = currentTimeMillis - ts; + Log.d("DiamondVisitor finished in ", duration, " ms ", "Vertex count: ", _minerMesh.vertexCount); + + invalidate(); + //for (int i = 0; i < 20; i++) + // Log.d("vertex: ", _minerMesh.vertex(i)); + } + + World _world; + vec3 _position; + float _angle; + vec3 _animatingPosition; + float _animatingAngle; + + void setPos(vec3 newPos, bool animateIt = false) { + if (animateIt) { + _position = newPos; + } else { + _animatingPosition = newPos; + _position = newPos; + } + } + + void setAngle(float newAngle, bool animateIt = false) { + if (animateIt) { + _angle = newAngle; + } else { + _animatingAngle = newAngle; + _angle = newAngle; + } + } + + /// returns true is widget is being animated - need to call animate() and redraw + @property override bool animating() { return true; } + /// animates window; interval is time left from previous draw, in hnsecs (1/10000000 of second) + override void animate(long interval) { + //Log.d("animating"); + if (_animatingAngle != _angle) { + float delta = _angle - _animatingAngle; + if (delta > 180) + delta -= 360; + else if (delta < -180) + delta += 360; + float dist = delta < 0 ? -delta : delta; + if (dist < 5) { + _animatingAngle = _angle; + } else { + float speed = 360; + float step = speed * interval / 10000000.0f; + //Log.d("Rotate animation delta=", delta, " dist=", dist, " elapsed=", interval, " step=", step); + if (step > dist) + step = dist; + delta = delta * (step /dist); + _animatingAngle += delta; + } + } + if (_animatingPosition != _position) { + vec3 delta = _position - _animatingPosition; + float dist = delta.length; + if (dist < 0.01) { + _animatingPosition = _position; + // done + } else { + float speed = 8; + if (dist > 2) + speed = (dist - 2) * 3 + speed; + float step = speed * interval / 10000000.0f; + //Log.d("Move animation delta=", delta, " dist=", dist, " elapsed=", interval, " step=", step); + if (step > dist) + step = dist; + delta = delta * (step / dist); + _animatingPosition += delta; + } + } + invalidate(); + } + float angle = 0; + + Scene3d _scene; + Camera _cam; + Mesh _minerMesh; + + + /// this is OpenGLDrawableDelegate implementation + private void doDraw(Rect windowRect, Rect rc) { + _cam.setPerspective(rc.width, rc.height, 45.0f, 0.1, 100); + //_cam.translate(vec3( + // childById!ScrollBar("sbTranslationX").position / 10.0f, + // childById!ScrollBar("sbTranslationY").position / 10.0f, + // childById!ScrollBar("sbTranslationZ").position / 10.0f)); + //_world.camPosition.pos.x; + _cam.setIdentity(); + _cam.translate(_animatingPosition); + _cam.rotateY(_animatingAngle); + //_cam.rotateX(-15); + //_cam.lookAt(vec3(dir.x, dir.y, dir.z), vec3(pos.x, pos.y, pos.z), vec3(0,1,0)); + //_cam.translateX(_world.camPosition.pos.x); + //_cam.translateY(_world.camPosition.pos.y); + //_cam.translateZ(_world.camPosition.pos.z); + dirLightNode.setIdentity(); + dirLightNode.translate(_animatingPosition); + dirLightNode.rotateY(_animatingAngle); + //dirLightNode.setIdentity(); + //dirLightNode.translateX(_world.camPosition.pos.x); + //dirLightNode.translateY(_world.camPosition.pos.y); + //dirLightNode.translateZ(_world.camPosition.pos.z); + //_cam.rotateX(rotationX); + //_cam.rotateY(rotationY); + //_cam.rotateZ(rotationZ); + //_cam.translate(vec3(-1, -1.5, -1)); // - angle/1000 + //_cam.translate(vec3(0, 0, -1.1)); // - angle/1000 + //_cam.translate(vec3(0, 3, - angle/1000)); // + //_cam.rotateZ(30.0f + angle * 0.3456778); + + checkgl!glEnable(GL_CULL_FACE); + //checkgl!glDisable(GL_CULL_FACE); + checkgl!glEnable(GL_DEPTH_TEST); + checkgl!glCullFace(GL_BACK); + + _scene.drawScene(false); + + checkgl!glDisable(GL_DEPTH_TEST); + checkgl!glDisable(GL_CULL_FACE); + } + + ~this() { + destroy(_scene); + destroy(_world); + } +} diff --git a/examples/dminer/views/res/i18n/en.ini b/examples/dminer/views/res/i18n/en.ini new file mode 100644 index 00000000..576671ef --- /dev/null +++ b/examples/dminer/views/res/i18n/en.ini @@ -0,0 +1,40 @@ +EXIT=Exit +MENU_FILE=&File +MENU_FILE_NEW=&New +MENU_FILE_OPEN=&Open +MENU_FILE_OPEN_RECENT=Open recent +MENU_FILE_SAVE=&Save +MENU_FILE_EXIT=E&xit +MENU_EDIT=&Edit +MENU_EDIT_COPY=&Copy +MENU_EDIT_PASTE=&Paste +MENU_EDIT_CUT=Cu&t +MENU_EDIT_UNDO=&Undo +MENU_EDIT_REDO=&Redo +MENU_EDIT_INDENT=Indent block +MENU_EDIT_UNINDENT=Unindent block +MENU_EDIT_TOGGLE_LINE_COMMENT=Toggle line comment +MENU_EDIT_TOGGLE_BLOCK_COMMENT=Toggle block comment +MENU_EDIT_PREFERENCES=&Preferences +MENU_VIEW=&View +MENU_VIEW_LANGUAGE=Interface &Language +MENU_VIEW_LANGUAGE_EN=English +MENU_VIEW_LANGUAGE_RU=Русский +MENU_VIEW_THEME=&Theme +MENU_VIEW_THEME_DEFAULT=Default +MENU_VIEW_THEME_DARK=Dark +MENU_VIEW_THEME_CUSTOM1=Custom 1 +MENU_WINDOW=&Window +MENU_WINDOW_PREFERENCES=&Preferences +MENU_HELP=&Help +MENU_HELP_VIEW_HELP=&View help +MENU_HELP_ABOUT=&About +MENU_DEBUG_UPDATE_PREVIEW=Update Preview + +TAB_LONG_LIST=Long list +TAB_BUTTONS=Buttons +TAB_ANIMATION=Animation +TAB_TABLE_LAYOUT=Table layout +TAB_EDITORS=Editors +TAB_CANVAS=Canvas + diff --git a/examples/dminer/views/res/i18n/ru.ini b/examples/dminer/views/res/i18n/ru.ini new file mode 100644 index 00000000..ab52d3d0 --- /dev/null +++ b/examples/dminer/views/res/i18n/ru.ini @@ -0,0 +1,34 @@ +EXIT=Выход +MENU_FILE=&Файл +MENU_FILE_OPEN=&Открыть +MENU_FILE_OPEN_RECENT=Открыть из последних +MENU_FILE_SAVE=&Сохранить +MENU_FILE_EXIT=Вы&ход +MENU_EDIT=&Правка +MENU_EDIT_COPY=&Копировать +MENU_EDIT_PASTE=&Вставить +MENU_EDIT_CUT=Вырезать +MENU_EDIT_UNDO=&Отмена +MENU_EDIT_REDO=&Повторить +MENU_EDIT_PREFERENCES=&Настройки +MENU_VIEW=&Вид +MENU_VIEW_LANGUAGE=&Язык интерфейса +MENU_VIEW_LANGUAGE_EN=English +MENU_VIEW_LANGUAGE_RU=Русский +MENU_VIEW_THEME=&Тема +MENU_VIEW_THEME_DEFAULT=Стандартная +MENU_VIEW_THEME_DARK=Тёмная +MENU_VIEW_THEME_CUSTOM1=Пример 1 +MENU_WINDOW=&Окно +MENU_WINDOW_PREFERENCES=&Настройки +MENU_HELP=&Справка +MENU_HELP_VIEW_HELP=&Просмотр справки +MENU_HELP_ABOUT=&О программе + +TAB_LONG_LIST=Длинный список +TAB_BUTTONS=Кнопки +TAB_ANIMATION=Анимация +TAB_TABLE_LAYOUT=Табличный layout +TAB_EDITORS=Редакторы + + diff --git a/examples/dminer/views/res/mdpi/blocks.png b/examples/dminer/views/res/mdpi/blocks.png new file mode 100644 index 00000000..0440ab4b Binary files /dev/null and b/examples/dminer/views/res/mdpi/blocks.png differ diff --git a/examples/dminer/views/res/mdpi/cr3_logo.png b/examples/dminer/views/res/mdpi/cr3_logo.png new file mode 100644 index 00000000..6e0d94ea Binary files /dev/null and b/examples/dminer/views/res/mdpi/cr3_logo.png differ diff --git a/examples/dminer/views/resources.list b/examples/dminer/views/resources.list new file mode 100644 index 00000000..46354f80 --- /dev/null +++ b/examples/dminer/views/resources.list @@ -0,0 +1,4 @@ +res/i18n/en.ini +res/i18n/ru.ini +res/mdpi/cr3_logo.png +res/mdpi/blocks.png