Jump to content

Josh

Staff
  • Posts

    23,266
  • Joined

  • Last visited

Everything posted by Josh

  1. A quick overview of imposters, a nifty little built-in feature coming soon. This is mostly meant for use with the foliage system, but it works with regular models as well.
  2. 0.9.6 Full update with all recent fixes.
  3. I notice that FirstPersonControls::Start() is being called five times. If I put this code in the update function, I can see there are two copies of the object being updated each frame: void FirstPersonControls::Update() { Print(uint64_t(this)); 1681950787280 1681952725632 1681950787280 1681952725632 1681950787280 1681952725632 1681950787280 Let me figure out why...
  4. In my tests the problem is resolved. New build will go up later today with the fix.
  5. Can confirm, this is also happens within the editor. Also, a model loaded in a prefab file cannot be picked in the viewports.
  6. Josh

    Mobile Site

    Yes, the custom skin removes a lot of things. This is an oversight that should be fixed in time.
  7. Marking this as solved since I think it is...
  8. Okay, the problem is that Entity::Copy() was not copying the tags. I believe this will be fixed in the next build that goes up.
  9. Okay, the problem is that Entity::Copy() was not copying the tags. I believe this will be fixed in the next build that goes up.
  10. Tags are being saved in the map file, and if I load the prefab like a map (File > Open) the tags load, but when objects are placed in the scene the tags are missing. { "scene": { "entities": [ { "castshadows": true, "collisiontype": 1, "extras": {}, "friction": [ 0.5, 0.8999999761581421 ], "group": "1663675869920", "kids": [ { "castshadows": true, "collisiontype": 1, "extras": {}, "friction": [ 0.5, 0.8999999761581421 ], "matrix": [ "0x42c80000", "0x0", "0x0", "0x0", "0x0", "0xb747ffff", "0x42c7fffe", "0x0", "0x0", "0xc2c7fffe", "0xb747ffff", "0x0", "0x0", "0x0", "0x0", "0x3f800000" ], "model": { "islimb": true }, "name": "Cube", "physicsmode": 0, "pickmode": 1, "quaternion": [ "0x3f3504f4", "0x0", "0x0", "0xbf3504f2" ], "reflection": true, "rotation": [ "0x42b3f5df", "0x80000000", "0x0" ], "scale": [ "0x42c80000", "0x42c7ffff", "0x42c7ffff" ], "tags": [ "test2" ], "uuid": "d0a578c6-4ac8-4de5-83e0-85db59a8a57b" } ], "model": { "islimb": false, "path": "Models/test.mdl" }, "name": "STATIC_MESH", "physicsmode": 0, "pickmode": 1, "reflection": true, "tags": [ "test1" ], "uuid": "bc1b377d-116b-45d8-b6dd-5eb8f8330079" } ] } }
  11. It's working fine for me within the editor, with this test: tagtest.zip Note that if you just click on the object, both the parent and child will be selected, and the tags property field will appear empty because the two selected objects have different values.
  12. Make sure you compare your shader code to my current shader. I had a similar problem with outline colors in the editor, due to some errors in the logic for the loops.
  13. Was this only occurring when the game is launched from the editor? We had a problem where the process was being blocked because its output pipe was not being read by the editor after launching, which would cause the game to freeze on a print command. This was recently fixed. Does this happen when running the game from Visual Studio, or only when launching from in the editor?
  14. Josh

    Quake BSP Loading

    Here is an example that uses another plugin to load PNG images in a glTF file: https://www.ultraengine.com/learn/LoadPlugin?lang=cpp
  15. Josh

    Quake BSP Loading

    Just call LoadPlugin("Plugins/QuakeLoader.dll"), make sure you keep it in memory, and call LoadModel() to load a quake bsp file as a model.
  16. This helps show the problem: model->Turn(0, 0.2, 0);
  17. Josh

    Jungle Scene

    I decided to upload my jungle scene as-is so you can play around with it. We are all learning. island.zip
  18. Josh

    Quake BSP Loading

    Some of the older builds of Ultra included a Quake file format plugin, which should work. You can revert to an older version, get the DLL from the plugins folder, and copy it into the latest version. You can also use the source code here to read Quake BSP files and construct a model: https://github.com/UltraEngine/PluginSDK/tree/master/Plugins/Quake Loader
  19. 0.9.6 Updated the thumbnail utility to support the newer version 301 model format. It's probably a little faster now too.
  20. A small change was needed for LOD distances. The current version is now 301. A Vec3 was added in the node properties. The old per-LOD value will be ignored. If you don't know what the LOD distances should be, just fill it with NAN values. See G3DModelLoader::LoadNode() below. The original version 300 models will continue to load with no changes. #include "UltraEngine.h" using namespace UltraEngine; namespace UltraEngine::Core { G3DModelLoader::G3DModelLoader() { extensions = { L"mdl" }; } String G3DModelLoader::ReadText(shared_ptr<Stream> stream) { int len = stream->ReadInt(); auto pos = stream->GetPosition(); String s; if (len) { s = stream->ReadString(len); stream->Seek(pos + len); } return s; } bool G3DModelLoader::Reload(shared_ptr<Stream> stream, shared_ptr<Object> o, const LoadFlags flags) { auto modelbase = o->As<ModelBase>(); if (modelbase == NULL) return false; modelbase->model = CreateModel(NULL); auto model = modelbase->model->As<Model>(); auto start = stream->GetPosition(); if (stream->ReadString(12) != "Ultra Model") return false; Assert(stream->GetPosition() == start + 12); this->version = stream->ReadInt(); if (version != 300 and version != 301) { Print("Error: MDL version " + String(version) + " not supported"); return false; } return LoadNode(stream, model, flags); } bool G3DModelLoader::LoadNode(shared_ptr<Stream> stream, shared_ptr<Model> model, const LoadFlags flags) { Vec3 pos, scale, lodrange; Vec4 color; Quat rot; String s; if (stream->ReadString(4) != "NODE") { Print("Error: Expected NODE tag"); return false; } model->name = ReadText(stream); model->properties = ParseJson(ReadText(stream)); ParseJson(ReadText(stream)); pos.x = stream->ReadFloat(); pos.y = stream->ReadFloat(); pos.z = stream->ReadFloat(); rot.x = stream->ReadFloat(); rot.y = stream->ReadFloat(); rot.z = stream->ReadFloat(); rot.w = stream->ReadFloat(); scale.x = stream->ReadFloat(); scale.y = stream->ReadFloat(); scale.z = stream->ReadFloat(); color.x = stream->ReadFloat(); color.y = stream->ReadFloat(); color.z = stream->ReadFloat(); color.a = stream->ReadFloat(); if (version > 300) { lodrange.x = stream->ReadFloat(); lodrange.y = stream->ReadFloat(); lodrange.z = stream->ReadFloat(); } model->SetPosition(pos); model->SetRotation(rot); model->SetScale(scale); model->SetColor(color); if (version > 300) { auto range = model->GetLodDistance(); if (not isnan(lodrange.x)) range.x = lodrange.x; if (not isnan(lodrange.y)) range.y = lodrange.y; if (not isnan(lodrange.z)) range.z = lodrange.z; model->SetLodDistance(range.x, range.y, range.z); } int countlods = stream->ReadInt(); for (int level = 0; level < countlods; ++level) { if (not LoadLod(stream, model, level, flags)) return false; } // Skeleton if (stream->ReadString(4) != "SKEL") { Print("Error: Expected SKEL tag"); return false; } int bones = stream->ReadInt(); if (bones) { if (model->GetParent()) { Print("Error: Skeleton can only appear in the model root node"); return false; } if (bones < 0) { Print("Error: Skeleton bones must be more than zero"); return false; } auto skeleton = CreateSkeleton(nullptr); skeleton->root = std::make_shared<Bone>(nullptr, skeleton); skeleton->bones.resize(bones); if (not LoadBone(stream, skeleton, skeleton->root, 0, flags)) return false; skeleton->root->UpdateMatrix(); skeleton->bones[0] = skeleton->root; skeleton->UpdateSkinning(); for (int n = 0; n < skeleton->bones.size(); ++n) { if (skeleton->bones[n] == NULL) continue; auto bone = skeleton->bones[n]; bone->inversebindmatrix = bone->matrix.Inverse(); bone->animbone->inversebindmatrix = bone->inversebindmatrix; bone->Finalize(); } model->SetSkeleton(skeleton); } //Attachment if (stream->ReadString(4) != "ATCH") { Print("Error: Expected ATCH tag"); return false; } int attachmentboneid = stream->ReadInt(); if (attachmentboneid != -1) { stream->Seek(stream->GetPosition() + 64); } // Animations if (stream->ReadString(4) != "ASET") { Print("Error: Expected ASET tag"); return false; } int animations = stream->ReadInt(); if (animations) { if (model->GetParent()) { Print("Error: Animations can only appear in the model root node"); return false; } for (int anim = 0; anim < animations; ++anim) { if (stream->ReadString(4) != "ANIM") { Print("Error: Expected ANIM tag"); return false; } auto seq = std::make_shared<Sequence>(); model->skeleton->root->animations.push_back(seq); model->skeleton->root->animbone->animations.push_back(seq); WString animname = ReadText(stream); float speed = stream->ReadFloat(); int keyframes = stream->ReadInt(); float duration = float(keyframes) / 60.0f * speed; int bones = stream->ReadInt(); for (int b = 0; b < bones; ++b) { if (stream->ReadString(4) != "BONE") { Print("Error: Expected BONE tag"); return false; } int keyflags = stream->ReadInt(); auto bone = model->skeleton->bones[b]; bone->animations.resize(animations); bone->animations[anim] = std::make_shared<Sequence>(); bone->animations[anim]->name = animname; bone->animations[anim]->speed = speed; bone->animations[anim]->keyframes.reserve(keyframes); bone->animations[anim]->duration = duration; KeyFrame key; for (int k = 0; k < keyframes; ++k) { if ((1 & keyflags) != 0) { key.position.x = stream->ReadFloat(); key.position.y = stream->ReadFloat(); key.position.z = stream->ReadFloat(); } if ((2 & keyflags) != 0) { key.rotation.x = stream->ReadFloat(); key.rotation.y = stream->ReadFloat(); key.rotation.z = stream->ReadFloat(); key.rotation.w = stream->ReadFloat(); } if ((4 & keyflags) != 0) { key.scale = stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat(); } bone->animations[anim]->keyframes.push_back(key); } bone->animbone->animations = bone->animations; } } } // Collider if (stream->ReadString(4) != "PHYS") { auto pos = stream->GetPosition() - 4; Print("Error: Expected PHYS tag at position " + String(pos)); return false; } int colliderdatasize = stream->ReadInt(); auto colliderstartposition = stream->GetPosition(); if (colliderdatasize) { Vec3 position, scale, euler; Quat rotation; std::vector<shared_ptr<Collider> > parts; int partcount = stream->ReadInt(); for (int n = 0; n < partcount; ++n) { if (stream->ReadString(4) != "PART") { auto pos = stream->GetPosition() - 4; Print("Error: Expected PART tag at position " + String(pos)); return false; } shared_ptr<Collider> part; auto tag = stream->ReadString(4); if (tag == "HULL") { float tol = stream->ReadFloat();// tolerance std::vector<Vec3> points; Vec3 p; int count = stream->ReadInt(); for (int n = 0; n < count; ++n) { p.x = stream->ReadFloat(); p.y = stream->ReadFloat(); p.z = stream->ReadFloat(); points.push_back(p); } part = CreateConvexHullCollider(points, 0.0f); if (part) part->tolerance = tol; } else if (tag == "MESH") { int opt = stream->ReadInt();// optimize flag int count = stream->ReadInt(); int vcount; std::vector<Vec3> face; Vec3 p; std::vector<std::vector<Vec3> > meshfaces; for (int n = 0; n < count; ++n) { vcount = stream->ReadInt(); face.clear(); for (int v = 0; v < vcount; ++v) { p.x = stream->ReadFloat(); p.y = stream->ReadFloat(); p.z = stream->ReadFloat(); face.push_back(p); } meshfaces.push_back(face); } part = CreateMeshCollider(meshfaces, false); if (part) part->optimizemesh = opt; } else { position.x = stream->ReadFloat(); position.y = stream->ReadFloat(); position.z = stream->ReadFloat(); rotation.x = stream->ReadFloat(); rotation.y = stream->ReadFloat(); rotation.z = stream->ReadFloat(); rotation.w = stream->ReadFloat(); scale.x = stream->ReadFloat(); scale.y = stream->ReadFloat(); scale.z = stream->ReadFloat(); if (tag == "BOX_") { part = CreateBoxCollider(scale, position, rotation.Euler()); } else if (tag == "CYLI") { part = CreateCylinderCollider(scale.x, scale.y, position, rotation.Euler()); } else if (tag == "CONE") { part = CreateConeCollider(scale.x, scale.y, position, rotation.Euler()); } else if (tag == "CCYL") { part = CreateChamferCylinderCollider(scale.x, scale.y, position, rotation.Euler()); } else if (tag == "CAPS") { part = CreateCapsuleCollider(scale.x, scale.y, position, rotation.Euler()); } else if (tag == "SPHE") { part = CreateSphereCollider(scale.x, position); } else { Print("Error: Unknown collider type \"" + tag + "\""); return false; } } if (part) parts.push_back(part); } if (parts.size()) { if (parts.size() == 1) { model->SetCollider(parts[0]); } else { auto c = CreateCompoundCollider(parts); model->SetCollider(c); } } stream->Seek(colliderstartposition + colliderdatasize); } // Load children if (stream->ReadString(4) != "KIDS") { Print("Error: Expected KIDS tag"); return false; } int countkids = stream->ReadInt(); for (int n = 0; n < countkids; ++n) { auto child = CreateModel(NULL); child->SetParent(model); if (not LoadNode(stream, child, flags)) return false; } model->UpdateBounds(); return true; } bool G3DModelLoader::LoadLod(shared_ptr<Stream> stream, shared_ptr<Model> model, const int level, const LoadFlags flags) { if (stream->ReadString(4) != "LOD_") { Print("Error: Expected LOD_ tag"); return false; } if (level >= model->lods.size()) model->AddLod(); float loddistance = stream->ReadFloat(); if (version < 301) { if (loddistance > 0.0f and model->lods.size() > 0 and model->lods.size() <= 4) { auto lods = model->GetLodDistance(); int i = int(model->lods.size()) - 2; lods[i] = loddistance; model->SetLodDistance(lods.x, lods.y, lods.z); } } int countmeshes = stream->ReadInt(); for (int m = 0; m < countmeshes; ++m) { if (not LoadMesh(stream, model, level, flags)) return false; } return true; } bool G3DModelLoader::LoadMesh(shared_ptr<Stream> stream, shared_ptr<Model> model, const int level, const LoadFlags flags) { if (stream->ReadString(4) != "MESH") { Print("Error: Expected MESH tag"); return false; } MeshPrimitives type = MeshPrimitives(stream->ReadInt()); if (type < 1 or type > 4) { Print("Error: Mesh type must be between one and four"); return false; } auto mesh = model->AddMesh(type, level); mesh->name = ReadText(stream); WString mtlpath = ReadText(stream); if (not mtlpath.empty()) { if (mtlpath.Left(2) == "./" and not stream->path.empty()) { mtlpath = ExtractDir(stream->path) + "/" + mtlpath; } auto mtl = LoadMaterial(mtlpath, flags); if (mtl) mesh->SetMaterial(mtl); } int vertexstride = stream->ReadInt(); if (vertexstride != 88) { Print("Vertext stride must be 84"); return false; } int vertexcount = stream->ReadInt(); mesh->m_vertices.resize(vertexcount); for (int v = 0; v < vertexcount; ++v) { mesh->m_vertices[v].position.x = stream->ReadFloat(); mesh->m_vertices[v].position.y = stream->ReadFloat(); mesh->m_vertices[v].position.z = stream->ReadFloat(); mesh->m_vertices[v].normal.x = stream->ReadFloat(); mesh->m_vertices[v].normal.y = stream->ReadFloat(); mesh->m_vertices[v].normal.z = stream->ReadFloat(); mesh->m_vertices[v].texcoords.x = stream->ReadFloat(); mesh->m_vertices[v].texcoords.y = stream->ReadFloat(); mesh->m_vertices[v].texcoords.z = stream->ReadFloat(); mesh->m_vertices[v].texcoords.w = stream->ReadFloat(); mesh->m_vertices[v].color.r = float(stream->ReadByte()) / 255.0f; mesh->m_vertices[v].color.g = float(stream->ReadByte()) / 255.0f; mesh->m_vertices[v].color.b = float(stream->ReadByte()) / 255.0f; mesh->m_vertices[v].color.a = float(stream->ReadByte()) / 255.0f; mesh->m_vertices[v].displacement = stream->ReadFloat(); mesh->m_vertices[v].tangent.x = stream->ReadFloat(); mesh->m_vertices[v].tangent.y = stream->ReadFloat(); mesh->m_vertices[v].tangent.z = stream->ReadFloat(); mesh->m_vertices[v].bitangent.x = stream->ReadFloat(); mesh->m_vertices[v].bitangent.y = stream->ReadFloat(); mesh->m_vertices[v].bitangent.z = stream->ReadFloat(); mesh->m_vertices[v].boneindices[0] = stream->ReadShort(); mesh->m_vertices[v].boneindices[1] = stream->ReadShort(); mesh->m_vertices[v].boneindices[2] = stream->ReadShort(); mesh->m_vertices[v].boneindices[3] = stream->ReadShort(); mesh->m_vertices[v].boneweights.x = float(stream->ReadByte()) / 255.0f; mesh->m_vertices[v].boneweights.y = float(stream->ReadByte()) / 255.0f; mesh->m_vertices[v].boneweights.z = float(stream->ReadByte()) / 255.0f; mesh->m_vertices[v].boneweights.w = float(stream->ReadByte()) / 255.0f; //SubD normals stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat(); } Vec4 zero; for (const auto& vertex : mesh->vertices) { if (vertex.boneweights != zero) { mesh->SetSkinned(true); break; } } int indicesize = stream->ReadInt(); int indicecount = stream->ReadInt(); uint32_t index; switch (indicesize) { case 2: mesh->m_indices.reserve(indicecount); for (int i = 0; i < indicecount; ++i) mesh->AddIndice(stream->ReadShort()); break; case 4: mesh->m_indices.resize(indicecount); stream->Read(mesh->m_indices.data(), indicecount * sizeof(mesh->indices[0])); break; default: return false; } //Primitives flags if (stream->ReadString(4) != "PRIM") { Print("Error: Expected PRIM tag"); return false; } int primcount = stream->ReadInt(); if (primcount) { if (primcount != indicecount / type) { Print("Error: Primitives count must be equal to the number of primitives in the mesh, or zero"); return false; } stream->Seek(stream->GetPosition() + primcount); } //Vertex morphs if (stream->ReadString(4) != "MSET") { Print("Error: Expected MORP tag"); return false; } int morphcount = stream->ReadInt(); for (int m = 0; m < morphcount; ++m) { if (stream->ReadString(4) != "MORP") { Print("Error: Expected MORP tag"); return false; } if (stream->ReadInt() != 48) return false; for (int v = 0; v < vertexcount; ++v) { // Position stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat(); // Normal stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat(); // Tangent stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat(); // Bitangent stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat(); } } // Pick structure cache if (stream->ReadString(4) != "PICK") { Print("Error: Expected PICK tag"); return false; } int pickcachesize = stream->ReadInt(); if (pickcachesize) stream->Seek(stream->GetPosition() + pickcachesize); mesh->UpdateBounds(); return true; } bool G3DModelLoader::LoadBone(shared_ptr<Stream> stream, shared_ptr<Skeleton> skeleton, shared_ptr<Bone> bone, const int animcount, const LoadFlags flags) { if (stream->ReadString(4) != "BONE") { Print("Error: Expected BONE tag"); return false; } bone->m_id = stream->ReadInt(); bone->animbone->id = bone->id; //Print(bone->m_id); if (bone->id >= skeleton->bones.size()) skeleton->bones.resize(bone->id + 1); skeleton->bones[bone->id] = bone; if (bone->id >= skeleton->animskeleton->bones.size()) skeleton->animskeleton->bones.resize(bone->id + 1); skeleton->animskeleton->bones[bone->id] = bone->animbone; bone->name = ReadText(stream); bone->position.x = stream->ReadFloat(); bone->position.y = stream->ReadFloat(); bone->position.z = stream->ReadFloat(); bone->quaternion.x = stream->ReadFloat(); bone->quaternion.y = stream->ReadFloat(); bone->quaternion.z = stream->ReadFloat(); bone->quaternion.w = stream->ReadFloat(); bone->scale = stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat();// scale y and z not supported /*int count = stream->ReadInt(); if (bone->GetParent() and count != animcount) { Print("Error: Bone animation count must match that of the root node"); return false; } int i = stream->GetPosition(); for (int anim = 0; anim < count; ++anim) { auto seq = std::make_shared<Sequence>(); bone->animations.push_back(seq); bone->animbone->animations.push_back(seq); if (stream->ReadString(4) != "ANIM") { Print("Error: Expected ANIM tag"); return false; } bone->animations[anim]->name = ReadText(stream); bone->animations[anim]->speed = stream->ReadFloat(); int keyflags = stream->ReadInt(); int keyframes = stream->ReadInt(); if (not keyflags or not keyframes) continue; bone->animations[anim]->duration = float(keyframes) / 60.0f * bone->animations[anim]->speed; KeyFrame key; bone->animations[anim]->keyframes.reserve(keyframes); for (int k = 0; k < keyframes; ++k) { if ((1 & keyflags) != 0) { key.position.x = stream->ReadFloat(); key.position.y = stream->ReadFloat(); key.position.z = stream->ReadFloat(); } if ((2 & keyflags) != 0) { key.rotation.x = stream->ReadFloat(); key.rotation.y = stream->ReadFloat(); key.rotation.z = stream->ReadFloat(); key.rotation.w = stream->ReadFloat(); } if ((4 & keyflags) != 0) { key.scale = stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat(); } bone->animations[anim]->keyframes.push_back(key); } } bone->animbone->animations = bone->animations; */ if (stream->ReadString(4) != "KIDS") { Print("Error: Expected KIDS tag"); return false; } int childcount = stream->ReadInt(); for (int n = 0; n < childcount; ++n) { auto child = std::make_shared<Bone>(bone, skeleton); bone->kids.push_back(child); if (not LoadBone(stream, skeleton, child, bone->animations.size(), flags)) { return false; } } return true; } }
  21. I just tried converting the same model and got three materials. Maybe this is already fixed?
  22. It actually looks fine on my AMD 6600, but you can try this to disable linear filtering: auto sz = framebuffer->GetSize(); auto texbuffer = CreateTextureBuffer(sz.x, sz.y); auto pixels = CreatePixmap(sz.x, sz.y, TEXTURE_RGBA); auto tex = CreateTexture(TEXTURE_2D, sz.x, sz.y, pixels->format, { pixels }, 1, TEXTURE_DEFAULT, TEXTUREFILTER_NEAREST); texbuffer->SetColorAttachment(tex); cam2->SetRenderTarget(texbuffer); Now if I enable MSAA I do see an outline, and that is what I would expect, because the pixels are combined from multiple samples: You might be able to eliminate that by ignoring colors that have a red value over 0.99, or something like that. I'm not sure if there is really any way around that though.
×
×
  • Create New...