Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Topics - Solitudinal

Pages: 1
Function Peer Review / d3d_unproject
« on: November 07, 2017, 03:27:49 pm »
This handy function set allows you to convert a 2d screen coordinate (such as the mouse position) into 3d coordinates - basically the 3d coordinates that it's pointing at.

This is sometimes called unproject, because it reverses the projection process (which converts 3d coordinates into 2d screen coordinates). It's also sometimes called raycasting, since you are effectively casting a geometric ray from the camera/viewport into 3d space, and then in this case colliding it with the z-depth of whatever pixel it is (e.g. z=0 if you click on the floor).

4 functions are provided:
d3d_unproject returns a Vector3, if you are ok with OO programming.
d3d_unproject_x/y/z returns the respective x, y, and z coordinates, in GM's regular functional programming (where functions return a single number, rather than an object).
All functions take an x and a y coordinate, which refer to the location respective to the viewport. I.e. d3d_unroject(mouse_x,mouse_y)
Or if you're weird and actually use views in conjunction with 3d:
d3d_unproject(mouse_x - view_xview[view_current], mouse_y - view_yview[view_current])

These functions are a little expensive due to complex math going on behind the scenes, so try not to use them more than a few times every step. I.e. if you're using them for mouse coordinates, consider storing the results in a global variable.

NOTE this only works in OpenGL1, as it depends on a call to gluUnproject. I tried porting the code to OpenGL3, but I'm terrible with matrices and ENIGMA uses an awkward matrix format. I've included my attempted OpenGL3 code below if you want to try and fix it.

Code: [Select]
enigma::Vector3 d3d_unproject(gs_scalar x, gs_scalar y)
  GLdouble model[16];
  GLdouble proj[16];
  GLint view[4];
  GLdouble retX, retY, retZ;
  GLfloat gly, glz;


  //invert mouse y into openGL y
  gly = (float)view[3] - (float)y;
  //Read the depth of the moused pixel as the z
  //this stops the raycast at the first pixel collision

  //TODO: Provide a raycast_to_floor, stopping at a desired z rather than the first pixel collision
  //I believe the way to do this is to call gluUnProject twice at glz=0 and glz=1 to raycast,
  //then somehow collide that ray with the desired z.

  return enigma::Vector3(retX, retY, retZ);

gs_scalar d3d_unproject_x(gs_scalar x, gs_scalar y)
  return d3d_unproject(x,y).x;
gs_scalar d3d_unproject_y(gs_scalar x, gs_scalar y)
  return d3d_unproject(x,y).y;
gs_scalar d3d_unproject_z(gs_scalar x, gs_scalar y)
  return d3d_unproject(x,y).z;

OpenGL3 BROKEN code, which I created by researching a few places explaining how gluUnproject works:
Code: [Select]
//This code does not work. Please fix it.
enigma::Vector3 d3d_unproject(gs_scalar x, gs_scalar y)
  GLint view[4];
  GLfloat gly, glz;

  //invert mouse y into openGL y
  gly = (float)view[3] - (float)y;
  //Read the depth of the moused pixel as the z
  //this stops the raycast at the first pixel collision
  glReadPixels(x,y,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&glz); //Not sure but I think this is broken

  //Now perform the unproject.
  //create an unprojection matrix by inverting the MVP. This is probably the part I screwed up.
  enigma::Matrix4 inv = enigma::view_matrix * enigma::projection_matrix;
  //Create the normalized ray in GL-space [-1,1]
  enigma::Vector4 tmp = enigma::Vector4((float)x / (float)view[2],gly / (float)view[3],glz,-1.0f);
  tmp.x = tmp.x * 2.0f - 1.0f;
  tmp.y = tmp.y * 2.0f - 1.0f;
  tmp.z = tmp.z * 2.0f - 1.0f;
  //Cast it
  enigma::Vector4 obj = inv * tmp;

  //Un-normalize the result (the magnitude is 1/obj.w)
  return enigma::Vector3(obj.x / obj.w,obj.y / obj.w,glz / obj.w);

Graphics and Video / [WIP] Collada (.dae) 3d Animated Model Loader
« on: September 01, 2017, 03:48:24 pm »
Before I start, just let me say that I didn't see any existing code in enigma for loading Collada models, so I started writing my own. I also didn't see any in-built xml readers (aside from the skeletal remains of RapidXML), so I used pugixml. If I missed something, please be nice :-)
I'm aware that ENIGMA is already capable of reading .obj models, which is awesome, but Collada has animation and such.

