Jump to content

Josh

Staff
  • Posts

    23,248
  • Joined

  • Last visited

Everything posted by Josh

  1. 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
  2. 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
  3. 0.9.6 Updated the thumbnail utility to support the newer version 301 model format. It's probably a little faster now too.
  4. 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; } }
  5. I just tried converting the same model and got three materials. Maybe this is already fixed?
  6. 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.
  7. I think this is fixed on current build of beta branch, please sync to get the shader update.
  8. 0.9.6 Shader update improves the default offsets for shadow maps. I think it's good, let me know what you think. Directional lights now have a longer view distance.
  9. Your final rotation must be calculated arbitrarily then. You want to turn the entity 90 degrees over the edge. You have to rotate around the axis of that edge.
  10. 0.9.6 Full build with all recent bug fixes.
  11. Thanks for reporting this. The issue will be resolved in the next build that goes up. This was caused by an "improvement" I tried to make, so it was easier to jump while running up stairs, but the problem here clearly show the "fix" was not worth it.
  12. Josh

    PBR Bug

    Okay, I changed the pixel format these images get stored in, and it seems to work correctly now. Thank you for reporting this issue.
  13. Josh

    PBR Bug

    It looks like the normal map is causing this. If you remove the normal map the model looks like it should. The texture looks okay, but I notice it is in RGBA16 format. Maybe it it being loaded into the wrong pixel format...
  14. Josh

    PBR Bug

    Here is what I see, side by side: Inspecting the model now...
  15. I am going to mark this as solved because I think it is not occurring in the current build. I recently made some changes with colliders stored in the map file, and that probably eliminated this problem. Please let me know if anything needs further investigation.
  16. I am going to mark this as solved. I think you just need to cast the entity to a sprite. Let me know if there is still a problem when you get back.
  17. I sent an example project to try to produce the error.
  18. 0.9.6 Fixed a few bugs in the editor.
  19. I had some problems with some of the logic in the code that handles MSAA and had to edit that shader. I think the current version is fixed.
  20. Whatever material the sprite uses should be using the unlit shader.
  21. You need quaternions and Slerp(). It's the only way to interpolate between arbitrary rotations.
×
×
  • Create New...