/* * Copyright (c) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "frameworks/bridge/declarative_frontend/jsview/js_sceneview.h" #include #include "custom/custom_render_descriptor.h" #include "custom/shader_input_buffer.h" #include "data_type/constants.h" #include "data_type/geometry/cone.h" #include "data_type/geometry/cube.h" #include "data_type/geometry/sphere.h" #include "data_type/light.h" #include "base/geometry/quaternion.h" #include "base/geometry/vec3.h" #include "core/components_ng/base/view_stack_model.h" #include "core/components_ng/pattern/model/model_view_ng.h" #include "frameworks/bridge/declarative_frontend/engine/functions/js_click_function.h" #include "frameworks/bridge/declarative_frontend/view_stack_processor.h" namespace OHOS::Ace { std::unique_ptr ModelView::instance_ = nullptr; std::mutex ModelView::mutex_; ModelView* ModelView::GetInstance() { if (!instance_) { std::lock_guard lock(mutex_); if (!instance_) { #ifdef NG_BUILD instance_.reset(new NG::ModelViewNG()); #else if (Container::IsCurrentUseNewPipeline()) { instance_.reset(new NG::ModelViewNG()); } else { LOGW("ModelView::GetInstance() NOT NG Pipeline not support"); } #endif } } return instance_.get(); } } // namespace OHOS::Ace namespace OHOS::Ace::Framework { static const std::regex MODEL_RES_ID_REGEX(R"(^resource://\w+/([0-9]+)\.\w+$)", std::regex::icase); static const std::regex MODEL_APP_RES_PATH_REGEX(R"(^resource://RAWFILE/(.*)$)"); static const std::regex MODEL_APP_RES_ID_REGEX(R"(^resource://.*/([0-9]+)\.\w+$)", std::regex::icase); static const std::regex MODEL_RES_NAME_REGEX(R"(^resource://.*/(\w+)\.\w+$)", std::regex::icase); static constexpr uint32_t MODEL_RESOURCE_MATCH_SIZE = 2; bool GetResourceId(const std::string& uri, uint32_t& resId) { std::smatch matches; if (std::regex_match(uri, matches, MODEL_RES_ID_REGEX) && matches.size() == MODEL_RESOURCE_MATCH_SIZE) { resId = static_cast(std::stoul(matches[1].str())); return true; } std::smatch appMatches; if (std::regex_match(uri, appMatches, MODEL_APP_RES_ID_REGEX) && appMatches.size() == MODEL_RESOURCE_MATCH_SIZE) { resId = static_cast(std::stoul(appMatches[1].str())); return true; } return false; } bool GetResourceId(const std::string& uri, std::string& path) { std::smatch matches; if (std::regex_match(uri, matches, MODEL_APP_RES_PATH_REGEX) && matches.size() == MODEL_RESOURCE_MATCH_SIZE) { path = matches[1].str(); return true; } return false; } bool GetResourceName(const std::string& uri, std::string& resName) { std::smatch matches; if (std::regex_match(uri, matches, MODEL_RES_NAME_REGEX) && matches.size() == MODEL_RESOURCE_MATCH_SIZE) { resName = matches[1].str(); return true; } return false; } bool SetOhosPath(const std::string& uri, std::string& ohosPath) { if (GetResourceId(uri, ohosPath)) { ohosPath = "OhosRawFile://" + ohosPath; return true; } uint32_t resId = 0; if (GetResourceId(uri, resId)) { ohosPath = "OhosRawFile://" + std::to_string(resId); return true; } if (GetResourceName(uri, ohosPath)) { ohosPath = "OhosRawFile://" + ohosPath; return true; } ohosPath = "OhosRawFile://" + ohosPath; return false; } // get Number data template bool GetModelProperty( const JSRef& jsValue, const std::string& propertyName, std::unordered_map& propertyData) { auto item = jsValue->GetProperty(propertyName.c_str()); if (item->IsObject()) { JSRef itemObj = JSRef::Cast(item); for (auto iter = propertyData.begin(); iter != propertyData.end(); ++iter) { JSRef itemData = itemObj->GetProperty((iter->first).c_str()); if (itemData->IsNumber()) { iter->second = itemData->ToNumber(); continue; } if (itemData->IsBoolean()) { iter->second = itemData->ToBoolean(); continue; } return false; } return true; } return false; } void JSSceneView::JsSetHandleCameraMove(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } if (!info[0]->IsBoolean()) { return; } bool value = info[0]->ToBoolean(); ModelView::GetInstance()->SetHandleCameraMove(value); } void JSSceneView::Create(const JSCallbackInfo& info) { const auto& length = info.Length(); std::string srcPath(""); int surfaceData = 0; std::string bundleName; std::string moduleName; Render3D::SurfaceType surfaceType = OHOS::Render3D::SurfaceType::SURFACE_TEXTURE; if (length == 2) { // 2: info size surfaceData = info[1]->ToNumber(); ParseJsMedia(info[0], srcPath); GetJsMediaBundleInfo(info[0], bundleName, moduleName); } else if (length == 1) { if (!ParseJsMedia(info[0], srcPath)) { // SceneOptions JSRef jsObj = JSRef::Cast(info[0]); auto scene = jsObj->GetProperty("scene"); if (!scene->IsNull()) { ParseJsMedia(scene, srcPath); GetJsMediaBundleInfo(scene, bundleName, moduleName); } auto type = jsObj->GetProperty("modelType"); if (!type->IsNull()) { surfaceData = type->ToNumber(); } } } surfaceType = (surfaceData == 0) ? OHOS::Render3D::SurfaceType::SURFACE_TEXTURE : OHOS::Render3D::SurfaceType::SURFACE_WINDOW; std::string ohosPath(""); SetOhosPath(srcPath, ohosPath); LOGD("srcPath after ParseJsMedia(): %s bundleName: %s, moduleName %s", ohosPath.c_str(), bundleName.c_str(), moduleName.c_str()); ModelView::GetInstance()->Create(bundleName, moduleName, surfaceType); ModelView::GetInstance()->SetModelSource(ohosPath); } void JSSceneView::JsCamera(const JSCallbackInfo& info) { // Parse the info object. if (info.Length() <= 0 || !info[0]->IsObject()) { return; } AnimationOption animOption = ViewStackModel::GetInstance()->GetImplicitAnimationOption(); JSRef jsObj = JSRef::Cast(info[0]); std::unordered_map perspect { { "zNear", 0.5f }, { "zFar", 50.0f }, { "yFov", 60.0f } }; GetModelProperty(jsObj, "perspective", perspect); ModelView::GetInstance()->SetCameraFrustum(perspect["zNear"], perspect["zFar"], perspect["yFov"]); // cameraSpace std::unordered_map positionAng { { "theta", 0.0f }, { "phi", 0.0f }, { "radius", 4.0f } }; std::unordered_map position { { "x", 0.0f }, { "y", 0.0f }, { "z", 4.0f } }; std::unordered_map front { { "x", 0.0f }, { "y", 0.0f }, { "z", 0.0f } }; std::unordered_map up { { "x", 0.0f }, { "y", 0.0f }, { "z", 0.0f } }; if (GetModelProperty(jsObj, "cameraSpace", positionAng)) { ModelView::GetInstance()->SetCameraPosition(AnimatableFloat(positionAng["theta"], animOption), AnimatableFloat(0, animOption), AnimatableFloat(positionAng["phi"], animOption), AnimatableFloat(positionAng["radius"], animOption), true); return; } auto itemCameraSpace = jsObj->GetProperty("cameraSpace"); if (itemCameraSpace->IsObject()) { JSRef spaceObj = JSRef::Cast(itemCameraSpace); // position GetModelProperty(spaceObj, "position", position); // front GetModelProperty(spaceObj, "front", front); // up GetModelProperty(spaceObj, "up", up); } ModelView::GetInstance()->SetCameraPosition(AnimatableFloat(position["x"], animOption), AnimatableFloat(position["y"], animOption), AnimatableFloat(position["z"], animOption), AnimatableFloat(0.0f, animOption), false); Vec3 lookVec(front["x"], front["y"], front["z"]); ModelView::GetInstance()->SetCameraLookAt(lookVec); Vec3 upVec(up["x"], up["y"], up["z"]); ModelView::GetInstance()->SetCameraUp(upVec); } void JSSceneView::JsSetTransparent(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } if (!info[0]->IsBoolean()) { return; } bool value = info[0]->ToBoolean(); ModelView::GetInstance()->SetTransparent(value); } void JSSceneView::JsSetBackground(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } std::string srcPath; auto parseOk = ParseJsMedia(info[0], srcPath); if (!parseOk) { return; } std::string ohosPath(""); SetOhosPath(srcPath, ohosPath); ModelView::GetInstance()->SetBackground(ohosPath); } void JSSceneView::JsLight(const JSCallbackInfo& info) { // Parse the info object. if (info.Length() <= 0 || !info[0]->IsObject()) { return; } JSRef jsObj = JSRef::Cast(info[0]); JSRef itemType = jsObj->GetProperty("type"); JSRef itemIntensity = jsObj->GetProperty("intensity"); JSRef itemShadow = jsObj->GetProperty("shadow"); auto type = static_cast((itemType->IsNumber()) ? itemType->ToNumber() : 1); int intensity = (itemIntensity->IsNumber()) ? itemIntensity->ToNumber() : 10; bool shadow = (itemShadow->IsBoolean()) ? itemShadow->ToBoolean() : false; JSRef lightColor = jsObj->GetProperty("color"); Color color(0xffffffff); // red:255, green:255, blue:255 ParseJsColor(lightColor, color); AnimationOption animOption = ViewStackModel::GetInstance()->GetImplicitAnimationOption(); Vec3 inputColor = Vec3(color.GetRed() / 255.0f, color.GetGreen() / 255.0f, color.GetBlue() / 255.0f, animOption); OHOS::Ace::NG::ModelPosition position; double maxInvalid = std::numeric_limits::max(); Quaternion rotation = Quaternion(maxInvalid, maxInvalid, maxInvalid, maxInvalid); std::unordered_map positionAng { { "theta", 0.0f }, { "phi", 0.0f }, { "radius", 4.0f } }; std::unordered_map pos { { "x", 0.0f }, { "y", 1.0f }, { "z", 0.0f } }; std::unordered_map quat { { "x", 0.0f }, { "y", 1.0f }, { "z", 0.0f }, { "w", 1.0f } }; if (GetModelProperty(jsObj, "lightSpace", positionAng)) { position.Set({ AnimatableFloat(positionAng["theta"], animOption), AnimatableFloat(0.0f, animOption), AnimatableFloat(positionAng["phi"], animOption) }, AnimatableFloat(positionAng["radius"], animOption), true); ModelView::GetInstance()->AddLight(AceType::MakeRefPtr( type, inputColor, AnimatableFloat(intensity, animOption), shadow, position, rotation)); return; } auto itemLightSpace = jsObj->GetProperty("lightSpace"); if (itemLightSpace->IsObject()) { JSRef spaceObj = JSRef::Cast(itemLightSpace); GetModelProperty(spaceObj, "position", pos); position.Set({ AnimatableFloat(pos["x"], animOption), AnimatableFloat(pos["y"], animOption), AnimatableFloat(pos["z"], animOption) }, AnimatableFloat(0.0f, animOption), false); GetModelProperty(spaceObj, "rotation", quat); rotation = Quaternion(quat["x"], quat["y"], quat["z"], quat["w"]); } ModelView::GetInstance()->AddLight(AceType::MakeRefPtr( type, inputColor, AnimatableFloat(intensity, animOption), shadow, position, rotation)); } void JSSceneView::JsAddCube(const JSCallbackInfo& info) { // Parse the info object. if (info.Length() <= 0 || !info[0]->IsObject()) { return; } JSRef jsObj = JSRef::Cast(info[0]); auto name = jsObj->GetPropertyValue("name", ""); auto width = jsObj->GetPropertyValue("width", 0.0); auto height = jsObj->GetPropertyValue("height", 0.0); auto depth = jsObj->GetPropertyValue("depth", 0.0); OHOS::Render3D::Vec3 position(0.0f, 0.0f, 0.0f); if (jsObj->HasProperty("position")) { JSRef positionArgs = jsObj->GetProperty("position"); if (positionArgs->IsObject()) { JSRef posObj = JSRef::Cast(positionArgs); position.SetX(posObj->GetPropertyValue("x", 0.0)); position.SetY(posObj->GetPropertyValue("y", 0.0)); position.SetZ(posObj->GetPropertyValue("z", 0.0)); } } ModelView::GetInstance()->AddGeometry( std::make_shared(name.c_str(), width, height, depth, position)); } void JSSceneView::JsAddSphere(const JSCallbackInfo& info) { // Parse the info object. if (info.Length() <= 0 || !info[0]->IsObject()) { return; } JSRef jsObj = JSRef::Cast(info[0]); auto name = jsObj->GetPropertyValue("name", ""); auto radius = jsObj->GetPropertyValue("radius", 0.0); auto rings = jsObj->GetPropertyValue("rings", 0); auto sectors = jsObj->GetPropertyValue("sectors", 0); OHOS::Render3D::Vec3 position(0.0f, 0.0f, 0.0f); if (jsObj->HasProperty("position")) { JSRef positionArgs = jsObj->GetProperty("position"); if (positionArgs->IsObject()) { JSRef posObj = JSRef::Cast(positionArgs); position.SetX(posObj->GetPropertyValue("x", 0.0)); position.SetY(posObj->GetPropertyValue("y", 0.0)); position.SetZ(posObj->GetPropertyValue("z", 0.0)); } } ModelView::GetInstance()->AddGeometry( std::make_shared(name.c_str(), radius, rings, sectors, position)); } void JSSceneView::JsAddCone(const JSCallbackInfo& info) { // Parse the info object. if (info.Length() <= 0 || !info[0]->IsObject()) { return; } JSRef jsObj = JSRef::Cast(info[0]); auto name = jsObj->GetPropertyValue("name", ""); auto radius = jsObj->GetPropertyValue("radius", 0.0); auto length = jsObj->GetPropertyValue("length", 0); auto sectors = jsObj->GetPropertyValue("sectors", 0); OHOS::Render3D::Vec3 position(0.0f, 0.0f, 0.0f); if (jsObj->HasProperty("position")) { JSRef positionArgs = jsObj->GetProperty("position"); if (positionArgs->IsObject()) { JSRef posObj = JSRef::Cast(positionArgs); position.SetX(posObj->GetPropertyValue("x", 0.0)); position.SetY(posObj->GetPropertyValue("y", 0.0)); position.SetZ(posObj->GetPropertyValue("z", 0.0)); } } ModelView::GetInstance()->AddGeometry( std::make_shared(name.c_str(), radius, length, sectors, position)); } void JSSceneView::JsGLTFAnimation(const JSCallbackInfo& info) { // Parse the info object. if (info.Length() < 1 || !info[0]->IsObject()) { return; } JSRef jsObj = JSRef::Cast(info[0]); JSRef itemName = jsObj->GetProperty("name"); std::string name = (itemName->IsString()) ? itemName->ToString() : ""; JSRef itemState = jsObj->GetProperty("state"); auto state = (itemState->IsNumber()) ? itemState->ToNumber() : 0; JSRef itemRepeat = jsObj->GetProperty("repeatCount"); auto repeatCount = (itemRepeat->IsNumber()) ? itemRepeat->ToNumber() : -1; JSRef itemSpeed = jsObj->GetProperty("speed"); auto speed = (itemSpeed->IsNumber()) ? itemSpeed->ToNumber() : 1.0f; JSRef itemDuration = jsObj->GetProperty("duration"); auto duration = (itemDuration->IsNumber()) ? itemDuration->ToNumber() : -1.0f; JSRef itemReverse = jsObj->GetProperty("reverse"); auto reverse = (itemReverse->IsBoolean()) ? itemReverse->ToBoolean() : false; ModelView::GetInstance()->AddGLTFAnimation(std::make_shared( name, static_cast(state), repeatCount, speed, duration, reverse)); } void JSSceneView::JsAddCustomRender(const JSCallbackInfo& info) { if (info.Length() != 2) { return; } if (info[1]->IsNull() || !info[1]->IsBoolean()) { return; } std::string uri; auto parseOk = ParseJsMedia(info[0], uri); if (!parseOk) { return; } std::string ohosPath(""); SetOhosPath(uri, ohosPath); auto desc = std::make_shared(ohosPath, info[1]->ToBoolean()); ModelView::GetInstance()->AddCustomRender(desc); } void JSSceneView::JsWidth(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } if (LessNotEqual(value.Value(), 0.0)) { value.SetValue(0.0); } ModelView::GetInstance()->SetWidth(value); } void JSSceneView::JsHeight(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { return; } if (LessNotEqual(value.Value(), 0.0)) { value.SetValue(0.0); } ModelView::GetInstance()->SetHeight(value); } void JSSceneView::JsRenderWidth(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { value.SetValue(1.0f); return; } if (info[0]->IsNumber() || info[0]->IsObject()) { value.SetValue(1.0f); } if (LessNotEqual(value.Value(), 0.0f)) { value.SetValue(0.0f); } ModelView::GetInstance()->SetRenderWidth(value); } void JSSceneView::JsRenderHeight(const JSCallbackInfo& info) { if (info.Length() < 1) { return; } CalcDimension value; if (!ParseJsDimensionVp(info[0], value)) { LOGE("invalid args for render height"); value.SetValue(1.0f); return; } if (info[0]->IsNumber() || info[0]->IsObject()) { value.SetValue(1.0f); } if (LessNotEqual(value.Value(), 0.0f)) { value.SetValue(0.0f); } ModelView::GetInstance()->SetRenderHeight(value); } void JSSceneView::JsRenderFrameRate(const JSCallbackInfo& info) {} void JSSceneView::JsShader(const JSCallbackInfo& info) { if (info.Length() != 1) { return; } std::string shaderPath; auto parseOk = ParseJsMedia(info[0], shaderPath); if (!parseOk) { return; } std::string ohosPath(""); SetOhosPath(shaderPath, ohosPath); ModelView::GetInstance()->SetShader(ohosPath); } void JSSceneView::JsShaderImageTexture(const JSCallbackInfo& info) { if (info.Length() != 1) { return; } std::string texturePath; auto parseOk = ParseJsMedia(info[0], texturePath); if (!parseOk) { return; } std::string ohosPath(""); SetOhosPath(texturePath, ohosPath); ModelView::GetInstance()->AddShaderImageTexture(ohosPath); } void JSSceneView::JsShaderInputBuffer(const JSCallbackInfo& info) { if (info.Length() != 1 || !info[0]->IsArray()) { return; } JSRef array = JSRef::Cast(info[0]); int32_t length = static_cast(array->Length()); if (length <= 0) { return; } auto modelView = ModelView::GetInstance(); std::shared_ptr buffer = nullptr; // same shader input buffer would be rejected to update for nearEqual check buffer = std::make_shared(); if (!buffer->Alloc(length)) { return; } for (uint32_t i = 0; i < static_cast(length); i++) { JSRef jsValue = array->GetValueAt(i); if (jsValue->IsNumber()) { buffer->Update(jsValue->ToNumber(), i); } else { return; } } modelView->AddShaderInputBuffer(buffer); } void JSSceneView::JsOnError(const JSCallbackInfo& info) {} void JSSceneView::JSBind(BindingTarget globalObj) { JSClass::Declare("Component3D"); MethodOptions opt = MethodOptions::NONE; JSClass::StaticMethod("create", &JSSceneView::Create, opt); JSClass::StaticMethod("gestureAccess", &JSSceneView::JsSetHandleCameraMove); JSClass::StaticMethod("camera", &JSSceneView::JsCamera); JSClass::StaticMethod("transparent", &JSSceneView::JsSetTransparent); JSClass::StaticMethod("environment", &JSSceneView::JsSetBackground); JSClass::StaticMethod("light", &JSSceneView::JsLight); JSClass::StaticMethod("cube", &JSSceneView::JsAddCube); JSClass::StaticMethod("sphere", &JSSceneView::JsAddSphere); JSClass::StaticMethod("cone", &JSSceneView::JsAddCone); JSClass::StaticMethod("modelAnimation", &JSSceneView::JsGLTFAnimation); JSClass::StaticMethod("customRender", &JSSceneView::JsAddCustomRender); JSClass::StaticMethod("width", &JSSceneView::JsWidth); JSClass::StaticMethod("height", &JSSceneView::JsHeight); JSClass::StaticMethod("shader", &JSSceneView::JsShader); JSClass::StaticMethod("renderWidth", &JSSceneView::JsRenderWidth); JSClass::StaticMethod("renderHeight", &JSSceneView::JsRenderHeight); JSClass::StaticMethod("renderFrameRateHint", &JSSceneView::JsRenderFrameRate); JSClass::StaticMethod("shaderImageTexture", &JSSceneView::JsShaderImageTexture); JSClass::StaticMethod("shaderInputBuffer", &JSSceneView::JsShaderInputBuffer); JSClass::StaticMethod("OnError", &JSSceneView::JsOnError); JSClass::StaticMethod("onAppear", &JSInteractableView::JsOnAppear); JSClass::StaticMethod("onDisAppear", &JSInteractableView::JsOnDisAppear); JSClass::StaticMethod("onClick", &JSInteractableView::JsOnClick); JSClass::InheritAndBind(globalObj); } } // namespace OHOS::Ace::Framework