Collada is an industry standard format (ISO/PAS 17506) for animated 3d models, utilizing skeletal joints for animation (it does some other things too, but this is what I'm using it for here). It stores data in XML.

This, then, is my ENIGMA/C++ reader for said format. It is a work in progress, so be patient and I'll continue to share my progress on it, and feel free to share feedback along the way (this is why I've shared it here). I'm developing this for a game idea that I'm poking at in ENIGMA.

I'm aware that other Collada readers exist, but they tend to be extremely bulky, easily reaching up into multiple MBs, so I figured something much smaller could be achieved and could easily be added to ENIGMA.

I'll provide steps for adding this to ENIGMA, but please ADD AT YOUR OWN RISK. Everything should be easily reversible by just undoing these changes (or if you're familiar with git, there's a git path).

First, pick up a copy of pugixml (not pugxml). Copy the .cpp and .hpp files into ENIGMAsystem/SHELL/Universal_System/ and rename the .hpp to .h
Edit both and add the following lines near the top:
Code: [Select]

Next, edit ENIGMAsystem/SHELL/Graphics_Systems/General/GSmodel.h and add the following function def:
Code: [Select]
void d3d_collada_load(int id, std::string fname);
Finally, edit ENIGMAsystem/SHELL/Graphics_Systems/OpenGL1/GSmodel.cpp (only tested for OpenGL1, but shouldn't require much if any modification for other systems) and on line 41 add the following includes:
Code: [Select]
#include <sstream> //used to split strings
#include <algorithm> //needed for find(), used kinda like indexof
#include "Universal_System/pugixml.h"
And on line 107 or thereabouts (I chose between d3d_model_save and d3d_model_load) add the following code:
Code: [Select]
//temporarily stores the vertex data with texture/normal ids to construct unique VNTs
class Vertex {
  unsigned int index;
  float x, y, z;
  int texId = -1;
  int normId = -1;
  Vertex *next = NULL;

  Vertex(int id, float x, float y, float z) :
      index(id), x(x), y(y), z(z) {}

// I don't like enigma's split functions, so I made my own
template <class T>
std::vector<T> cppsplit(const pugi::char_t* slist) {
  std::stringstream ss(slist);
  std::vector<T> tlist;
  T token;
  while (ss >> token)
  return tlist;

//This workhorse reads a collada xml file (.dae) into an enigma model "mesh"
void d3d_collada_load(int id, string fname)
    pugi::xml_document doc;
    pugi::xml_node rootNode = doc.child("COLLADA");

    pugi::xml_node meshData = rootNode.child("library_geometries").child("geometry").child("mesh");
    const pugi::char_t* posId = meshData.child("vertices").child("input").attribute("source").value();
    pugi::xml_node posData = meshData.find_child_by_attribute("source","id",posId + 1).child("float_array");
    int posCount = posData.attribute("count").as_int();
    const pugi::char_t* sposList = posData.text().get();
    std::stringstream sspos(sposList);
    std::vector<Vertex*> vertices;
    for(int i = 0; i < posCount / 3; i++) {
        float x,y,z;
        sspos >> x >> y >> z;
        //correct Collada's z-up to y-up with (x,z,-y)
        vertices.push_back(new Vertex(i, x,y,z));
    pugi::xml_node poly = meshData.child("polylist");
    const pugi::char_t* normId = poly.find_child_by_attribute("input","semantic","NORMAL").attribute("source").value();
    pugi::xml_node normData = meshData.find_child_by_attribute("source","id",normId + 1).child("float_array");
    int normCount = normData.attribute("count").as_int();
    const pugi::char_t* snormList = normData.text().get();
    std::stringstream ssnorm(snormList);
    std::vector<std::array<float,3>> normList;
    for(int i = 0; i < normCount / 3; i++) {
        float x,y,z;
        ssnorm >> x >> y >> z;
        std::array<float,3> row = {x,y,z}; //correct Collada's z-up to y-up
    const pugi::char_t* texcId = poly.find_child_by_attribute("input","semantic","TEXCOORD").attribute("source").value();
    pugi::xml_node texcData = meshData.find_child_by_attribute("source","id",texcId + 1).child("float_array");
    int texcCount = texcData.attribute("count").as_int();
    const pugi::char_t* stexcList = texcData.text().get();
    std::stringstream sstexc(stexcList);
    std::vector<std::array<float,2>> texcList;
    for(int i = 0; i < texcCount / 2; i++) {
        float s,t;
        sstexc >> s >> t;
        std::array<float,2> row = {s, 1.0f - t}; //in OpenGL, texture's y coordinate "t" is inverted
    const pugi::char_t* indexData = poly.child("p").text().get();
    std::stringstream ssp(indexData);
    std::vector<unsigned int> indices;
    int idVert, idNorm, idTexc, idCol; //I'm assuming it's just these 4
    while (ssp >> idVert >> idNorm >> idTexc >> idCol) {
        Vertex *v = vertices[idVert];
        if (v->texId == -1 || v->normId == -1) {
            v->texId = idTexc;
            v->normId = idNorm;
        } else if (v->texId != idTexc || v->normId != idNorm) {
            //modified dealWithAlreadyProcessedVertex
            Vertex *newv = v;
            while (newv != NULL) {
                if (newv->texId == idTexc && newv->normId == idNorm)
                v = newv;
                newv = newv->next;
            if (newv == NULL) {
                Vertex *nv = new Vertex(vertices.size(), v->x,v->y,v->z);
                nv->texId = idTexc;
                nv->normId = idNorm;
                vertices.push_back(nv); //this is why we can't just vector<tex,norm>
                newv = nv;
                v->next = newv;
                v = newv;

    // Close the xml file
//    delete doc;

    //"removeUnusedVertices", or in this case we just link them to first texture and normal
    for (Vertex *vertex : vertices) {
        //tangents aren't used, otherwise we'd average them
        if (vertex->normId == -1) vertex->normId = 0;
        if (vertex->texId == -1) vertex->texId = 0;

    // Convert to ENIGMA model/mesh.
    for (const Vertex *ov : vertices) {
        meshes[id]->AddVertex(ov->x, ov->y, ov->z);
        delete ov;
    for (unsigned int i = 0; i < indices.size(); i++) {

So the code will only read the base model right now. Animation will come eventually. It will then push the base model VNT (Vertex, Normal, Texture) and indices into an ENIGMA mesh. Code ported from this java example and
Also, if you need a model/texture to test with, it has one.

Here's an example of it used in a game, if you can get it to compile:
Create a background called tex_dude and load in the texture (diffuse.png in the java example)
Create event:
Code: [Select]
file = "C:/path/to/model.dae" //please point this to a valid collada .dae file

//here we load in the model and store it in the "mdl" variable
mdl = d3d_model_create(0); //0 means its static, for now

Draw event:
Code: [Select]
d3d_set_projection(0,-20,0, 0,0,0, 0,0,1); //feel free to tweak these numbers as needed to point the camera at your model

Sept 2 @ 1500est: included a few missing functions
Sept 2 @ 1600est: and a few missing structs
Sept 4 @ 2200est: replaced with instructions for inserting into ENIGMA and an example
Sept 5 @ 1900est: textures now apply correctly
Sept 16 @ 2300est: removed unused skeletal data and recursive function. Should now load a little faster and static models no longer have a dependency on having a skeleton.
Sept 29 @ 1800est: fixed memory leak
Oct 7 @ 1500est: removed unused joint weight data. Should now load a little faster and static models no longer have a dependency on having joint weights.

Windows 10

Code: [Select]
java.lang.Error: Invalid memory access
        at com.sun.jna.Native.invokePointer(Native Method)
        at com.sun.jna.Function.invokePointer(
        at com.sun.jna.Function.invoke(
        at com.sun.jna.Function.invoke(
        at com.sun.jna.Library$Handler.invoke(
        at com.sun.proxy.$Proxy0.definitionsModified(Unknown Source)
        at org.enigma.backend.EnigmaSettings.commitToDriver(
        at org.enigma.EnigmaRunner$

So enigma was working fine yesterday, but today I added a new hard drive and moved my Desktop folder (which contained enigma) and some other folders (Documents, etc - but not Program Files or ProgramData) to the new E: drive. It started throwing this error, so I reinstalled enigma to C:\gamedev\ENIGMA and tweaked the usual things:

Code: [Select]
idecommand="C:\Program Files (x86)\Java\jre1.8.0_74\bin\java.exe -jar -Xms256m -Xmx1000m"
(I do java dev, so I needed to explicitly point it to my 32-bit java. I also told the console to stay around so I can see what's going on)

Code: [Select]
        Default-Windows: "C:/ProgramData/ENIGMA/"
(That directory does exist). As per the usual solution to Invalid Memory Address problems. Doesn't seem to make a difference if I change this or not, though.

Unfortunately, after the long build process, it's still throwing the exact same error as above.
I also did a "git pull", in case something was outdated, but after it rebuilt the necessary pieces, it still threw the exact same error.

This is quite bizarre since it just worked yesterday.

What exactly happens is I open ENIGMA.exe, the console appears and LateralGM appears, and the console text scrolls for a little bit, and then stops with the aforementioned error - and LateralGM shows the error in a popup.
In other words, it's during startup.
I can close the error and continue to use LateralGM without ENIGMA, but I kinda need ENIGMA, lol.

Update: Forgot to Run as administrator. It used to automatically run as admin, but the move must have changed that. Ran as admin again and it all works again.

Pages: 1