feat(web): add scene multiview authoring
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-29 20:17:50 +08:00
parent 217ab7cfe9
commit acf4d3d3d0
46 changed files with 1939 additions and 36 deletions

View File

@@ -1592,7 +1592,7 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
type == "insertGreasePencilFrame" || type == "removeGreasePencilFrame" ||
type == "setGreasePencilStrokes" || type == "setVertexColors" ||
type == "setVertexWeights" || type == "setCameraProperties" || type == "setLightProperties" ||
type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings" ||
type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings" || type == "setSceneMultiviewSettings" ||
type == "setSculptMeshAttributes" ||
type == "sculptStroke") {
if (!engine->authoritative_commands || engine->authoritative_main == nullptr) {
@@ -2196,7 +2196,7 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
command.value("normalize", false), command.value("limit", 0u), command.value("mirror", false),
command.value("mirrorAxis", 0), command.value("mirrorTolerance", 1e-4f), main_error);
}
else if (type == "setCameraProperties" || type == "setLightProperties" || type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings")
else if (type == "setCameraProperties" || type == "setLightProperties" || type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings" || type == "setSceneMultiviewSettings")
{
const json properties = command.value("properties", json::object());
if (type == "setSceneProperties") {
@@ -2290,6 +2290,11 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
engine->authoritative_main, command.value("dataId", "").c_str(),
properties.dump().c_str(), main_error);
}
else if (type == "setSceneMultiviewSettings") {
applied = web_engine_blend_main_set_scene_multiview_settings(
engine->authoritative_main, command.value("dataId", "").c_str(),
properties.dump().c_str(), main_error);
}
else {
applied = web_engine_blend_main_set_render_properties(
engine->authoritative_main, command.value("dataId", "").c_str(),

View File

@@ -5290,6 +5290,22 @@ json scene_ir_from_blend(const ParsedBlend &blend,
{"pixelAspectY", read_float(*blend.sdna, *render, "yasp").value_or(1.0f)},
{"filterSize", read_float(*blend.sdna, *render, "gauss").value_or(1.5f)},
{"dither", read_float(*blend.sdna, *render, "dither_intensity").value_or(1.0f)}};
if (const std::optional<ElementRef> image_format = embedded_element(*blend.sdna, *render, "im_format")) {
const int64_t image_views = read_integer(*blend.sdna, *image_format, "views_format").value_or(R_IMF_VIEWS_INDIVIDUAL);
int64_t stereo_flags = 0;
int64_t display_mode = S3D_DISPLAY_ANAGLYPH;
if (const std::optional<ElementRef> stereo = embedded_element(*blend.sdna, *image_format, "stereo3d_format")) {
stereo_flags = read_integer(*blend.sdna, *stereo, "flag").value_or(0);
display_mode = read_integer(*blend.sdna, *stereo, "display_mode").value_or(S3D_DISPLAY_ANAGLYPH);
}
scene["multiviewSettings"] = {{"enabled", (render_mode & R_MULTIVIEW) != 0},
{"viewsFormat", read_integer(*blend.sdna, *render, "views_format").value_or(SCE_VIEWS_FORMAT_STEREO_3D)},
{"imageViewsFormat", image_views},
{"stereoDisplayMode", display_mode},
{"interlaceSwap", (stereo_flags & S3D_INTERLACE_SWAP) != 0},
{"sideBySideCrossed", (stereo_flags & S3D_SIDEBYSIDE_CROSSEYED) != 0},
{"squeezedFrame", (stereo_flags & S3D_SQUEEZED_FRAME) != 0}};
}
scene["timelineSettings"] = {{"fps", read_integer(*blend.sdna, *render, "frs_sec").value_or(24)},
{"fpsBase", read_float(*blend.sdna, *render, "frs_sec_base").value_or(1.0f)},
{"frameStep", read_integer(*blend.sdna, *render, "frame_step").value_or(1)},

View File

@@ -6577,6 +6577,48 @@ bool web_engine_blend_main_set_scene_sampling_settings(WebBlendMainState *state,
return true;
}
bool web_engine_blend_main_set_scene_multiview_settings(WebBlendMainState *state,
const char *target_id,
const char *properties_json,
std::string &error)
{
if (state == nullptr || state->main == nullptr || target_id == nullptr || properties_json == nullptr) { error = "RENDER_PROPERTY_INVALID: scene multiview target is missing"; return false; }
Scene *scene = find_scene(state->main, target_id);
if (scene == nullptr || scene->id.lib != nullptr) { error = "LINKED_DATA_MUTATION_BLOCKED: Scene is not editable"; return false; }
const json properties = json::parse(properties_json, nullptr, false);
if (!properties.is_object() || properties.empty()) { error = "RENDER_PROPERTY_INVALID: scene multiview settings must be a non-empty object"; return false; }
const std::unordered_set<std::string> allowed = {"enabled", "viewsFormat", "imageViewsFormat", "stereoDisplayMode", "interlaceSwap", "sideBySideCrossed", "squeezedFrame"};
for (const auto &[key, value] : properties.items()) { (void)value; if (!allowed.contains(key)) { error = "RENDER_PROPERTY_INVALID: unsupported multiview property " + key; return false; } }
auto read_int = [&](const char *key, int minimum, int maximum, int &target) {
if (!properties.contains(key)) return true;
if (!properties[key].is_number_integer()) { error = std::string("RENDER_PROPERTY_INVALID: ") + key + " must be an integer"; return false; }
const int value = properties[key].get<int>();
if (value < minimum || value > maximum) { error = std::string("RENDER_PROPERTY_INVALID: ") + key + " is outside the bounded range"; return false; }
target = value; return true;
};
int views_format = scene->r.views_format;
int image_views_format = scene->r.im_format.views_format;
int display_mode = scene->r.im_format.stereo3d_format.display_mode;
if (!read_int("viewsFormat", 0, 1, views_format) || !read_int("imageViewsFormat", 0, 2, image_views_format) || !read_int("stereoDisplayMode", 0, 4, display_mode)) return false;
scene->r.views_format = eSceneViews_Format(views_format);
scene->r.im_format.views_format = eImageFormat_ViewsFormat(image_views_format);
scene->r.im_format.stereo3d_format.display_mode = eStereoDisplayMode(display_mode);
const auto read_stereo_flag = [&](const char *key, eStereo3dFlag flag) {
if (!properties.contains(key)) return true;
if (!properties[key].is_boolean()) { error = std::string("RENDER_PROPERTY_INVALID: ") + key + " must be boolean"; return false; }
if (properties[key].get<bool>()) scene->r.im_format.stereo3d_format.flag |= flag;
else scene->r.im_format.stereo3d_format.flag &= ~flag;
return true;
};
if (!read_stereo_flag("interlaceSwap", S3D_INTERLACE_SWAP) || !read_stereo_flag("sideBySideCrossed", S3D_SIDEBYSIDE_CROSSEYED) || !read_stereo_flag("squeezedFrame", S3D_SQUEEZED_FRAME)) return false;
if (properties.contains("enabled")) {
if (!properties["enabled"].is_boolean()) { error = "RENDER_PROPERTY_INVALID: enabled must be boolean"; return false; }
if (properties["enabled"].get<bool>()) scene->r.mode |= R_MULTIVIEW; else scene->r.mode &= ~R_MULTIVIEW;
}
scene->id.recalc |= ID_RECALC_PARAMETERS;
return true;
}
bool web_engine_blend_main_write(WebBlendMainState *state,
std::vector<uint8_t> &bytes,
std::string &error)

View File

@@ -591,6 +591,10 @@ bool web_engine_blend_main_set_scene_sampling_settings(WebBlendMainState *state,
const char *target_id,
const char *properties_json,
std::string &error);
bool web_engine_blend_main_set_scene_multiview_settings(WebBlendMainState *state,
const char *target_id,
const char *properties_json,
std::string &error);
bool web_engine_blend_main_write(WebBlendMainState *state,
std::vector<uint8_t> &bytes,
std::string &error);