// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved // Please see LICENSE.md in repository root for license information // https://github.com/AtomicGameEngine/AtomicGameEngine #include #include "AtomicEditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "JSONSceneProcess.h" #include "JSONSceneImporter.h" static String __rootFolder = "/Users/josh/Desktop/Test/Resources"; namespace AtomicEditor { bool JSONSceneProcess::ProcessTextures() { const PODVector& textures = importer_->GetTextures(); for (unsigned i = 0; i < textures.Size(); i++) { const JSONTexture* jtexture = textures.At(i); unsigned length; const unsigned char* pixels = jtexture->GetPNGPixels(length); String filename = __rootFolder + "/Textures/" + jtexture->GetName() + ".png"; SharedPtr file (new File(context_, filename, FILE_WRITE)); file->Write(pixels, length); file->Close(); } return true; } bool JSONSceneProcess::ProcessLightmaps() { const PODVector& lightmaps = importer_->GetLightmaps(); for (unsigned i = 0; i < lightmaps.Size(); i++) { const JSONLightmap* jlightmap = lightmaps.At(i); unsigned length; const unsigned char* pixels = jlightmap->GetPNGPixels(length); String filename = __rootFolder + "/Textures/" + jlightmap->GetName() + ".png"; SharedPtr file (new File(context_, filename, FILE_WRITE)); file->Write(pixels, length); file->Close(); } return true; } bool JSONSceneProcess::ProcessMaterials() { const PODVector& materials = importer_->GetMaterials(); ResourceCache* cache = GetSubsystem(); for (unsigned i = 0; i < materials.Size(); i++) { const JSONMaterial* jmaterial = materials.At(i); const String& shader = jmaterial->GetShader(); //LOGINFOF("Shader: %s", shader.CString()); // TODO: factor in lightmaps bool lightmap = false; String technique("Diff.xml"); if (!lightmap) { if (shader == "Transparent/Diffuse") technique = "DiffAlpha.xml"; else if (shader == "Transparent/Cutout/Diffuse") technique = "DiffAlphaMask.xml"; else if (shader == "Transparent/Cutout/Specular") technique = "DiffAlphaMask.xml"; else if (shader == "Transparent/Specular") technique = "DiffAlpha.xml"; else if (shader == "Hidden/Nature/Tree Creator Leaves Fast Optimized") technique = "DiffAlphaMask.xml"; } else { technique = "DiffLightMap.xml"; if (shader == "Transparent/Diffuse") technique = "DiffLightMapAlpha.xml"; else if (shader == "Transparent/Cutout/Diffuse") technique = "DiffLightMapAlpha.xml"; else if (shader == "Transparent/Cutout/Specular") technique = "DiffLightMapAlpha.xml"; else if (shader == "Transparent/Specular") technique = "DiffLightMapAlpha.xml"; } SharedPtr material; material = new Material(context_); material->SetName("Materials/" + jmaterial->GetName() + ".xml"); Technique* _technique = cache->GetResource("Techniques/" + technique); assert(_technique); material->SetTechnique(0, _technique); const String& mainTexture = jmaterial->GetMainTexture(); if (mainTexture.Length()) { Texture2D* texture = cache->GetResource("Textures/" + mainTexture + ".png"); material->SetTexture(TU_DIFFUSE, texture); } materials_[jmaterial->GetName()] = material; } return true; } bool JSONSceneProcess::WriteMaterials() { String materialFolder("Materials"); HashMap >::Iterator itr = materials_.Begin(); while (itr != materials_.End()) { Material* material = itr->second_; SharedPtr file; file = new File(context_, __rootFolder + "/" + material->GetName(), FILE_WRITE); material->Save(*file); itr++; } return true; } bool JSONSceneProcess::WriteModels() { HashMap >::Iterator itr = models_.Begin(); while (itr != models_.End()) { Model* model = itr->second_; SharedPtr file; file = new File(context_, __rootFolder + "/" + model->GetName(), FILE_WRITE); model->Save(*file); itr++; } return true; } bool JSONSceneProcess::WriteHierarchy(Scene* scene) { List >::Iterator itr = hierarchy_.Begin(); while (itr != hierarchy_.End()) { scene->AddChild(*itr); itr++; } File file(context_); bool useXML = true; String filename; filename.AppendWithFormat("%s", importer_->GetSceneName().CString()); if (!useXML) filename += ".scene"; else filename += ".xml"; filename = __rootFolder + "/Scenes/" + filename; if (!file.Open(filename, FILE_WRITE)) ErrorExit("Could not open output file: Scenes/Test.bin"); if (useXML) scene->SaveXML(file); else scene->Save(file); file.Close(); return true; } bool JSONSceneProcess::ProcessModels() { PODVector& meshes = importer_->GetMeshes(); ResourceCache* cache = GetSubsystem(); for (unsigned i = 0; i < meshes.Size(); i++) { JSONMesh* jmesh = meshes.At(i); SharedPtr model(new Model(context_)); model->SetName("Models/" + jmesh->GetName() + ".mdl"); unsigned vertexElementMask = MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1;// | MASK_COLOR; if (jmesh->GetNumUVSets() == 2) vertexElementMask |= MASK_TEXCOORD2; PODVector& boneWeights = jmesh->GetBoneWeights(); if (boneWeights.Size()) vertexElementMask |= (MASK_BLENDWEIGHTS | MASK_BLENDINDICES); // the model has to have 65535 or less indices to avoid 32 bit indices, check this SharedPtr ib (new IndexBuffer(context_)); SharedPtr vb (new VertexBuffer(context_)); // even though shadowing, need to "SetData()" on these below // could we SetData with the shadowed data? ib->SetShadowed(true); vb->SetShadowed(true); // Setup Vertex Data unsigned vertexCount = jmesh->GetVertexCount(); vb->SetSize(vertexCount, vertexElementMask); BoundingBox bbox; PODVector& vpos = jmesh->GetVertexPositions(); PODVector& vnorm = jmesh->GetVertexNormals(); PODVector& vtan = jmesh->GetVertexTangents(); PODVector& uv0 = jmesh->GetUVSet(0); PODVector& uv1 = jmesh->GetUVSet(1); PODVector vdata; unsigned vertexSize = 8; if (vertexElementMask & MASK_TEXCOORD2) vertexSize += 2; if (vertexElementMask & MASK_BLENDWEIGHTS) vertexSize += 5; vdata.Resize(vertexCount * vertexSize); // TODO: we should be identifying unique vertices float* v = &vdata[0]; for (unsigned j = 0; j < vertexCount; j++) { if (j < vpos.Size()) { *v = vpos[j].x_; v++; *v = vpos[j].y_; v++; *v = vpos[j].z_; v++; } else { *v = 0; v++; *v = 0; v++; *v = 0; v++; } if (j < vnorm.Size()) { *v = vnorm[j].x_; v++; *v = vnorm[j].y_; v++; *v = vnorm[j].z_; v++; } else { *v = 0; v++; *v = 0; v++; *v = 0; v++; } if (j < uv0.Size()) { *v = uv0[j].x_; v++; *v = -uv0[j].y_; v++; } else { *v = 0; v++; *v = 0; v++; } if (vertexElementMask & MASK_TEXCOORD2) { if (j < uv1.Size()) { *v = uv1[j].x_; v++; *v = -uv1[j].y_; v++; } else { *v = 0; v++; *v = 0; v++; } } if (vertexElementMask & MASK_BLENDWEIGHTS) { JSONMesh::BoneWeight& boneWeight = boneWeights[j]; for (unsigned k = 0; k < 4; ++k) { *v = boneWeight.weights_[k]; v++; } } if (vertexElementMask & MASK_BLENDINDICES) { JSONMesh::BoneWeight& boneWeight = boneWeights[j]; unsigned char* destBytes = (unsigned char*)v; ++v; for (unsigned k = 0; k < 4; ++k) { *destBytes++ = (unsigned char) boneWeight.indexes_[k]; } } bbox.Merge(vpos[j]); } vb->SetData(&vdata[0]); unsigned numIndices = 0; PODVector allIndices; unsigned numGeom = jmesh->GetSubMeshCount(); for (unsigned j = 0; j < numGeom; j++) { PODVector& submesh = jmesh->GetSubMesh(j); numIndices += submesh.Size(); for (unsigned k = 0; k < submesh.Size(); k++) { allIndices.Push((uint16_t) submesh[k]); } } ib->SetSize(numIndices, false); ib->SetData(&allIndices[0]); Vector > geometry; uint16_t start = 0; for (unsigned j = 0; j < numGeom; j++) { PODVector& submesh = jmesh->GetSubMesh(j); SharedPtr geom(new Geometry(context_)); geometry.Push(geom); geom->SetIndexBuffer(ib); geom->SetVertexBuffer(0, vb, vertexElementMask); geom->SetDrawRange(TRIANGLE_LIST, start, submesh.Size(), false); start += submesh.Size(); } model->SetNumGeometries(geometry.Size()); Vector > vbVector; Vector > ibVector; vbVector.Push(vb); ibVector.Push(ib); PODVector emptyMorphRange; model->SetVertexBuffers(vbVector, emptyMorphRange, emptyMorphRange); model->SetIndexBuffers(ibVector); Vector& jbones = jmesh->GetBones(); // FIXME: we need to get the bounds or calculate propoer if (jbones.Size()) { bbox.min_ *= 2; bbox.max_ *= 2; } // see fixme's for skeletal geo center/bounds for (unsigned j = 0; j < geometry.Size(); j++) { model->SetNumGeometryLodLevels(j, 1); model->SetGeometry(j, 0, geometry[j]); // this could be finer grained // also, fix this for skeletal model->SetGeometryCenter(j, jbones.Size() ? Vector3::ZERO : bbox.Center());//Vector3::ZERO); } // FIXME: skeletal bounds is off if (!jbones.Size()) model->SetBoundingBox(bbox); else model->SetBoundingBox(BoundingBox(-1024, 1024)); // Build skeleton if necessary if (jbones.Size()) { Skeleton skeleton; Vector& bones = skeleton.GetModifiableBones(); unsigned rootindex = 0; const String& rootBoneName = jmesh->GetRootBone(); for (unsigned j = 0; j < jbones.Size(); ++j) { JSONMesh::Bone& jbone = jbones[j]; if (jbone.name_ == rootBoneName) { rootindex = j; break; } } for (unsigned j = 0; j < jbones.Size(); ++j) { JSONMesh::Bone& jbone = jbones[j]; String boneName(jbone.name_); Bone newBone; newBone.name_ = boneName; newBone.initialPosition_ = jbone.pos_; newBone.initialRotation_ = jbone.rot_; newBone.initialScale_ = jbone.scale_; // Get offset information if exists newBone.offsetMatrix_ = Matrix3x4(jmesh->GetBindPoses()[j]); //newBone.radius_ = model.boneRadii_[i]; //newBone.boundingBox_ = model.boneHitboxes_[i]; //newBone.collisionMask_ = BONECOLLISION_SPHERE | BONECOLLISION_BOX; newBone.parentIndex_ = j; bones.Push(newBone); } // Set the bone hierarchy for (unsigned j = 0; j < jbones.Size(); ++j) { JSONMesh::Bone& jbone = jbones[j]; String parentName = jbone.parentName_; if (!parentName.Length()) { bones[j].parentIndex_ = j; } else { for (unsigned k = 0; k < bones.Size(); ++k) { if (bones[k].name_ == parentName) { bones[j].parentIndex_ = k; break; } } } } skeleton.SetRootBoneIndex(rootindex); model->SetSkeleton(skeleton); } models_[jmesh->GetName()] = model; } return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONTransform* jtransform) { node->SetPosition(jtransform->GetLocalPosition()); node->SetRotation(jtransform->GetLocalRotation()); node->SetScale(jtransform->GetLocalScale()); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONTimeOfDay* jtime ) { TimeOfDay* timeofday = node->CreateComponent(); timeofday->SetTimeOn(jtime->GetTimeOn()); timeofday->SetTimeOff(jtime->GetTimeOff()); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONLight* jlight ) { if (!jlight->GetRealtime() || jlight->GetLightType() != "Point") return true; Light* light = node->CreateComponent(); light->SetRange(jlight->GetRange()); light->SetCastShadows(jlight->GetCastsShadows()); light->SetColor(jlight->GetColor()); light->SetLightType(LIGHT_POINT); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONBoxCollider* jbox ) { CollisionShape* shape = node->CreateComponent(); shape->SetBox(jbox->GetSize(), jbox->GetCenter()); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONMeshCollider* jmesh ) { // JSONMeshCollider should store the mesh, as this may not be on node CollisionShape* shape = node->CreateComponent(); StaticModel* model = node->GetComponent(); if (!model || !model->GetModel()) { LOGWARNING("Missing model for MeshCollier"); return true; } assert(model && model->GetModel()); if (model) shape->SetTriangleMesh(model->GetModel()); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONRigidBody* jbody ) { RigidBody* body = node->CreateComponent(); body->SetMass(jbody->GetMass()); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONTerrain* jterrain) { int heightmapHeight = jterrain->GetHeightMapHeight(); int heightmapWidth = jterrain->GetHeightMapWidth(); unsigned length; const float* heightmap = jterrain->GetHeightMap(length); const Vector3 heightmapScale = jterrain->GetHeightMapScale(); const Vector3 heightmapSize = jterrain->GetHeightMapSize(); PODVector bytes; bytes.Resize(heightmapHeight * heightmapWidth * 4); uint8_t* out = &bytes[0]; for (int y = 0; y < heightmapHeight; y++) { for (int x = 0; x < heightmapWidth; x++) { float h = heightmap[y * heightmapWidth + x]; uint8_t hbyte = uint8_t(255.0 * h); *out++ = hbyte; *out++ = hbyte; *out++ = hbyte; *out++ = 255; } } SharedPtr image(new Image(context_)); image->SetSize(heightmapWidth, heightmapHeight, 4); image->SetData(&bytes[0]); String heightMapPath = __rootFolder + "/Textures/TerrainHeightMap.png"; image->SavePNG(heightMapPath); int alphamapWidth = jterrain->GetAlphaMapHeight(); int alphamapHeight = jterrain->GetAlphaMapWidth(); int alphamapLayers = jterrain->GetAlphaMapLayers(); if (alphamapLayers > 3) alphamapLayers = 3; const float* alphamap = jterrain->GetAlphaMap(length); bytes.Resize( alphamapWidth * alphamapWidth * 4); memset (&bytes[0], 255, alphamapWidth * alphamapWidth * 4); for (int i = 0; i < alphamapLayers; i++) { uint8_t* out = &bytes[i]; for (int y = 0; y < alphamapHeight; y++) { for (int x = 0; x < alphamapWidth; x++) { float w = alphamap[(i * (alphamapWidth * alphamapHeight)) + y * alphamapWidth + x ]; uint8_t wbyte = uint8_t(255.0 * w); *out = wbyte; out += 4; } } } SharedPtr alphaWeights(new Image(context_)); alphaWeights->SetSize(alphamapWidth, alphamapHeight, 4); alphaWeights->SetData(&bytes[0]); String alphaWeightsPath = __rootFolder + "/Textures/TerrainWeights.png"; alphaWeights->SavePNG(alphaWeightsPath); ResourceCache* cache = GetSubsystem(); Image* heightMap = cache->GetResource(heightMapPath); Material* material = cache->GetResource("Materials/DemoTerrain.xml"); Terrain* terrain = node->CreateComponent(); terrain->SetHeightMap(heightMap); terrain->SetMaterial(material); terrain->SetSmoothing(true); //terrain->SetPatchSize(64); //terrain->SetSpacing(Vector3(2.0f, 0.5f, 2.0f)); // Spacing between vertices and vertical resolution of the height map //terrain->SetSmoothing(true); Vector3 spacing = heightmapScale; spacing.y_ = heightmapScale.y_ / 250.0f; terrain->SetSpacing(spacing); Vector3 pos = node->GetPosition(); pos.x_ += heightmapSize.x_/2.0f; pos.y_ += .1f; pos.z_ += heightmapSize.z_/2.0f; node->SetPosition(pos); Quaternion rotation; rotation = node->GetRotation(); rotation.FromEulerAngles(0, -90, 0); node->SetRotation(rotation); RigidBody* body = node->CreateComponent(); // use layer 2 for static body->SetCollisionLayer(2); body->SetFriction(1.0f); CollisionShape* shape = node->CreateComponent(); shape->SetTerrain(); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONAnimation* janim ) { const Vector& clips = janim->GetClips(); for (unsigned i = 0; i < clips.Size(); i++) { const JSONAnimation::AnimationClip* clip = clips[i]; SharedPtr outAnim(new Animation(context_)); outAnim->SetAnimationName(clip->name_); outAnim->SetLength(clip->GetDuration()); Vector tracks; for (unsigned j = 0; j < clip->nodes_.Size(); ++j) { const JSONAnimation::AnimationNode* node = clip->nodes_[j]; String channelName = node->name_; if (channelName.Contains('/')) { channelName = channelName.Split('/').Back(); } AnimationTrack track; track.name_ = channelName; track.nameHash_ = channelName; // TODO: optimize channels track.channelMask_ |= (CHANNEL_POSITION | CHANNEL_ROTATION | CHANNEL_SCALE); //assert(node->keyframes_.Size()); for (unsigned k = 0; k < node->keyframes_.Size(); k++) { const JSONAnimation::Keyframe* keyframe = node->keyframes_[k]; AnimationKeyFrame key; key.time_ = keyframe->time_; key.position_ = keyframe->pos_; key.rotation_ = keyframe->rot_; key.scale_ = keyframe->scale_; track.keyFrames_.Push(key); } tracks.Push(track); } outAnim->SetTracks(tracks); String filename = __rootFolder; filename.AppendWithFormat("/Models/AS_FatZombie_FBX_FatZombie_LOD0_%s.ani", clip->name_.CString()); File outFile(context_); if (!outFile.Open(filename, FILE_WRITE)) ErrorExit("Could not open output file for animation"); outAnim->Save(outFile); } return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONMeshRenderer* jmeshrenderer ) { ResourceCache* cache = GetSubsystem(); StaticModel* staticModel = NULL; int lightmapIndex = jmeshrenderer->GetLightmapIndex(); if (lightmapIndex >= 0) { LMStaticModel* lmstatic = node->CreateComponent(); staticModel = lmstatic; const Vector4& tilingOffset = jmeshrenderer->GetLightmapTilingOffset(); lmstatic->lightmapTilingOffset_ = tilingOffset; lmstatic->lightmapTilingOffset_.w_ = -lmstatic->lightmapTilingOffset_.w_; String lightmapName; lightmapName.AppendWithFormat("Textures/%s_Lightmap_%i.png", importer_->GetSceneName().CString(), lightmapIndex); Texture2D* texture = cache->GetResource(lightmapName); assert(texture); lmstatic->SetLightmapTexure(texture); } else staticModel = node->CreateComponent(); const JSONMesh* mesh = jmeshrenderer->GetMesh(); assert(models_.Contains(mesh->GetName())); Model* model = models_[mesh->GetName()]; staticModel->SetModel(model); for (unsigned i = 0; i < jmeshrenderer->GetNumMaterials(); i++) { const JSONMaterial* jmat = jmeshrenderer->GetMaterial(i); assert(materials_.Contains(jmat->GetName())); staticModel->SetMaterial(i, materials_[jmat->GetName()]); } staticModel->SetCastShadows(jmeshrenderer->GetCastShadows()); /* CollisionShape* shape = node->CreateComponent(); shape->SetTriangleMesh(model, 0); node->CreateComponent(); */ BoundingBox bbox = model->GetBoundingBox(); bbox.Transform(node->GetWorldTransform()); Vector3 size = bbox.Size(); //staticModel->SetDrawDistance(size.Length() * 30.0f); //staticModel->SetShadowDistance(size.Length() * 10.0f); return true; } bool JSONSceneProcess::ProcessComponent(Node* node, const JSONSkinnedMeshRenderer* jmeshrenderer ) { ResourceCache* cache = GetSubsystem(); AnimatedModel* animatedModel = node->CreateComponent(); const JSONMesh* mesh = jmeshrenderer->GetMesh(); assert(models_.Contains(mesh->GetName())); Model* model = models_[mesh->GetName()]; animatedModel->SetModel(model); for (unsigned i = 0; i < jmeshrenderer->GetNumMaterials(); i++) { const JSONMaterial* jmat = jmeshrenderer->GetMaterial(i); assert(materials_.Contains(jmat->GetName())); animatedModel->SetMaterial(i, materials_[jmat->GetName()]); } animatedModel->SetCastShadows(jmeshrenderer->GetCastShadows()); return true; } Node* JSONSceneProcess::ProcessNode(const JSONNode* jnode, Node* parent) { Node* node = parent->CreateChild(jnode->GetName()); const PODVector& components = jnode->GetComponents(); for (unsigned i = 0; i < components.Size(); i++) { const JSONComponent* c = components.At(i); if (c->GetType() == "Transform") { ProcessComponent(node, (const JSONTransform*) c); } else if (c->GetType() == "MeshRenderer") { ProcessComponent(node, (const JSONMeshRenderer*) c); } else if (c->GetType() == "SkinnedMeshRenderer") { ProcessComponent(node, (const JSONSkinnedMeshRenderer*) c); } else if (c->GetType() == "Terrain") { ProcessComponent(node, (const JSONTerrain*) c); } else if (c->GetType() == "Animation") { ProcessComponent(node, (const JSONAnimation*) c); } else if (c->GetType() == "BoxCollider") { ProcessComponent(node, (const JSONBoxCollider*) c); } else if (c->GetType() == "MeshCollider") { ProcessComponent(node, (const JSONMeshCollider*) c); } else if (c->GetType() == "RigidBody") { ProcessComponent(node, (const JSONRigidBody*) c); } else if (c->GetType() == "Light") { ProcessComponent(node, (const JSONLight*) c); } else if (c->GetType() == "TimeOfDay") { ProcessComponent(node, (const JSONTimeOfDay*) c); } } if (node->GetComponent() && !node->GetComponent()) { RigidBody* body = node->CreateComponent(); body->SetCollisionLayer(2); } const PODVector& children = jnode->GetChildren(); for (unsigned i = 0; i < children.Size(); i++) { node->AddChild(ProcessNode(children.At(i), node)); } return node; } bool JSONSceneProcess::ProcessHierarchy(Scene* scene) { const PODVector& hierarchy = importer_->GetHierarchy(); for (unsigned i = 0; i < hierarchy.Size(); i++) { hierarchy_.Push(SharedPtr(ProcessNode(hierarchy[i], scene))); } return true; } bool JSONSceneProcess::Process() { ResourceCache* cache = GetSubsystem(); cache->AddResourceDir(__rootFolder); scene_ = new Scene(context_); scene_->CreateComponent(); scene_->CreateComponent(); scene_->CreateComponent(); Node* zoneNode = scene_->CreateChild("Zone"); Zone* zone = zoneNode->CreateComponent(); zone->SetBoundingBox(BoundingBox(-10000.0f, 10000.f)); zone->SetAmbientColor(Color(1, 1, 1)); ProcessTextures(); ProcessLightmaps(); ProcessMaterials(); ProcessModels(); ProcessHierarchy(scene_); return true; } bool JSONSceneProcess::Write() { WriteMaterials(); WriteModels(); WriteHierarchy(scene_); return true; } }