Files
oak-editor/crates/oaknode/src/nodes/noise.rs
T
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- oakengine_sequence_move_clip implemented for real (oaktimeline
  TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
  trim bugs the stub was hiding); same-track via the frozen C ABI,
  cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
  effect_input to it, so timeline clips can host effect chains; facade
  test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
  Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
  dav1d Cellar path, breaking links); reads a git-ignored workspace
  .env for IDEs that cannot inject env vars (RustRover); links the C++
  stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
  it_node uses the direct instance's value type where it calls the
  module FFI (the --workspace dev-dependency feature split builds
  oaknode twice)
2026-08-11 23:04:48 +08:00

288 lines
8.5 KiB
Rust

// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! Noise generator (C++ `src/node/src/generator/noise/noise.{h,cpp}`,
//! `olive::NoiseGeneratorNode`).
use crate::factory::NodeMeta;
use crate::node::{Category, NodeBehavior, NodeCore};
/// Base texture input id (C++ `k_base_in`). Type: texture; flags:
/// not-keyframable; this is the node's effect input.
pub const BASE_INPUT: &str = "base_in";
/// Color noise toggle input id (C++ `k_color_input`). Type: bool;
/// default `false`.
pub const COLOR_INPUT: &str = "color_in";
/// Noise strength input id (C++ `k_strength_input`). Type: float;
/// default `0.2`; properties: `view = percentage`, `min = 0`.
pub const STRENGTH_INPUT: &str = "strength_in";
/// Noise generator node. Adds gold-noise to an optional base texture
/// (or generates it standalone). Has no own member fields in C++.
pub struct NoiseGeneratorNode;
/// Fragment shader (C++ loads the `:/shaders/noise.frag` resource in
/// `get_shader_code`). Text copied verbatim from
/// `engine/shaders/noise.frag`.
const SHADER_FRAG: &str = r#"uniform float time_in;
uniform float strength_in;
uniform bool color_in;
uniform sampler2D base_in;
uniform bool base_in_enabled;
in vec2 ove_texcoord;
out vec4 frag_color;
float PHI = 1.61803398874989484820459 * 00000.1; // Golden Ratio
float PI = 3.14159265358979323846264 * 00000.1; // PI
float SQ2 = 1.41421356237309504880169 * 10000.0; // Square Root of Two
bool isNan( float val )
{
return ( val < 0.0 || 0.0 < val || val == 0.0 ) ? false : true;
// important: some nVidias failed to cope with version below.
// Probably wrong optimization.
/*return ( val <= 0.0 || 0.0 <= val ) ? false : true;*/
// Taken from: https://stackoverflow.com/questions/11810158/how-to-deal-with-nan-or-inf-in-opengl-es-2-0-shaders
}
float gold_noise(vec2 coordinate, float seed){
float value = fract(tan(distance(coordinate*(seed+PHI), vec2(PHI, PI)))*SQ2)*(strength_in);
return isNan(value) ? 0.0 : value;
}
void main(void) {
vec3 noise;
if (color_in) {
noise = vec3(gold_noise(ove_texcoord, time_in + 42069.0), gold_noise(ove_texcoord, time_in + 69220.0), gold_noise(ove_texcoord, time_in + 1337.0));
} else {
noise = vec3(gold_noise(ove_texcoord, time_in + 69420.0));
}
if (base_in_enabled) {
vec4 base = texture(base_in, ove_texcoord);
base.rgb += noise;
frag_color = base;
} else {
frag_color = vec4(noise, 1.0);
}
}
"#;
impl NoiseGeneratorNode {
/// Fragment shader for all shader requests (C++
/// `get_shader_code()` ignores the request id).
fn shader_frag() -> &'static str {
SHADER_FRAG
}
}
impl NodeBehavior for NoiseGeneratorNode {
/// Human-readable name (C++ `name()`).
fn name(&self) -> &str {
"Noise"
}
/// Stable type id (C++ `id()`).
fn type_id(&self) -> &str {
"org.olivevideoeditor.Olive.noise"
}
/// Categories (C++ `category()`).
fn categories(&self) -> &[Category] {
&[Category::Generator]
}
/// Description (C++ `description()`).
fn description(&self) -> &str {
"Generates noise patterns"
}
/// Localized input names (C++ `retranslate()`): `base_in` ->
/// "Base", `strength_in` -> "Strength", `color_in` -> "Color".
fn input_name<'a>(&self, id: &'a str) -> &'a str {
match id {
BASE_INPUT => "Base",
STRENGTH_INPUT => "Strength",
COLOR_INPUT => "Color",
_ => id,
}
}
/// Evaluate outputs (C++ `value()`): builds a shader job from the
/// input row, additionally inserting `time_in` (current time in
/// seconds as a float), then pushes a texture job using the base
/// texture's params when connected, else the sequence video params.
///
/// The Rust model has no shader-job payload: the job (including the
/// `time_in` value) is deferred to the renderer seam, so a null
/// texture handle marks "renderer must produce this texture"
/// (`// CPP-PARITY: noise.cpp` value()).
fn value(
&self,
core: &NodeCore,
inputs: &crate::value::NodeValueRow,
time: oakcore_rs::Rational,
table: &mut crate::value::NodeValueTable,
) {
// C++ always pushes a job — with a base texture connected it runs
// at the base's params, otherwise at the sequence params.
let _ = (core, inputs, time);
table.push(
crate::value::ValueType::Texture,
crate::value::NodeValue::Texture(crate::handle::CHandle::null()),
None,
);
}
/// Shader code request (C++ `get_shader_code()`): returns the
/// noise fragment shader for any request id.
fn shader_code(&self, _request: &str) -> Option<String> {
Some(Self::shader_frag().to_string())
}
/// Deep copy (C++ `copy()`).
fn duplicate(&self, _core: &NodeCore) -> Option<Box<dyn NodeBehavior>> {
Some(Box::new(NoiseGeneratorNode))
}
}
/// Constructor (C++ `NoiseGeneratorNode::NoiseGeneratorNode()`): adds
/// `base_in`, `strength_in` and `color_in` with the defaults, flags and
/// properties documented on the constants, sets the video-effect flag
/// and makes `base_in` the effect input.
pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
let mut core = NodeCore::new();
let mut base = crate::input::Input::new(
BASE_INPUT,
crate::value::ValueType::Texture,
crate::value::NodeValue::None,
);
base.flags |= crate::input::flags::NOT_KEYFRAMABLE;
core.add_input(base);
let mut strength = crate::input::Input::new(
STRENGTH_INPUT,
crate::value::ValueType::Float,
crate::value::NodeValue::Float(0.2),
);
strength.properties = vec![
(
"view".to_string(),
crate::value::NodeValue::Text("percentage".into()),
),
("min".to_string(), crate::value::NodeValue::Float(0.0)),
];
core.add_input(strength);
core.add_input(crate::input::Input::new(
COLOR_INPUT,
crate::value::ValueType::Boolean,
crate::value::NodeValue::Boolean(false),
));
core.flags |= crate::node::flags::VIDEO_EFFECT;
core.effect_input = BASE_INPUT.to_string();
(core, Box::new(NoiseGeneratorNode))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::NodeBehavior;
use crate::value::{NodeValue, NodeValueTable, ValueType};
use oakcore_rs::Rational;
#[test]
fn input_names() {
let n = NoiseGeneratorNode;
assert_eq!(n.input_name(BASE_INPUT), "Base");
assert_eq!(n.input_name(STRENGTH_INPUT), "Strength");
assert_eq!(n.input_name(COLOR_INPUT), "Color");
}
#[test]
fn create_wires_inputs_and_flags() {
let (core, behavior) = create();
assert_eq!(behavior.type_id(), "org.olivevideoeditor.Olive.noise");
assert_eq!(
core.get_input(STRENGTH_INPUT).unwrap().default,
NodeValue::Float(0.2)
);
assert_eq!(
core.get_input(COLOR_INPUT).unwrap().default,
NodeValue::Boolean(false)
);
assert_eq!(core.effect_input, BASE_INPUT);
assert_ne!(core.flags & crate::node::flags::VIDEO_EFFECT, 0);
}
#[test]
fn value_always_pushes_deferred_job() {
let (core, behavior) = create();
let mut table = NodeValueTable::default();
behavior.value(
&core,
&crate::value::NodeValueRow::default(),
Rational::new(0, 1),
&mut table,
);
assert!(table.get(ValueType::Texture).is_some());
// With a base texture connected the job is still pushed.
let inputs = crate::value::NodeValueRow::from([(
BASE_INPUT.to_string(),
NodeValue::Texture(crate::handle::CHandle::null()),
)]);
let mut table = NodeValueTable::default();
behavior.value(&core, &inputs, Rational::new(0, 1), &mut table);
assert!(table.get(ValueType::Texture).is_some());
}
#[test]
fn shader_code_returns_noise_shader() {
let n = NoiseGeneratorNode;
let code = n.shader_code("anything").unwrap();
assert!(code.contains("uniform float time_in;"));
assert!(code.contains("gold_noise"));
}
#[test]
fn duplicate_clones() {
let (core, behavior) = create();
let dup = behavior.duplicate(&core).unwrap();
assert_eq!(dup.name(), "Noise");
}
}
/// Register this node type (C++ factory entry for
/// `org.olivevideoeditor.Olive.noise`).
pub fn register(meta: &mut Vec<NodeMeta>) {
meta.push(NodeMeta {
type_id: "org.olivevideoeditor.Olive.noise",
name: "Noise",
categories: &[Category::Generator],
create,
});
}