1/*
2 * Copyright (C) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15#include "EnvironmentJS.h"
16
17#include <meta/api/make_callback.h>
18#include <meta/interface/intf_task_queue.h>
19#include <meta/interface/intf_task_queue_registry.h>
20#include <meta/interface/property/property_events.h>
21#include <scene_plugin/api/camera.h> // for the classid..
22#include <scene_plugin/api/node_uid.h>
23#include <scene_plugin/interface/intf_ecs_scene.h>
24#include <scene_plugin/interface/intf_node.h>
25#include <scene_plugin/interface/intf_scene.h>
26
27#include <render/intf_render_context.h>
28
29#include "SceneJS.h"
30using namespace SCENE_NS;
31
32void EnvironmentJS::Init(napi_env env, napi_value exports)
33{
34    using namespace NapiApi;
35
36    BASE_NS::vector<napi_property_descriptor> node_props;
37    SceneResourceImpl::GetPropertyDescs(node_props);
38    // clang-format off
39
40    node_props.emplace_back(GetSetProperty<uint32_t, EnvironmentJS, &EnvironmentJS::GetBackgroundType,
41        &EnvironmentJS::SetBackgroundType>("backgroundType"));
42    node_props.emplace_back(GetSetProperty<Object, EnvironmentJS, &EnvironmentJS::GetEnvironmentImage,
43        &EnvironmentJS::SetEnvironmentImage>("environmentImage"));
44    node_props.emplace_back(GetSetProperty<Object, EnvironmentJS, &EnvironmentJS::GetRadianceImage,
45        &EnvironmentJS::SetRadianceImage>("radianceImage"));
46    node_props.emplace_back(GetSetProperty<NapiApi::Array, EnvironmentJS, &EnvironmentJS::GetIrradianceCoefficients,
47        &EnvironmentJS::SetIrradianceCoefficients>("irradianceCoefficients"));
48    node_props.emplace_back(GetSetProperty<Object, EnvironmentJS, &EnvironmentJS::GetIndirectDiffuseFactor,
49        &EnvironmentJS::SetIndirectDiffuseFactor>("indirectDiffuseFactor"));
50    node_props.emplace_back(GetSetProperty<Object, EnvironmentJS, &EnvironmentJS::GetIndirectSpecularFactor,
51        &EnvironmentJS::SetIndirectSpecularFactor>("indirectSpecularFactor"));
52    node_props.emplace_back(GetSetProperty<Object, EnvironmentJS, &EnvironmentJS::GetEnvironmentMapFactor,
53        &EnvironmentJS::SetEnvironmentMapFactor>("environmentMapFactor"));
54
55    // clang-format on
56
57    napi_value func;
58    auto status = napi_define_class(env, "Environment", NAPI_AUTO_LENGTH, BaseObject::ctor<EnvironmentJS>(), nullptr,
59        node_props.size(), node_props.data(), &func);
60
61    NapiApi::MyInstanceState* mis;
62    napi_get_instance_data(env, (void**)&mis);
63    mis->StoreCtor("Environment", func);
64
65    NapiApi::Object exp(env, exports);
66
67    napi_value eType;
68    napi_value v;
69    napi_create_object(env, &eType);
70#define DECL_ENUM(enu, x)                                      \
71    napi_create_uint32(env, EnvironmentBackgroundType::x, &v); \
72    napi_set_named_property(env, enu, #x, v)
73
74    DECL_ENUM(eType, BACKGROUND_NONE);
75    DECL_ENUM(eType, BACKGROUND_IMAGE);
76    DECL_ENUM(eType, BACKGROUND_CUBEMAP);
77    DECL_ENUM(eType, BACKGROUND_EQUIRECTANGULAR);
78#undef DECL_ENUM
79    exp.Set("EnvironmentBackgroundType", eType);
80}
81
82napi_value EnvironmentJS::Dispose(NapiApi::FunctionContext<>& ctx)
83{
84    LOG_F("EnvironmentJS::Dispose");
85    DisposeNative();
86    return {};
87}
88void EnvironmentJS::DisposeNative()
89{
90    if (!disposed_) {
91        CORE_LOG_F("EnvironmentJS::DisposeNative");
92        disposed_ = true;
93        NapiApi::Object obj = scene_.GetObject();
94        auto* tro = obj.Native<TrueRootObject>();
95        if (tro) {
96            SceneJS* sceneJS = ((SceneJS*)tro->GetInstanceImpl(SceneJS::ID));
97            if (sceneJS) {
98                sceneJS->ReleaseStrongDispose((uintptr_t)&scene_);
99            }
100        }
101
102        diffuseFactor_.reset();
103        specularFactor_.reset();
104        environmentFactor_.reset();
105        if (auto env = interface_pointer_cast<IEnvironment>(GetNativeObject())) {
106            // reset the native object refs
107            SetNativeObject(nullptr, false);
108            SetNativeObject(nullptr, true);
109            diffuseFactor_.reset();
110            specularFactor_.reset();
111            environmentFactor_.reset();
112
113            NapiApi::Object sceneJS = scene_.GetObject();
114            if (sceneJS) {
115                napi_value null;
116                napi_get_null(sceneJS.GetEnv(), &null);
117                sceneJS.Set("environment", null);
118
119                scene_.Reset();
120                auto* tro = sceneJS.Native<TrueRootObject>();
121                IScene::Ptr scene = interface_pointer_cast<IScene>(tro->GetNativeObject());
122                ExecSyncTask([s = BASE_NS::move(scene), e = BASE_NS::move(env)]() {
123                    auto en = interface_pointer_cast<SCENE_NS::INode>(e);
124                    s->ReleaseNode(en);
125                    en.reset();
126                    return META_NS::IAny::Ptr {};
127                });
128            }
129        }
130    }
131    scene_.Reset();
132}
133void* EnvironmentJS::GetInstanceImpl(uint32_t id)
134{
135    if (id == EnvironmentJS::ID) {
136        return this;
137    }
138    return SceneResourceImpl::GetInstanceImpl(id);
139}
140void EnvironmentJS::Finalize(napi_env env)
141{
142    // hmm.. do i need to do something BEFORE the object gets deleted..
143    DisposeNative();
144    BaseObject<EnvironmentJS>::Finalize(env);
145}
146
147EnvironmentJS::EnvironmentJS(napi_env e, napi_callback_info i)
148    : BaseObject<EnvironmentJS>(e, i), SceneResourceImpl(SceneResourceImpl::ENVIRONMENT)
149{
150    LOG_F("EnvironmentJS ++");
151    NapiApi::FunctionContext<NapiApi::Object, NapiApi::Object> fromJs(e, i);
152    if (!fromJs) {
153        // no arguments. so internal create.
154        // expecting caller to finish
155        return;
156    }
157
158    scene_ = { fromJs, fromJs.Arg<0>() };
159    if (!GetNativeMeta<SCENE_NS::IScene>(scene_.GetObject())) {
160        CORE_LOG_F("INVALID SCENE!");
161    }
162
163    NapiApi::Object meJs(e, fromJs.This());
164    auto* tro = scene_.GetObject().Native<TrueRootObject>();
165    auto* sceneJS = ((SceneJS*)tro->GetInstanceImpl(SceneJS::ID));
166    sceneJS->StrongDisposeHook((uintptr_t)&scene_, meJs);
167
168    IScene::Ptr scene = interface_pointer_cast<IScene>(tro->GetNativeObject());
169
170    NapiApi::Value<BASE_NS::string> name;
171    NapiApi::Object args = fromJs.Arg<1>();
172    if (auto prm = args.Get("name")) {
173        name = NapiApi::Value<BASE_NS::string>(e, prm);
174    }
175
176    BASE_NS::string nameS = name;
177    if (nameS.empty()) {
178        // create "unique" name
179        nameS = BASE_NS::to_string((uint64_t)this);
180    }
181    IEnvironment::Ptr env = GetNativeMeta<IEnvironment>(meJs);
182    // Construct native object (if needed)
183
184    if (!env) {
185        ExecSyncTask([&env, scene, nameS]() {
186            BASE_NS::string_view n = nameS; /*nodepath actually*/
187            env = scene->CreateNode<SCENE_NS::IEnvironment>(nameS);
188            return META_NS::IAny::Ptr {};
189        });
190    }
191
192    // process constructor args
193    // weak ref, due to being owned by the scene.
194    SetNativeObject(interface_pointer_cast<META_NS::IObject>(env), false);
195    StoreJsObj(interface_pointer_cast<META_NS::IObject>(env), meJs);
196    env.reset();
197
198    if (name) {
199        // set the name of the object. if we were given one
200        meJs.Set("name", name);
201    }
202}
203
204EnvironmentJS::~EnvironmentJS()
205{
206    LOG_F("EnvironmentJS --");
207    DisposeNative();
208    if (!GetNativeObject()) {
209        return;
210    }
211}
212
213napi_value EnvironmentJS::GetBackgroundType(NapiApi::FunctionContext<>& ctx)
214{
215    uint32_t typeI = 0;
216    if (auto env = interface_cast<IEnvironment>(GetNativeObject())) {
217        ExecSyncTask([env, &typeI]() {
218            typeI = env->Background()->GetValue();
219            return META_NS::IAny::Ptr {};
220        });
221    }
222    return NapiApi::Value(ctx, (uint32_t)typeI);
223}
224
225void EnvironmentJS::SetBackgroundType(NapiApi::FunctionContext<uint32_t>& ctx)
226{
227    if (auto env = interface_cast<IEnvironment>(GetNativeObject())) {
228        uint32_t typeI = ctx.Arg<0>();
229        auto typeE = static_cast<EnvironmentBackgroundType>(typeI);
230        IEnvironment::BackgroundType type;
231        switch (typeE) {
232            case EnvironmentBackgroundType::BACKGROUND_NONE:
233                type = IEnvironment::BackgroundType::NONE;
234                break;
235            case EnvironmentBackgroundType::BACKGROUND_IMAGE:
236                type = IEnvironment::BackgroundType::IMAGE;
237                break;
238            case EnvironmentBackgroundType::BACKGROUND_CUBEMAP:
239                type = IEnvironment::BackgroundType::CUBEMAP;
240                break;
241            case EnvironmentBackgroundType::BACKGROUND_EQUIRECTANGULAR:
242                type = IEnvironment::BackgroundType::EQUIRECTANGULAR;
243                break;
244            default:
245                type = IEnvironment::BackgroundType::NONE;
246                break;
247        }
248        ExecSyncTask([env, &type]() {
249            env->Background()->SetValue(type);
250            return META_NS::IAny::Ptr {};
251        });
252    }
253}
254napi_value EnvironmentJS::GetEnvironmentImage(NapiApi::FunctionContext<>& ctx)
255{
256    if (auto environment = interface_cast<SCENE_NS::IEnvironment>(GetNativeObject())) {
257        SCENE_NS::IBitmap::Ptr image;
258        ExecSyncTask([environment, &image]() {
259            image = environment->EnvironmentImage()->GetValue();
260            return META_NS::IAny::Ptr {};
261        });
262        auto obj = interface_pointer_cast<META_NS::IObject>(image);
263
264        if (auto cached = FetchJsObj(obj)) {
265            return cached;
266        }
267
268        napi_value args[] = { scene_.GetValue(), NapiApi::Object(ctx) };
269        return CreateFromNativeInstance(ctx, obj, false, BASE_NS::countof(args), args);
270    }
271    return ctx.GetNull();
272}
273
274void EnvironmentJS::SetEnvironmentImage(NapiApi::FunctionContext<NapiApi::Object>& ctx)
275{
276    NapiApi::Object imageJS = ctx.Arg<0>();
277    SCENE_NS::IBitmap::Ptr image;
278    if (auto nat = imageJS.Native<TrueRootObject>()) {
279        image = interface_pointer_cast<SCENE_NS::IBitmap>(nat->GetNativeObject());
280    }
281    if (auto environment = interface_cast<SCENE_NS::IEnvironment>(GetNativeObject())) {
282        ExecSyncTask([environment, image]() {
283            environment->EnvironmentImage()->SetValue(image);
284            return META_NS::IAny::Ptr {};
285        });
286    }
287}
288
289napi_value EnvironmentJS::GetRadianceImage(NapiApi::FunctionContext<>& ctx)
290{
291    if (auto environment = interface_cast<SCENE_NS::IEnvironment>(GetNativeObject())) {
292        SCENE_NS::IBitmap::Ptr image;
293        ExecSyncTask([environment, &image]() {
294            image = environment->RadianceImage()->GetValue();
295            return META_NS::IAny::Ptr {};
296        });
297        auto obj = interface_pointer_cast<META_NS::IObject>(image);
298
299        if (auto cached = FetchJsObj(obj)) {
300            return cached;
301        }
302
303        napi_value args[] = { scene_.GetValue(), NapiApi::Object(ctx) };
304        return CreateFromNativeInstance(ctx, obj, false, BASE_NS::countof(args), args);
305    }
306    return ctx.GetNull();
307}
308
309void EnvironmentJS::SetRadianceImage(NapiApi::FunctionContext<NapiApi::Object>& ctx)
310{
311    NapiApi::Object imageJS = ctx.Arg<0>();
312    SCENE_NS::IBitmap::Ptr image;
313    if (auto nat = imageJS.Native<TrueRootObject>()) {
314        image = interface_pointer_cast<SCENE_NS::IBitmap>(nat->GetNativeObject());
315    }
316    if (auto environment = interface_cast<SCENE_NS::IEnvironment>(GetNativeObject())) {
317        ExecSyncTask([environment, image]() {
318            environment->RadianceImage()->SetValue(image);
319            return META_NS::IAny::Ptr {};
320        });
321    }
322}
323napi_value EnvironmentJS::GetIrradianceCoefficients(NapiApi::FunctionContext<>& ctx)
324{
325    BASE_NS::vector<BASE_NS::Math::Vec3> coeffs;
326    if (auto environment = interface_cast<SCENE_NS::IEnvironment>(GetNativeObject())) {
327        ExecSyncTask([environment, &coeffs]() {
328            coeffs = environment->IrradianceCoefficients()->GetValue();
329            return META_NS::IAny::Ptr {};
330        });
331    }
332    NapiApi::Array res(ctx, 9); // array size 9
333    size_t index = 0;
334    for (auto& v : coeffs) {
335        NapiApi::Object vec(ctx);
336        vec.Set("x", NapiApi::Value<float>(ctx, v.x));
337        vec.Set("y", NapiApi::Value<float>(ctx, v.y));
338        vec.Set("z", NapiApi::Value<float>(ctx, v.z));
339        res.Set(index++, vec);
340    }
341    return res;
342}
343void EnvironmentJS::SetIrradianceCoefficients(NapiApi::FunctionContext<NapiApi::Array>& ctx)
344{
345    NapiApi::Array coeffJS = ctx.Arg<0>();
346    if (coeffJS.Count() != 9) { // array size 9
347        // not enough elements in array
348        return;
349    }
350    BASE_NS::vector<BASE_NS::Math::Vec3> coeffs;
351    for (auto i = 0; i < coeffJS.Count(); i++) {
352        NapiApi::Object obj = coeffJS.Get<NapiApi::Object>(i);
353        if (!obj) {
354            // not an object in array
355            return;
356        }
357        auto x = obj.Get<float>("x");
358        auto y = obj.Get<float>("y");
359        auto z = obj.Get<float>("z");
360        if (!x || !y || !z) {
361            // invalid kind of object.
362            return;
363        }
364        coeffs.emplace_back((float)x, (float)y, (float)z);
365    }
366
367    if (auto environment = interface_cast<SCENE_NS::IEnvironment>(GetNativeObject())) {
368        ExecSyncTask([environment, &coeffs]() {
369            environment->IrradianceCoefficients()->SetValue(coeffs);
370            return META_NS::IAny::Ptr {};
371        });
372    }
373}
374
375napi_value EnvironmentJS::GetIndirectDiffuseFactor(NapiApi::FunctionContext<>& ctx)
376{
377    auto node = interface_pointer_cast<SCENE_NS::IEnvironment>(GetThisNativeObject(ctx));
378    if (!node) {
379        return ctx.GetUndefined();
380    }
381    if (diffuseFactor_ == nullptr) {
382        diffuseFactor_ = BASE_NS::make_unique<Vec4Proxy>(ctx, node->IndirectDiffuseFactor());
383    }
384    return *diffuseFactor_;
385}
386
387void EnvironmentJS::SetIndirectDiffuseFactor(NapiApi::FunctionContext<NapiApi::Object>& ctx)
388{
389    auto node = interface_pointer_cast<SCENE_NS::IEnvironment>(GetThisNativeObject(ctx));
390    if (!node) {
391        return;
392    }
393    NapiApi::Object obj = ctx.Arg<0>();
394    if (diffuseFactor_ == nullptr) {
395        diffuseFactor_ = BASE_NS::make_unique<Vec4Proxy>(ctx, node->IndirectDiffuseFactor());
396    }
397    diffuseFactor_->SetValue(obj);
398}
399
400napi_value EnvironmentJS::GetIndirectSpecularFactor(NapiApi::FunctionContext<>& ctx)
401{
402    auto node = interface_pointer_cast<SCENE_NS::IEnvironment>(GetThisNativeObject(ctx));
403    if (!node) {
404        return ctx.GetUndefined();
405    }
406    if (specularFactor_ == nullptr) {
407        specularFactor_ = BASE_NS::make_unique<Vec4Proxy>(ctx, node->IndirectSpecularFactor());
408    }
409    return *specularFactor_;
410}
411
412void EnvironmentJS::SetIndirectSpecularFactor(NapiApi::FunctionContext<NapiApi::Object>& ctx)
413{
414    auto node = interface_pointer_cast<SCENE_NS::IEnvironment>(GetThisNativeObject(ctx));
415    if (!node) {
416        return;
417    }
418    NapiApi::Object obj = ctx.Arg<0>();
419    if (specularFactor_ == nullptr) {
420        specularFactor_ = BASE_NS::make_unique<Vec4Proxy>(ctx, node->IndirectSpecularFactor());
421    }
422    specularFactor_->SetValue(obj);
423}
424
425napi_value EnvironmentJS::GetEnvironmentMapFactor(NapiApi::FunctionContext<>& ctx)
426{
427    auto node = interface_pointer_cast<SCENE_NS::IEnvironment>(GetThisNativeObject(ctx));
428    if (!node) {
429        return ctx.GetUndefined();
430    }
431    if (environmentFactor_ == nullptr) {
432        environmentFactor_ = BASE_NS::make_unique<Vec4Proxy>(ctx, node->EnvMapFactor());
433    }
434    return *environmentFactor_;
435}
436
437void EnvironmentJS::SetEnvironmentMapFactor(NapiApi::FunctionContext<NapiApi::Object>& ctx)
438{
439    auto node = interface_pointer_cast<SCENE_NS::IEnvironment>(GetThisNativeObject(ctx));
440    if (!node) {
441        return;
442    }
443    NapiApi::Object obj = ctx.Arg<0>();
444    if (environmentFactor_ == nullptr) {
445        environmentFactor_ = BASE_NS::make_unique<Vec4Proxy>(ctx, node->EnvMapFactor());
446    }
447    environmentFactor_->SetValue(obj);
448}
449