mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-06 00:55:50 +00:00
Cleaned up some old code
This commit is contained in:
parent
6b6f5b95ec
commit
782a90066d
@ -296,6 +296,22 @@ static void createOgreMesh(Mesh *mesh, NiTriShape *shape, const String &material
|
||||
|
||||
// Set material if one was given
|
||||
if(!material.empty()) sub->setMaterialName(material);
|
||||
|
||||
/* Old commented D code. Might be useful when reimplementing
|
||||
animation.
|
||||
// Assign this submesh to the given bone
|
||||
VertexBoneAssignment v;
|
||||
v.boneIndex = ((Bone*)bone)->getHandle();
|
||||
v.weight = 1.0;
|
||||
|
||||
std::cerr << "+ Assigning bone index " << v.boneIndex << "\n";
|
||||
|
||||
for(int i=0; i < numVerts; i++)
|
||||
{
|
||||
v.vertexIndex = i;
|
||||
sub->addBoneAssignment(v);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Helper math functions. Reinventing linear algebra for the win!
|
||||
@ -632,3 +648,109 @@ MeshPtr NIFLoader::load(const std::string &name,
|
||||
// Nope, create a new one.
|
||||
return MeshManager::getSingleton().createManual(name, group, &g_sing);
|
||||
}
|
||||
|
||||
/* More code currently not in use, from the old D source. This was
|
||||
used in the first attempt at loading NIF meshes, where each submesh
|
||||
in the file was given a separate bone in a skeleton. Unfortunately
|
||||
the OGRE skeletons can't hold more than 256 bones, and some NIFs go
|
||||
way beyond that. The code might be of use if we implement animated
|
||||
submeshes like this (the part of the NIF that is animated is
|
||||
usually much less than the entire file, but the method might still
|
||||
not be water tight.)
|
||||
|
||||
// Insert a raw RGBA image into the texture system.
|
||||
extern "C" void ogre_insertTexture(char* name, uint32_t width, uint32_t height, void *data)
|
||||
{
|
||||
TexturePtr texture = TextureManager::getSingleton().createManual(
|
||||
name, // name
|
||||
"General", // group
|
||||
TEX_TYPE_2D, // type
|
||||
width, height, // width & height
|
||||
0, // number of mipmaps
|
||||
PF_BYTE_RGBA, // pixel format
|
||||
TU_DEFAULT); // usage; should be TU_DYNAMIC_WRITE_ONLY_DISCARDABLE for
|
||||
// textures updated very often (e.g. each frame)
|
||||
|
||||
// Get the pixel buffer
|
||||
HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer();
|
||||
|
||||
// Lock the pixel buffer and get a pixel box
|
||||
pixelBuffer->lock(HardwareBuffer::HBL_NORMAL); // for best performance use HBL_DISCARD!
|
||||
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||
|
||||
void *dest = pixelBox.data;
|
||||
|
||||
// Copy the data
|
||||
memcpy(dest, data, width*height*4);
|
||||
|
||||
// Unlock the pixel buffer
|
||||
pixelBuffer->unlock();
|
||||
}
|
||||
|
||||
// We need this later for animated meshes.
|
||||
extern "C" void* ogre_setupSkeleton(char* name)
|
||||
{
|
||||
SkeletonPtr skel = SkeletonManager::getSingleton().create(
|
||||
name, "Closet", true);
|
||||
|
||||
skel->load();
|
||||
|
||||
// Create all bones at the origin and unrotated. This is necessary
|
||||
// since our submeshes each have their own model space. We must
|
||||
// move the bones after creating an entity, then copy this entity.
|
||||
return (void*)skel->createBone();
|
||||
}
|
||||
|
||||
extern "C" void *ogre_insertBone(char* name, void* rootBone, int32_t index)
|
||||
{
|
||||
return (void*) ( ((Bone*)rootBone)->createChild(index) );
|
||||
}
|
||||
*/
|
||||
/* This was the D part:
|
||||
|
||||
// Create a skeleton and get the root bone (index 0)
|
||||
BonePtr bone = ogre_setupSkeleton(name);
|
||||
|
||||
// Reset the bone index. The next bone to be created has index 1.
|
||||
boneIndex = 1;
|
||||
// Create a mesh and assign the skeleton to it
|
||||
MeshPtr mesh = ogre_setupMesh(name);
|
||||
|
||||
// Loop through the nodes, creating submeshes, materials and
|
||||
// skeleton bones in the process.
|
||||
handleNode(node, bone, mesh);
|
||||
|
||||
// Create the "template" entity
|
||||
EntityPtr entity = ogre_createEntity(name);
|
||||
|
||||
// Loop through once again, this time to set the right
|
||||
// transformations on the entity's SkeletonInstance. The order of
|
||||
// children will be the same, allowing us to reference bones using
|
||||
// their boneIndex.
|
||||
int lastBone = boneIndex;
|
||||
boneIndex = 1;
|
||||
transformBones(node, entity);
|
||||
if(lastBone != boneIndex) writefln("WARNING: Bone number doesn't match");
|
||||
|
||||
if(!hasBBox)
|
||||
ogre_setMeshBoundingBox(mesh, minX, minY, minZ, maxX, maxY, maxZ);
|
||||
|
||||
return entity;
|
||||
}
|
||||
void handleNode(Node node, BonePtr root, MeshPtr mesh)
|
||||
{
|
||||
// Insert a new bone for this node
|
||||
BonePtr bone = ogre_insertBone(node.name, root, boneIndex++);
|
||||
|
||||
}
|
||||
|
||||
void transformBones(Node node, EntityPtr entity)
|
||||
{
|
||||
ogre_transformBone(entity, &node.trafo, boneIndex++);
|
||||
|
||||
NiNode n = cast(NiNode)node;
|
||||
if(n !is null)
|
||||
foreach(Node nd; n.children)
|
||||
transformBones(nd, entity);
|
||||
}
|
||||
*/
|
||||
|
@ -1,272 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (keys.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program 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
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* This module handles keyboard and mouse button configuration
|
||||
*/
|
||||
|
||||
module input.keys;
|
||||
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
|
||||
import input.ois;
|
||||
|
||||
// List of all functions we need to map to keys. If you add new keys,
|
||||
// REMEMBER to add strings for them below as well. TODO: We should
|
||||
// redo this entire section so that we insert actual functions into
|
||||
// the system instead. That way, if we do not insert a function, the
|
||||
// key gets treated as a "non-event" key. Then we will also force the
|
||||
// definition of strings and function call to be in the same
|
||||
// place. The Keys enum can be eliminated, really. Default keysyms can
|
||||
// be added when the functions are inserted. But don't do anything
|
||||
// until you know how this will interact with script code.
|
||||
enum Keys
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// Movement
|
||||
MoveLeft, MoveRight,
|
||||
TurnLeft, TurnRight,
|
||||
MoveForward, MoveBackward,
|
||||
|
||||
// Used eg. when flying or swimming
|
||||
MoveUp, MoveDown,
|
||||
|
||||
// These are handled as events, while the above are not.
|
||||
FirstEvent,
|
||||
|
||||
// Sound control
|
||||
MainVolUp, MainVolDown,
|
||||
MusVolUp, MusVolDown,
|
||||
SfxVolUp, SfxVolDown,
|
||||
Mute,
|
||||
|
||||
// These will not be part of the finished product
|
||||
Fullscreen,
|
||||
ToggleBattleMusic,
|
||||
PhysMode, // Toggle physics mode between walking, flying and ghost
|
||||
Nighteye, // Full ambient lighting
|
||||
ToggleGui,// Turn the GUI on/off
|
||||
Console, // Turn console on/off
|
||||
Debug,
|
||||
|
||||
// Misc
|
||||
Pause,
|
||||
ScreenShot,
|
||||
Exit,
|
||||
|
||||
Length
|
||||
}
|
||||
|
||||
// List of keyboard-bound functions
|
||||
char[][] keyToString;
|
||||
|
||||
// Lookup for keyboard key names. TODO: This is currently case
|
||||
// sensitive, we should use my own AA to fix that.
|
||||
int[char[]] stringToKeysym;
|
||||
|
||||
// Represents a key binding for a single function. Each function can
|
||||
// have any number keys bound to it, including mouse buttons. This
|
||||
// might be extended later to allow joystick buttons and other input
|
||||
// devices.
|
||||
struct KeyBind
|
||||
{
|
||||
int syms[];
|
||||
|
||||
// Does the given keysym match this binding?
|
||||
bool isMatch(int sym, char ch = 0)
|
||||
{
|
||||
assert(sym != 0, "don't send empty syms to isMatch");
|
||||
|
||||
// We don't match characters yet
|
||||
if(sym == KC.CharOnly)
|
||||
return false;
|
||||
|
||||
foreach(s; syms)
|
||||
if(sym == s) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Does any of the given syms match this binding?
|
||||
bool isMatchArray(int arr[] ...)
|
||||
{
|
||||
foreach(i; arr)
|
||||
if(i!=0 && isMatch(i)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assign key bindings to this structure. Can be called multiple
|
||||
// times or with multiple paramters (or an array) to bind multiple
|
||||
// keys.
|
||||
void bind(int symlist[] ...)
|
||||
{
|
||||
syms ~= symlist;
|
||||
}
|
||||
|
||||
// Remove all bindings to this function
|
||||
void clear()
|
||||
{
|
||||
syms = null;
|
||||
}
|
||||
|
||||
// Remove the given syms from this binding, if found.
|
||||
void remove(int symlist[] ...)
|
||||
{
|
||||
foreach(rs; symlist)
|
||||
// Just loop though all the syms and set matching values to
|
||||
// zero. isMatch() will ignore zeros.
|
||||
foreach(ref s; syms)
|
||||
if(s == rs) s = 0;
|
||||
}
|
||||
|
||||
// Turn the keysym list into a comma separated list of key names
|
||||
char[] getString()
|
||||
{
|
||||
char[] res = null;
|
||||
bool notFirst = false;
|
||||
|
||||
foreach(int k; syms)
|
||||
if(k != 0) // Ignore empty keysyms
|
||||
{
|
||||
if(notFirst) res ~= ",";
|
||||
else notFirst = true;
|
||||
|
||||
res ~= keysymToString[k];
|
||||
}
|
||||
|
||||
//writefln("getString returned %s", res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
KeyBindings keyBindings;
|
||||
|
||||
// This structure holds the bindings of all the functions
|
||||
struct KeyBindings
|
||||
{
|
||||
KeyBind[] bindings;
|
||||
|
||||
// Bind the given function to the given key(s)
|
||||
void bind(Keys func, char[] key1, char[] key2 = "")
|
||||
{
|
||||
bind(func, getSym(key1), getSym(key2));
|
||||
}
|
||||
|
||||
void bind(Keys func, int syms[] ...)
|
||||
{
|
||||
// Find other bindings that match this key
|
||||
foreach(int i, ref KeyBind kb; bindings)
|
||||
if(kb.isMatchArray(syms))
|
||||
kb.remove(syms);
|
||||
bindings[func].bind(syms);
|
||||
}
|
||||
|
||||
// Find the function that matches the given keysym. We could
|
||||
// optimize this, but I'm not sure it's worth it.
|
||||
Keys findMatch(KC keysym, dchar ch)
|
||||
{
|
||||
int start=cast(int)Keys.FirstEvent + 1;
|
||||
foreach(int i, ref KeyBind kb; bindings[start..$])
|
||||
if( kb.isMatch(keysym, ch) )
|
||||
return cast(Keys)(i+start);
|
||||
return cast(Keys)0; // No match
|
||||
}
|
||||
|
||||
static int getSym(char[] key)
|
||||
{
|
||||
key = strip(key);
|
||||
if(key.length)
|
||||
{
|
||||
int *p = key in stringToKeysym;
|
||||
if(p) return *p;
|
||||
else writefln("Warning: unknown key '%s'", key);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Bind a function to a comma-separated key list (intended to be
|
||||
// used directly with the ini file reader.)
|
||||
void bindComma(Keys func, char[] keys)
|
||||
{
|
||||
int index = keys.find(',');
|
||||
if(index != -1)
|
||||
{
|
||||
// Bind the first in the list
|
||||
bind(func, keys[0..index]);
|
||||
// Recurse on the rest
|
||||
bindComma(func, keys[index+1..$]);
|
||||
}
|
||||
// Last or only element in the list
|
||||
else bind(func, keys);
|
||||
}
|
||||
|
||||
// Remove all key bindings
|
||||
void clear()
|
||||
{
|
||||
foreach(ref kb; bindings)
|
||||
kb.clear();
|
||||
}
|
||||
|
||||
void initKeys()
|
||||
{
|
||||
// Keyboard functions
|
||||
keyToString.length = Keys.Length;
|
||||
|
||||
keyToString[Keys.MoveLeft] = "Move Left";
|
||||
keyToString[Keys.MoveRight] = "Move Right";
|
||||
keyToString[Keys.TurnLeft] = "Turn Left";
|
||||
keyToString[Keys.TurnRight] = "Turn Right";
|
||||
keyToString[Keys.MoveForward] = "Move Forward";
|
||||
keyToString[Keys.MoveBackward] = "Move Backward";
|
||||
keyToString[Keys.MoveUp] = "Move Up";
|
||||
keyToString[Keys.MoveDown] = "Move Down";
|
||||
|
||||
keyToString[Keys.MainVolUp] = "Increase Main Volume";
|
||||
keyToString[Keys.MainVolDown] = "Decrease Main Volume";
|
||||
keyToString[Keys.MusVolUp] = "Increase Music Volume";
|
||||
keyToString[Keys.MusVolDown] = "Decrease Music Volume";
|
||||
keyToString[Keys.SfxVolUp] = "Increase SFX Volume";
|
||||
keyToString[Keys.SfxVolDown] = "Decrease SFX Volume";
|
||||
keyToString[Keys.Mute] = "Mute Sound";
|
||||
|
||||
keyToString[Keys.Fullscreen] = "Toggle Fullscreen Mode";
|
||||
keyToString[Keys.ToggleBattleMusic] = "Toggle Battle Music";
|
||||
keyToString[Keys.PhysMode] = "Toggle Physics Mode";
|
||||
keyToString[Keys.Nighteye] = "Toggle Nighteye";
|
||||
keyToString[Keys.ToggleGui] = "Toggle GUI";
|
||||
keyToString[Keys.Console] = "Console";
|
||||
keyToString[Keys.Debug] = "OGRE Test Action";
|
||||
|
||||
keyToString[Keys.Pause] = "Pause";
|
||||
keyToString[Keys.ScreenShot] = "Screen Shot";
|
||||
keyToString[Keys.Exit] = "Quick Exit";
|
||||
//keyToString[Keys.] = "";
|
||||
|
||||
bindings.length = Keys.Length;
|
||||
|
||||
// Store all the key strings in a lookup-table
|
||||
foreach(int k, ref char[] s; keysymToString)
|
||||
if(s.length) stringToKeysym[s] = k;
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (bindings.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program 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
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module ogre.bindings;
|
||||
|
||||
import nif.misc; // for Transformation
|
||||
import ogre.ogre; // for Placement
|
||||
|
||||
import core.resource;
|
||||
|
||||
/*
|
||||
* This module is the interface to OGRE from D. Since OGRE is written
|
||||
* in C++, all the code that deals directly with the graphics engine
|
||||
* is packaged in a bunch of C++ functions. These functions are
|
||||
* exported from C++ through the C calling convention, and imported
|
||||
* here.
|
||||
*
|
||||
* Note that the C calling convension is not in any way type
|
||||
* safe. This is convenient, as it allows us to send pointers as one
|
||||
* type and recieve them as another, without casting, but also
|
||||
* dangerous since it opens for some nasty bugs.
|
||||
*/
|
||||
|
||||
// Represents a pointer to a Node in the OGRE engine. We never use
|
||||
// these directly in D code, only pass them back to the C++ code.
|
||||
typedef void* NodePtr;
|
||||
|
||||
extern(C):
|
||||
|
||||
// Do engine configuration. Returns 0 if we should continue, 1 if
|
||||
// not.
|
||||
int ogre_configure(int showConfig, // Do we show the config dialogue?
|
||||
char *plugincfg,// Name of 'plugin.cfg' file
|
||||
int debutOut); // Enable or disable debug output
|
||||
|
||||
// Sets up the window
|
||||
void ogre_initWindow();
|
||||
|
||||
// Set up an empty scene.
|
||||
void ogre_makeScene();
|
||||
|
||||
// Set the ambient light and "sunlight"
|
||||
void ogre_setAmbient(float r, float g, float b,
|
||||
float rs, float gs, float bs);
|
||||
|
||||
// Set fog color and view distance
|
||||
void ogre_setFog(float rf, float gf, float bf,
|
||||
float flow, float fhigh);
|
||||
|
||||
// Create a simple sky dome
|
||||
int ogre_makeSky();
|
||||
|
||||
// Toggle full ambient lighting on and off
|
||||
void ogre_toggleLight();
|
||||
|
||||
// Enter main rendering loop
|
||||
void ogre_startRendering();
|
||||
|
||||
// Cleans up after ogre
|
||||
void ogre_cleanup();
|
||||
|
||||
// Gets a child SceneNode from the root node, then detatches it to
|
||||
// hide it from view. Used for creating the "template" node associated
|
||||
// with a NIF mesh.
|
||||
NodePtr ogre_getDetachedNode();
|
||||
|
||||
// Convert a Morrowind rotation (3 floats) to a quaternion (4 floats)
|
||||
void ogre_mwToQuaternion(float *mw, float *quat);
|
||||
|
||||
// Create a copy of the given scene node, with the given coordinates
|
||||
// and rotation (as a quaternion.)
|
||||
NodePtr ogre_insertNode(NodePtr base, char* name,
|
||||
float *pos, float *quat, float scale);
|
||||
|
||||
// Get the world transformation of a node, returned as a translation
|
||||
// and a matrix. The matrix includes both rotation and scaling. The
|
||||
// buffers given must be large enough to store the result (3 and 9
|
||||
// floats respectively.)
|
||||
void ogre_getWorldTransform(NodePtr node, float *trans, float *matrix);
|
||||
|
||||
// Create a (very crappy looking) plane to simulate the water level
|
||||
void ogre_createWater(float level);
|
||||
|
||||
// Creates a scene node as a child of 'parent', then translates and
|
||||
// rotates it according to the data in 'trafo'.
|
||||
NodePtr ogre_createNode(
|
||||
char *name, // Name to give the node
|
||||
Transformation *trafo, // Transformation
|
||||
NodePtr parent, // Parent node
|
||||
int noRot); // If 1, don't rotate node
|
||||
|
||||
// Create a light with the given diffuse color. Attach it to SceneNode
|
||||
// 'parent'.
|
||||
NodePtr ogre_attachLight(char* name, NodePtr parent,
|
||||
float r, float g, float b,
|
||||
float radius);
|
||||
|
||||
// Create the specified material
|
||||
void ogre_createMaterial(char *name, // Name to give resource
|
||||
float *ambient, // Ambient RBG value
|
||||
float *diffuse,
|
||||
float *specular,
|
||||
float *emissive, // Self illumination
|
||||
float glossiness,// Same as shininess?
|
||||
float alpha, // Reflection alpha?
|
||||
char *texture, // Texture name
|
||||
int alphaFlags, // Alpha settings (see
|
||||
ubyte alphaTest);// NiAlphaProperty in nif/)
|
||||
|
||||
// Creates a mesh and gives it a bounding box. Also creates an entity
|
||||
// and attached it to the given SceneNode 'owner'.
|
||||
void ogre_createMesh(
|
||||
char* name, // Name of the mesh
|
||||
int numVerts, // Number of vertices
|
||||
float* vertices, // Vertex list
|
||||
float* normals, // Normal list
|
||||
float* colors, // Vertex colors
|
||||
float* uvs, // Texture coordinates
|
||||
int numFaces, // Number of faces*3
|
||||
short* faces, // Faces
|
||||
float radius, // Bounding sphere
|
||||
char* material, // Material name, if any
|
||||
|
||||
// Bounding box
|
||||
float minX,float minY,float minZ,
|
||||
float maxX,float maxY,float maxZ,
|
||||
|
||||
NodePtr owner // Scene node to attach to.
|
||||
);
|
||||
|
||||
// Toggle fullscreen mode
|
||||
void ogre_toggleFullscreen();
|
||||
|
||||
// Save a screen shot to the given file name
|
||||
void ogre_screenshot(char *filename);
|
||||
|
||||
// Camera control and information
|
||||
void ogre_rotateCamera(float x, float y);
|
||||
void ogre_moveCamera(float x, float y, float z);
|
||||
void ogre_setCameraRotation(float r1, float r2, float r3);
|
||||
void ogre_getCameraPos(float *x, float *y, float *z);
|
||||
void ogre_getCameraOrientation(float *fx, float *fy, float *fz, float *ux, float *uy, float *uz);
|
||||
void ogre_moveCameraRel(float x, float y, float z);
|
||||
|
||||
// Insert a raw RGBA image into the texture system.
|
||||
//void ogre_insertTexture(char *name, int width, int height, void *data);
|
@ -1,39 +1,3 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (cpp_framelistener.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program 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
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Callbacks to D code.
|
||||
|
||||
// Called once each frame
|
||||
extern "C" int32_t d_frameStarted(float time);
|
||||
|
||||
// Handle events
|
||||
extern "C" void d_handleKey(int keycode, uint32_t text);
|
||||
extern "C" void d_handleMouseMove(const OIS::MouseState *state);
|
||||
extern "C" void d_handleMouseButton(const OIS::MouseState *state,
|
||||
int32_t button);
|
||||
|
||||
// Frame listener, passed to Ogre. The only thing we use this for is
|
||||
// to capture input and pass control to D code.
|
||||
class MorroFrameListener: public FrameListener
|
||||
@ -134,10 +98,6 @@ InputListener mInput;
|
||||
extern "C" void ogre_screenshot(char* filename)
|
||||
{
|
||||
mWindow->writeContentsToFile(filename);
|
||||
|
||||
//This doesn't work, I think I have to set up an overlay or
|
||||
//something first and display the text manually.
|
||||
//mWindow->setDebugText(String("Wrote ") + filename);
|
||||
}
|
||||
|
||||
// Rotate camera as result of mouse movement
|
||||
@ -177,8 +137,6 @@ extern "C" void ogre_moveCamera(float x, float y, float z)
|
||||
// is not affected by the rotation of the root node, so we must
|
||||
// transform this manually.
|
||||
mCamera->setPosition(Vector3(x,z+90,-y));
|
||||
|
||||
//g_light->setPosition(mCamera->getPosition());
|
||||
}
|
||||
|
||||
// Rotate camera using Morrowind rotation specifiers
|
||||
|
@ -1,96 +1,3 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (cpp_interface.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program 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
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
extern "C" Light* ogre_attachLight(char *name, SceneNode* base,
|
||||
float r, float g, float b,
|
||||
float radius)
|
||||
{
|
||||
Light *l = mSceneMgr->createLight(name);
|
||||
l->setDiffuseColour(r,g,b);
|
||||
|
||||
radius /= 4.0f;
|
||||
|
||||
float cval=0.0f, lval=0.0f, qval=0.0f;
|
||||
if(lightConst)
|
||||
cval = lightConstValue;
|
||||
if(!lightOutQuadInLin)
|
||||
{
|
||||
if(lightLinear)
|
||||
radius *= lightLinearRadiusMult;
|
||||
if(lightQuadratic)
|
||||
radius *= lightQuadraticRadiusMult;
|
||||
|
||||
if(lightLinear)
|
||||
lval = lightLinearValue / pow(radius, lightLinearMethod);
|
||||
if(lightQuadratic)
|
||||
qval = lightQuadraticValue / pow(radius, lightQuadraticMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME:
|
||||
// Do quadratic or linear, depending if we're in an exterior or interior
|
||||
// cell, respectively. Ignore lightLinear and lightQuadratic.
|
||||
}
|
||||
|
||||
// The first parameter is a cutoff value on which meshes to
|
||||
// light. If it's set to small, some meshes will end up 'flashing'
|
||||
// in and out of light depending on the camera distance from the
|
||||
// light.
|
||||
l->setAttenuation(10*radius, cval, lval, qval);
|
||||
|
||||
// base might be null, sometimes lights don't have meshes
|
||||
if(base) base->attachObject(l);
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
extern "C" void ogre_setAmbient(float r, float g, float b, // Ambient light
|
||||
float rs, float gs, float bs) // "Sunlight"
|
||||
{
|
||||
g_ambient = ColourValue(r, g, b);
|
||||
mSceneMgr->setAmbientLight(g_ambient);
|
||||
|
||||
// Create a "sun" that shines light downwards. It doesn't look
|
||||
// completely right, but leave it for now.
|
||||
Light *l = mSceneMgr->createLight("Sun");
|
||||
l->setDiffuseColour(rs, gs, bs);
|
||||
l->setType(Light::LT_DIRECTIONAL);
|
||||
l->setDirection(0,-1,0);
|
||||
}
|
||||
|
||||
extern "C" void ogre_setFog(float rf, float gf, float bf, // Fog color
|
||||
float flow, float fhigh) // Fog distance
|
||||
{
|
||||
ColourValue fogColor( rf, gf, bf );
|
||||
mSceneMgr->setFog( FOG_LINEAR, fogColor, 0.0, flow, fhigh );
|
||||
|
||||
// Don't render what you can't see anyway
|
||||
mCamera->setFarClipDistance(fhigh + 10);
|
||||
|
||||
// Leave this out for now
|
||||
vp->setBackgroundColour(fogColor);
|
||||
}
|
||||
|
||||
// Copy a scene node and all its children
|
||||
void cloneNode(SceneNode *from, SceneNode *to, char* name)
|
||||
{
|
||||
@ -185,246 +92,6 @@ extern "C" void ogre_createWater(float level)
|
||||
ent->setCastShadows(false);
|
||||
}
|
||||
|
||||
// Manual loader for meshes. Reloading individual meshes is too
|
||||
// difficult, and not worth the trouble. Later I should make one
|
||||
// loader for each NIF file, and whenever it is invoked it should
|
||||
// somehow reload the entire file. How this is to be done when some of
|
||||
// the meshes might be loaded and in use already, I have no
|
||||
// idea. Let's just ignore it for now.
|
||||
|
||||
class MeshLoader : public ManualResourceLoader
|
||||
{
|
||||
public:
|
||||
|
||||
void loadResource(Resource *resource)
|
||||
{
|
||||
}
|
||||
} dummyLoader;
|
||||
|
||||
// Load the contents of a mesh
|
||||
extern "C" void ogre_createMesh(
|
||||
char* name, // Name of the mesh
|
||||
int32_t numVerts, // Number of vertices
|
||||
float* vertices, // Vertex list
|
||||
float* normals, // Normal list
|
||||
float* colors, // Vertex colors
|
||||
float* uvs, // Texture coordinates
|
||||
int32_t numFaces, // Number of faces*3
|
||||
uint16_t* faces, // Faces
|
||||
float radius, // Bounding sphere
|
||||
char* material, // Material
|
||||
// Bounding box
|
||||
float minX,float minY,float minZ,
|
||||
float maxX,float maxY,float maxZ,
|
||||
SceneNode *owner
|
||||
)
|
||||
{
|
||||
//std::cerr << "Creating mesh " << name << "\n";
|
||||
|
||||
MeshPtr msh = MeshManager::getSingleton().createManual(name, "Meshes",
|
||||
&dummyLoader);
|
||||
|
||||
Entity *e = mSceneMgr->createEntity(name, name);
|
||||
|
||||
owner->attachObject(e);
|
||||
//msh->setSkeletonName(name);
|
||||
|
||||
// Create vertex data structure
|
||||
msh->sharedVertexData = new VertexData();
|
||||
msh->sharedVertexData->vertexCount = numVerts;
|
||||
|
||||
/// Create declaration (memory format) of vertex data
|
||||
VertexDeclaration* decl = msh->sharedVertexData->vertexDeclaration;
|
||||
|
||||
int nextBuf = 0;
|
||||
// 1st buffer
|
||||
decl->addElement(nextBuf, 0, VET_FLOAT3, VES_POSITION);
|
||||
|
||||
/// Allocate vertex buffer of the requested number of vertices (vertexCount)
|
||||
/// and bytes per vertex (offset)
|
||||
HardwareVertexBufferSharedPtr vbuf =
|
||||
HardwareBufferManager::getSingleton().createVertexBuffer(
|
||||
VertexElement::getTypeSize(VET_FLOAT3),
|
||||
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
||||
|
||||
/// Upload the vertex data to the card
|
||||
vbuf->writeData(0, vbuf->getSizeInBytes(), vertices, true);
|
||||
|
||||
/// Set vertex buffer binding so buffer 0 is bound to our vertex buffer
|
||||
VertexBufferBinding* bind = msh->sharedVertexData->vertexBufferBinding;
|
||||
bind->setBinding(nextBuf++, vbuf);
|
||||
|
||||
// The lists are read in the same order that they appear in NIF
|
||||
// files, and likely in memory. Sequential reads might possibly
|
||||
// avert an occational cache miss.
|
||||
|
||||
// normals
|
||||
if(normals)
|
||||
{
|
||||
//std::cerr << "+ Adding normals\n";
|
||||
decl->addElement(nextBuf, 0, VET_FLOAT3, VES_NORMAL);
|
||||
vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
|
||||
VertexElement::getTypeSize(VET_FLOAT3),
|
||||
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
||||
|
||||
vbuf->writeData(0, vbuf->getSizeInBytes(), normals, true);
|
||||
|
||||
bind->setBinding(nextBuf++, vbuf);
|
||||
}
|
||||
|
||||
// vertex colors
|
||||
if(colors)
|
||||
{
|
||||
//std::cerr << "+ Adding vertex colors\n";
|
||||
// Use render system to convert colour value since colour packing varies
|
||||
RenderSystem* rs = Root::getSingleton().getRenderSystem();
|
||||
RGBA colorsRGB[numVerts];
|
||||
RGBA *pColour = colorsRGB;
|
||||
for(int i=0; i<numVerts; i++)
|
||||
{
|
||||
rs->convertColourValue(ColourValue(colors[0],colors[1],colors[2], colors[3]),
|
||||
pColour++);
|
||||
colors += 4;
|
||||
}
|
||||
|
||||
decl->addElement(nextBuf, 0, VET_COLOUR, VES_DIFFUSE);
|
||||
/// Allocate vertex buffer of the requested number of vertices (vertexCount)
|
||||
/// and bytes per vertex (offset)
|
||||
vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
|
||||
VertexElement::getTypeSize(VET_COLOUR),
|
||||
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
||||
/// Upload the vertex data to the card
|
||||
vbuf->writeData(0, vbuf->getSizeInBytes(), colorsRGB, true);
|
||||
|
||||
/// Set vertex buffer binding so buffer 1 is bound to our colour buffer
|
||||
bind->setBinding(nextBuf++, vbuf);
|
||||
}
|
||||
|
||||
if(uvs)
|
||||
{
|
||||
//std::cerr << "+ Adding texture coordinates\n";
|
||||
decl->addElement(nextBuf, 0, VET_FLOAT2, VES_TEXTURE_COORDINATES);
|
||||
vbuf = HardwareBufferManager::getSingleton().createVertexBuffer(
|
||||
VertexElement::getTypeSize(VET_FLOAT2),
|
||||
numVerts, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
||||
|
||||
vbuf->writeData(0, vbuf->getSizeInBytes(), uvs, true);
|
||||
|
||||
bind->setBinding(nextBuf++, vbuf);
|
||||
}
|
||||
|
||||
// Create the submesh that holds triangle data
|
||||
SubMesh* sub = msh->createSubMesh(name);
|
||||
sub->useSharedVertices = true;
|
||||
|
||||
if(numFaces)
|
||||
{
|
||||
//std::cerr << "+ Adding faces\n";
|
||||
/// Allocate index buffer of the requested number of faces
|
||||
HardwareIndexBufferSharedPtr ibuf = HardwareBufferManager::getSingleton().
|
||||
createIndexBuffer(
|
||||
HardwareIndexBuffer::IT_16BIT,
|
||||
numFaces,
|
||||
HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
||||
|
||||
/// Upload the index data to the card
|
||||
ibuf->writeData(0, ibuf->getSizeInBytes(), faces, true);
|
||||
|
||||
/// Set parameters of the submesh
|
||||
sub->indexData->indexBuffer = ibuf;
|
||||
sub->indexData->indexCount = numFaces;
|
||||
sub->indexData->indexStart = 0;
|
||||
}
|
||||
|
||||
// Create a material with the given texture, if any.
|
||||
|
||||
// If this mesh has a material, attach it.
|
||||
if(material) sub->setMaterialName(name);
|
||||
|
||||
/*
|
||||
// Assign this submesh to the given bone
|
||||
VertexBoneAssignment v;
|
||||
v.boneIndex = ((Bone*)bone)->getHandle();
|
||||
v.weight = 1.0;
|
||||
|
||||
std::cerr << "+ Assigning bone index " << v.boneIndex << "\n";
|
||||
|
||||
for(int i=0; i < numVerts; i++)
|
||||
{
|
||||
v.vertexIndex = i;
|
||||
sub->addBoneAssignment(v);
|
||||
}
|
||||
*/
|
||||
/// Set bounding information (for culling)
|
||||
msh->_setBounds(AxisAlignedBox(minX,minY,minZ,maxX,maxY,maxZ));
|
||||
|
||||
//std::cerr << "+ Radius: " << radius << "\n";
|
||||
msh->_setBoundingSphereRadius(radius);
|
||||
}
|
||||
|
||||
extern "C" void ogre_createMaterial(char *name, // Name to give
|
||||
// resource
|
||||
|
||||
float *ambient, // Ambient RBG
|
||||
// value
|
||||
float *diffuse,
|
||||
float *specular,
|
||||
float *emissive, // Self
|
||||
// illumination
|
||||
|
||||
float glossiness,// Same as
|
||||
// shininess?
|
||||
|
||||
float alpha, // Use this in all
|
||||
// alpha values?
|
||||
|
||||
char* texture, // Texture
|
||||
|
||||
int32_t alphaFlags,
|
||||
uint8_t alphaTest) // Alpha settings
|
||||
{
|
||||
MaterialPtr material = MaterialManager::getSingleton().create(
|
||||
name,
|
||||
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
||||
|
||||
// This assigns the texture to this material. If the texture
|
||||
// name is a file name, and this file exists (in a resource
|
||||
// directory), it will automatically be loaded when needed. If
|
||||
// not, we should already have inserted a manual loader for the texture.
|
||||
if(texture)
|
||||
{
|
||||
Pass *pass = material->getTechnique(0)->getPass(0);
|
||||
TextureUnitState *txt = pass->createTextureUnitState(texture);
|
||||
|
||||
// Add transparencly.
|
||||
|
||||
if(alphaFlags != -1)
|
||||
{
|
||||
// The 237 alpha flags are by far the most common. Check
|
||||
// NiAlphaProperty in nif/properties.d if you need to
|
||||
// decode other values. 237 basically means normal
|
||||
// transparencly.
|
||||
if(alphaFlags == 237)
|
||||
{
|
||||
// Enable transparency
|
||||
pass->setSceneBlending(SBT_TRANSPARENT_ALPHA);
|
||||
|
||||
//pass->setDepthCheckEnabled(false);
|
||||
pass->setDepthWriteEnabled(false);
|
||||
}
|
||||
else
|
||||
std::cout << "UNHANDLED ALPHA FOR " << texture << ": " << alphaFlags << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Set bells and whistles
|
||||
material->setAmbient(ambient[0], ambient[1], ambient[2]);
|
||||
material->setDiffuse(diffuse[0], diffuse[1], diffuse[2], alpha);
|
||||
material->setSpecular(specular[0], specular[1], specular[2], alpha);
|
||||
material->setSelfIllumination(emissive[0], emissive[1], emissive[2]);
|
||||
material->setShininess(glossiness);
|
||||
}
|
||||
|
||||
extern "C" SceneNode *ogre_getDetachedNode()
|
||||
{
|
||||
SceneNode *node = mwRoot->createChildSceneNode();
|
||||
@ -466,54 +133,3 @@ extern "C" SceneNode* ogre_createNode(
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* Code currently not in use
|
||||
|
||||
// Insert a raw RGBA image into the texture system.
|
||||
extern "C" void ogre_insertTexture(char* name, uint32_t width, uint32_t height, void *data)
|
||||
{
|
||||
TexturePtr texture = TextureManager::getSingleton().createManual(
|
||||
name, // name
|
||||
"General", // group
|
||||
TEX_TYPE_2D, // type
|
||||
width, height, // width & height
|
||||
0, // number of mipmaps
|
||||
PF_BYTE_RGBA, // pixel format
|
||||
TU_DEFAULT); // usage; should be TU_DYNAMIC_WRITE_ONLY_DISCARDABLE for
|
||||
// textures updated very often (e.g. each frame)
|
||||
|
||||
// Get the pixel buffer
|
||||
HardwarePixelBufferSharedPtr pixelBuffer = texture->getBuffer();
|
||||
|
||||
// Lock the pixel buffer and get a pixel box
|
||||
pixelBuffer->lock(HardwareBuffer::HBL_NORMAL); // for best performance use HBL_DISCARD!
|
||||
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||
|
||||
void *dest = pixelBox.data;
|
||||
|
||||
// Copy the data
|
||||
memcpy(dest, data, width*height*4);
|
||||
|
||||
// Unlock the pixel buffer
|
||||
pixelBuffer->unlock();
|
||||
}
|
||||
|
||||
// We need this later for animated meshes.
|
||||
extern "C" void* ogre_setupSkeleton(char* name)
|
||||
{
|
||||
SkeletonPtr skel = SkeletonManager::getSingleton().create(
|
||||
name, "Closet", true);
|
||||
|
||||
skel->load();
|
||||
|
||||
// Create all bones at the origin and unrotated. This is necessary
|
||||
// since our submeshes each have their own model space. We must
|
||||
// move the bones after creating an entity, then copy this entity.
|
||||
return (void*)skel->createBone();
|
||||
}
|
||||
|
||||
extern "C" void *ogre_insertBone(char* name, void* rootBone, int32_t index)
|
||||
{
|
||||
return (void*) ( ((Bone*)rootBone)->createChild(index) );
|
||||
}
|
||||
*/
|
||||
|
@ -1,48 +1,5 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (cpp_ogre.cpp) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program 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
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <OgreConfigFile.h>
|
||||
#include <OgreStringConverter.h>
|
||||
#include <OgreException.h>
|
||||
#include <OgreArchive.h>
|
||||
#include <OgreArchiveFactory.h>
|
||||
|
||||
#include <MyGUI.h>
|
||||
|
||||
#include "../util/dbg.h"
|
||||
|
||||
using namespace Ogre;
|
||||
|
||||
ColourValue g_ambient;
|
||||
int g_lightOn = 0;
|
||||
|
||||
// Set to nonzero if debug mode is enabled
|
||||
int g_isDebug = 0;
|
||||
|
||||
// The global GUI object
|
||||
MyGUI::Gui *mGUI;
|
||||
|
||||
@ -51,11 +8,5 @@ MyGUI::Gui *mGUI;
|
||||
// input into MyGUI.
|
||||
int32_t guiMode = 0;
|
||||
|
||||
// Include the other parts of the code, and make one big happy object
|
||||
// file. This is extremely against the grain of C++ "recomended
|
||||
// practice", but I don't care.
|
||||
#include "../gui/cpp_mygui.cpp"
|
||||
#include "cpp_framelistener.cpp"
|
||||
#include "cpp_bsaarchive.cpp"
|
||||
#include "cpp_interface.cpp"
|
||||
#include "../terrain/cpp_terrain.cpp"
|
||||
|
@ -389,50 +389,3 @@ struct MeshLoader
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
// Create a skeleton and get the root bone (index 0)
|
||||
BonePtr bone = ogre_setupSkeleton(name);
|
||||
|
||||
// Reset the bone index. The next bone to be created has index 1.
|
||||
boneIndex = 1;
|
||||
// Create a mesh and assign the skeleton to it
|
||||
MeshPtr mesh = ogre_setupMesh(name);
|
||||
|
||||
// Loop through the nodes, creating submeshes, materials and
|
||||
// skeleton bones in the process.
|
||||
handleNode(node, bone, mesh);
|
||||
|
||||
// Create the "template" entity
|
||||
EntityPtr entity = ogre_createEntity(name);
|
||||
|
||||
// Loop through once again, this time to set the right
|
||||
// transformations on the entity's SkeletonInstance. The order of
|
||||
// children will be the same, allowing us to reference bones using
|
||||
// their boneIndex.
|
||||
int lastBone = boneIndex;
|
||||
boneIndex = 1;
|
||||
transformBones(node, entity);
|
||||
if(lastBone != boneIndex) writefln("WARNING: Bone number doesn't match");
|
||||
|
||||
if(!hasBBox)
|
||||
ogre_setMeshBoundingBox(mesh, minX, minY, minZ, maxX, maxY, maxZ);
|
||||
|
||||
return entity;
|
||||
}
|
||||
void handleNode(Node node, BonePtr root, MeshPtr mesh)
|
||||
{
|
||||
// Insert a new bone for this node
|
||||
BonePtr bone = ogre_insertBone(node.name, root, boneIndex++);
|
||||
|
||||
}
|
||||
|
||||
void transformBones(Node node, EntityPtr entity)
|
||||
{
|
||||
ogre_transformBone(entity, &node.trafo, boneIndex++);
|
||||
|
||||
NiNode n = cast(NiNode)node;
|
||||
if(n !is null)
|
||||
foreach(Node nd; n.children)
|
||||
transformBones(nd, entity);
|
||||
}
|
||||
*/
|
||||
|
@ -30,19 +30,6 @@ NodePtr placeObject(MeshIndex mesh, Placement *pos, float scale,
|
||||
return node;
|
||||
}
|
||||
|
||||
void setAmbient(Color amb, Color sun, Color fog, float density)
|
||||
{
|
||||
ogre_setAmbient(amb.red/255.0, amb.green/255.0, amb.blue/255.0,
|
||||
sun.red/255.0, sun.green/255.0, sun.blue/255.0);
|
||||
|
||||
// Calculate fog distance
|
||||
// TODO: Mesh with absolute view distance later
|
||||
float fhigh = 4500 + 9000*(1-density);
|
||||
float flow = 200 + 2000*(1-density);
|
||||
|
||||
ogre_setFog(fog.red/255.0, fog.green/255.0, fog.blue/255.0, 200, fhigh);
|
||||
}
|
||||
|
||||
// Gives the placement of an item in the scene (position and
|
||||
// orientation). It must have this exact structure since we also use
|
||||
// it when reading ES files.
|
||||
|
Loading…
Reference in New Issue
Block a user