Skip to main content

Simple Flower

OCCT category icon with a stylized logo representation

Creating organic, flower-like forms demonstrates the power of parametric design in generating natural-looking geometry through mathematical operations. This tutorial explores how to build a simple flower using curve interpolation, mirroring, and rotational patterns—fundamental techniques that can be applied to create complex botanical forms, decorative elements, and artistic sculptures.

The flower design process combines several key concepts: defining control points for smooth curves, using symmetry operations to create balanced shapes, and employing rotational arrays to generate repetitive patterns. These techniques are essential for parametric art, architectural ornamentation, and procedural modeling applications.

Understanding the Flower Construction Process

Our approach breaks down flower creation into logical steps that mirror how natural flowers develop. We start with a single petal defined by a curved path, create symmetry through mirroring, add thickness to make it solid, then rotate copies around a central axis to form the complete flower.

This method is highly parametric—by adjusting the control points, rotation angles, or thickness values, you can quickly generate different flower variations. The same principles apply to creating other organic forms like leaves, shells, or abstract sculptural elements.

Bitbybit Platform

Simple flower

rete logoRete
Script Source (rete)
{
"id": "rete-v2-json",
"nodes": {
"8faa0c5fb9823237": {
"id": "8faa0c5fb9823237",
"name": "bitbybit.occt.shapes.wire.interpolatePoints",
"customName": "interpolate points",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"periodic": false,
"tolerance": 1e-7
},
"inputs": {
"points": {
"connections": [
{
"node": "e2e3d3aed9655ab6",
"output": "list",
"data": {}
}
]
}
},
"position": [
1287.0687813345048,
884.939567373176
]
},
"97cab9b66d410996": {
"id": "97cab9b66d410996",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 0,
"z": 0
},
"inputs": {},
"position": [
424.1404203359041,
394.04416440848064
]
},
"e2e3d3aed9655ab6": {
"id": "e2e3d3aed9655ab6",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "97cab9b66d410996",
"output": "result",
"data": {}
},
{
"node": "4382c18392c59a98",
"output": "result",
"data": {}
},
{
"node": "cf14d0a8051dabea",
"output": "result",
"data": {}
},
{
"node": "745a0565f9beb72a",
"output": "result",
"data": {}
}
]
}
},
"position": [
874.9262222684938,
923.4840379184922
]
},
"4382c18392c59a98": {
"id": "4382c18392c59a98",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 2,
"y": 0.5,
"z": 3
},
"inputs": {},
"position": [
423.3145944258628,
724.8011906965236
]
},
"cf14d0a8051dabea": {
"id": "cf14d0a8051dabea",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 1.1,
"y": -0.5,
"z": 7
},
"inputs": {},
"position": [
422.44718765175037,
1061.248952488166
]
},
"745a0565f9beb72a": {
"id": "745a0565f9beb72a",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 0,
"y": 0,
"z": 10
},
"inputs": {},
"position": [
422.8305905803102,
1394.5807382128428
]
},
"de9b5212dc1c2b72": {
"id": "de9b5212dc1c2b72",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 1,
"y": 0,
"z": 0
},
"inputs": {},
"position": [
1287.765098594874,
1388.7927624811502
]
},
"7988f97ff6647362": {
"id": "7988f97ff6647362",
"name": "bitbybit.occt.transforms.mirrorAlongNormal",
"customName": "mirror along normal",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"origin": [
0,
0,
0
],
"normal": [
0,
0,
1
]
},
"inputs": {
"shape": {
"connections": [
{
"node": "8faa0c5fb9823237",
"output": "result",
"data": {}
}
]
},
"normal": {
"connections": [
{
"node": "de9b5212dc1c2b72",
"output": "result",
"data": {}
}
]
}
},
"position": [
1731.6327597720638,
1082.62186661138
]
},
"a0d1cd554f66bf6d": {
"id": "a0d1cd554f66bf6d",
"name": "bitbybit.occt.shapes.wire.combineEdgesAndWiresIntoAWire",
"customName": "combine edges and wires into a wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"shapes": {
"connections": [
{
"node": "2f9a1265ead7733c",
"output": "list",
"data": {}
}
]
}
},
"position": [
2506.2324269417245,
883.6264712168129
]
},
"2f9a1265ead7733c": {
"id": "2f9a1265ead7733c",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "8faa0c5fb9823237",
"output": "result",
"data": {}
},
{
"node": "7988f97ff6647362",
"output": "result",
"data": {}
}
]
}
},
"position": [
2130.2179761064363,
924.030122309714
]
},
"ef3231701a568119": {
"id": "ef3231701a568119",
"name": "bitbybit.occt.shapes.face.createFaceFromWire",
"customName": "face from wire",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"planar": false
},
"inputs": {
"shape": {
"connections": [
{
"node": "a0d1cd554f66bf6d",
"output": "result",
"data": {}
}
]
}
},
"position": [
2883.5180269634725,
884.2787678537645
]
},
"87dc506a4f7281a0": {
"id": "87dc506a4f7281a0",
"name": "bitbybit.occt.operations.makeThickSolidSimple",
"customName": "make thick solid simple",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"offset": 0.1
},
"inputs": {
"shape": {
"connections": [
{
"node": "ef3231701a568119",
"output": "result",
"data": {}
}
]
}
},
"position": [
3249.36708265849,
883.5998482581062
]
},
"e8b98529014f2c04": {
"id": "e8b98529014f2c04",
"name": "bitbybit.occt.transforms.rotate",
"customName": "rotate",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"axis": [
0,
0,
1
],
"angle": 0
},
"inputs": {
"shape": {
"connections": [
{
"node": "87dc506a4f7281a0",
"output": "result",
"data": {}
}
]
},
"axis": {
"connections": [
{
"node": "98e745a3a45c3a36",
"output": "result",
"data": {}
}
]
},
"angle": {
"connections": [
{
"node": "7df432707a86eea8",
"output": "result",
"data": {}
}
]
}
},
"position": [
3956.179691345161,
1530.280959502406
]
},
"98e745a3a45c3a36": {
"id": "98e745a3a45c3a36",
"name": "bitbybit.vector.vectorXYZ",
"customName": "vector xyz",
"async": false,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"x": 2,
"y": 2,
"z": 0
},
"inputs": {},
"position": [
3115.161410801174,
1416.7869359779625
]
},
"3403bfdce4eb8b84": {
"id": "3403bfdce4eb8b84",
"name": "bitbybit.vector.span",
"customName": "span",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"step": 30,
"min": 0,
"max": 360
},
"inputs": {},
"position": [
3051.5172324998534,
1747.8824123854959
]
},
"7df432707a86eea8": {
"id": "7df432707a86eea8",
"name": "bitbybit.lists.flatten",
"customName": "flatten",
"data": {
"nrLevels": 1
},
"inputs": {
"list": {
"connections": [
{
"node": "3403bfdce4eb8b84",
"output": "result",
"data": {}
}
]
}
},
"position": [
3427.2646774866294,
1781.382012022833
]
},
"15c360f697b19def": {
"id": "15c360f697b19def",
"name": "bitbybit.babylon.scene.drawDirectionalLight",
"customName": "draw directional light",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"direction": [
-100,
-100,
-100
],
"intensity": 3,
"diffuse": "#ffffff",
"specular": "#ffffff",
"shadowGeneratorMapSize": 1024,
"enableShadows": true,
"shadowDarkness": 0,
"shadowUsePercentageCloserFiltering": true,
"shadowContactHardeningLightSizeUVRatio": 0.2,
"shadowBias": 0.0001,
"shadowNormalBias": 0.002,
"shadowMaxZ": 1000,
"shadowMinZ": 0
},
"inputs": {},
"position": [
2466.096861954312,
2128.5288188674936
]
},
"f851cdb292b8669c": {
"id": "f851cdb292b8669c",
"name": "bitbybit.babylon.scene.enableSkybox",
"customName": "enable skybox",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"skybox": "city",
"size": 1000,
"blur": 0.1,
"environmentIntensity": 0.7,
"hideSkybox": true
},
"inputs": {},
"position": [
2016.1735647375663,
2241.8369412610614
]
},
"bf2c2c89c14729af": {
"id": "bf2c2c89c14729af",
"name": "bitbybit.babylon.scene.twoColorLinearGradient",
"customName": "two color linear gradient",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"colorFrom": "#090a0b",
"colorTo": "#a4f9ef",
"direction": "to top",
"stopFrom": 0,
"stopTo": 100
},
"inputs": {},
"position": [
1665.8642745313932,
2241.0512080018066
]
},
"73cff4b3dac9ba74": {
"id": "73cff4b3dac9ba74",
"name": "bitbybit.occt.shapes.compound.makeCompound",
"customName": "make compound",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": true,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"shapes": {
"connections": [
{
"node": "3f7da96395d0cc0a",
"output": "list",
"data": {}
}
]
}
},
"position": [
4723.317347193844,
1529.5584313727202
]
},
"3f7da96395d0cc0a": {
"id": "3f7da96395d0cc0a",
"name": "bitbybit.lists.createList",
"customName": "create list",
"data": {},
"inputs": {
"listElements": {
"connections": [
{
"node": "e8b98529014f2c04",
"output": "result",
"data": {}
}
]
}
},
"position": [
4336.374658712047,
1569.7875970111186
]
},
"82dc30294f5ac9bf": {
"id": "82dc30294f5ac9bf",
"name": "bitbybit.draw.drawAnyAsync",
"customName": "draw any async",
"async": true,
"drawable": true,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
}
},
"inputs": {
"entity": {
"connections": [
{
"node": "73cff4b3dac9ba74",
"output": "result",
"data": {}
}
]
},
"options": {
"connections": [
{
"node": "b23a7af9c08392c7",
"output": "result",
"data": {}
}
]
}
},
"position": [
5349.600277496398,
1822.4770208653674
]
},
"b23a7af9c08392c7": {
"id": "b23a7af9c08392c7",
"name": "bitbybit.draw.optionsOcctShapeMaterial",
"customName": "options occt shape material",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"precision": 0.01,
"drawEdges": true,
"edgeColour": "#000000",
"edgeWidth": 2
},
"inputs": {
"faceMaterial": {
"connections": [
{
"node": "65dacf9cbbadd3c8",
"output": "result",
"data": {}
}
]
}
},
"position": [
4845.401993283481,
2112.9450605638203
]
},
"65dacf9cbbadd3c8": {
"id": "65dacf9cbbadd3c8",
"name": "bitbybit.babylon.material.pbrMetallicRoughness.create",
"customName": "pbr metallic roughness",
"async": false,
"drawable": false,
"data": {
"genericNodeData": {
"hide": false,
"oneOnOne": false,
"flatten": 0,
"forceExecution": false
},
"name": "Custom Material",
"baseColor": "#14ffa5",
"emissiveColor": "#000000",
"metallic": 0.6,
"roughness": 0.6,
"alpha": 1,
"backFaceCulling": false,
"zOffset": 2
},
"inputs": {},
"position": [
4481.373113348705,
2151.144219076012
]
}
}
}

Code Explanation

The flower creation process follows a systematic approach that demonstrates several important geometric modeling concepts:

Petal Shape Definition

The flower begins with four carefully chosen control points that define the basic petal profile:

  • Start point [0, 0, 0] at the flower center
  • Intermediate points [2, 0.5, 3] and [1.1, -0.5, 7] that control petal width and curvature
  • End point [0, 0, 10] at the petal tip

These points are connected using interpolatePoints, which creates a smooth B-spline curve through the control points. The periodic: false setting ensures the curve doesn't loop back on itself, while the small tolerance value 1e-7 ensures high precision in the curve calculation.

Symmetry Through Mirroring

To create a realistic petal shape, the curve is mirrored along the X-axis using mirrorAlongNormal. This creates a symmetrical petal with both sides having identical but opposite curvature. The mirroring operation is fundamental in creating balanced, natural-looking organic forms.

Wire Combination and Solid Creation

The original and mirrored curves are combined using combineEdgesAndWiresIntoAWire to form a closed profile. This closed wire is then converted to a face using createFaceFromWire, and finally given thickness using makeThickSolidSimple with a 0.1 unit offset.

Rotational Pattern Generation

The complete flower emerges through rotational multiplication. The vector.span function generates 12 evenly spaced angles from 0° to 360° in 30° increments. Each petal is rotated around the axis [2, 2, 0], which creates a slight tilt that adds visual interest to the final form.

Implementation Differences

Rete Version: Uses visual node connections and automatic list flattening to handle the rotation angles. The visual interface makes it easy to see data flow and adjust parameters through node properties.

Blockly Version: Employs standard programming loops since flatten operations aren't available. Variables and list management follow traditional programming patterns, making it accessible to users familiar with block-based coding.

TypeScript Version: Provides full type safety with proper DTO usage and async/await patterns. This version is most suitable for production environments and offers the best development experience with IntelliSense and error checking.

Advanced Interactive Enhancement

The example below demonstrates how to extend static geometric models with dynamic visual effects. By combining the flower geometry with a sophisticated particle system, we create an immersive interactive experience where thousands of particles emit from the flower surface and respond to mouse movement with realistic physics.

This advanced implementation showcases several cutting-edge techniques:

Surface-Based Particle Emission

Rather than emitting particles from simple points or volumes, this system calculates the surface area of each triangle in the flower mesh and distributes particles proportionally. This creates a natural, organic emission pattern that follows the flower's complex geometry perfectly.

Custom Physics Simulation

The particle system implements realistic physics including:

  • Gravity Effects: Particles fall naturally with customizable gravitational pull
  • Drag Forces: Air resistance slows particles over time for realistic motion
  • Mouse Repulsion: Interactive forces push particles away from cursor position
  • Drift Behavior: Some particles follow random drift patterns for organic movement

Multi-Layered Visual Design

The flower uses multiple overlapping layers rotated at different angles to create depth and visual richness. Combined with translucent materials and additive blending, this produces an ethereal, luminous effect.

Performance Optimization

Despite managing up to 25,000 particles simultaneously, the system maintains smooth performance through efficient update functions and memory management techniques.

Move your mouse over the flower to interact with the particle field:

Bitbybit Platform

Simple flower

typescript logoTypescript
Script Source (typescript)
// Import required DTOs for creating curves, shapes, and operations
const { InterpolationDto, MirrorAlongNormalDto, ShapesDto, CompoundShapesDto,
FaceFromWireDto, ThisckSolidSimpleDto, RotateDto } = Bit.Inputs.OCCT;
const { SceneTwoColorLinearGradientDto, DirectionalLightDto } = Bit.Inputs.BabylonScene;
const { gradientDirectionEnum } = Bit.Inputs.Base;

// Import type definitions for type safety
type Point3 = Bit.Inputs.Base.Point3;
type Vector3 = Bit.Inputs.Base.Vector3;
type TopoDSWirePointer = Bit.Inputs.OCCT.TopoDSWirePointer;
type TopoDSFacePointer = Bit.Inputs.OCCT.TopoDSFacePointer;
type TopoDSShapePointer = Bit.Inputs.OCCT.TopoDSShapePointer;

interface Particle extends BABYLON.Particle {
isLarge: boolean,
isDrifter: boolean,
driftDirection: BABYLON.Vector3
}

// Get access to OCCT modules and utility functions
const { wire, face, compound } = bitbybit.occt.shapes;
const { operations, transforms } = bitbybit.occt;
const { vector } = bitbybit;
const { scene } = bitbybit.babylon;
const { pbrMetallicRoughness } = bitbybit.babylon.material;


const createEnvironment = async () => {
const dirLightOpt = new DirectionalLightDto();
dirLightOpt.intensity = 3;
scene.drawDirectionalLight(dirLightOpt);
}

// Define the main function to create a parametric flower
const start = async () => {
createEnvironment();
// Flower parameters - easily adjustable for different designs
const thickness = 0.1; // Thickness of the flower petals
const rotationStep = 60; // Degrees between each petal (12 petals total)
const rotationAxis: Vector3 = [2, 2, 0]; // Axis for petal rotation

// Define control points for the flower petal curve
const controlPoints: Point3[] = [
[0, 0, 0], // Start point (flower center)
[5, 1.25, 7.5], // First control point (petal width)
[2.75, -1.25, 17.5], // Second control point (petal curve)
[0, 0, 25] // End point (petal tip)
];

// Create interpolated curve through control points
const curveOptions = new InterpolationDto();
curveOptions.points = controlPoints;
curveOptions.periodic = false;
curveOptions.tolerance = 1e-7;
const petalCurve = await wire.interpolatePoints(curveOptions);

// Mirror the curve to create symmetry for the petal
const mirrorOptions = new MirrorAlongNormalDto<TopoDSWirePointer>();
mirrorOptions.shape = petalCurve;
mirrorOptions.origin = [0, 0, 0];
mirrorOptions.normal = [1, 0, 0]; // Mirror along X-axis
const mirroredCurve = await transforms.mirrorAlongNormal(mirrorOptions);

// Combine the original and mirrored curves into a single wire
const combineOptions = new ShapesDto<TopoDSWirePointer>();
combineOptions.shapes = [petalCurve, mirroredCurve];
const combinedWire = await wire.combineEdgesAndWiresIntoAWire(combineOptions);

// Create a face from the combined wire
const faceOptions = new FaceFromWireDto<TopoDSWirePointer>();
faceOptions.shape = combinedWire;
faceOptions.planar = false; // Allow non-planar surface
const petalFace = await face.createFaceFromWire(faceOptions);

// Create a thick solid from the face
const thickOptions = new ThisckSolidSimpleDto<TopoDSFacePointer>();
thickOptions.shape = petalFace;
thickOptions.offset = thickness;
const thickPetal = await operations.makeThickSolidSimple(thickOptions);

// Generate rotation angles for petals (0° to 360° in steps)
const rotationAngles = vector.span({
min: 0,
max: 360,
step: rotationStep
});

const rotationAngles2 = vector.span({
min: 30,
max: 360,
step: rotationStep
});

// Create all flower petals by rotating the base petal
const flowerPetalPromises: Promise<TopoDSShapePointer>[] = [];
const flowerPetalPromises2: Promise<TopoDSShapePointer>[] = [];

for (const angle of rotationAngles) {
const rotateOptions = new RotateDto<TopoDSShapePointer>();
rotateOptions.shape = thickPetal;
rotateOptions.axis = rotationAxis;
rotateOptions.angle = angle;

const rotatedPetal = transforms.rotate(rotateOptions);
flowerPetalPromises.push(rotatedPetal);
}

for (const angle of rotationAngles2) {
const rotateOptions = new RotateDto<TopoDSShapePointer>();
rotateOptions.shape = thickPetal;
rotateOptions.axis = rotationAxis;
rotateOptions.angle = angle;

const rotatedPetal = transforms.rotate(rotateOptions);
flowerPetalPromises2.push(rotatedPetal);
}

const flowerPetals = await Promise.all(flowerPetalPromises);
const flowerPetals2 = await Promise.all(flowerPetalPromises2);

// Combine all petals into a single compound shape
const compoundOptions = new CompoundShapesDto<TopoDSShapePointer>();
compoundOptions.shapes = flowerPetals;
const flower = await compound.makeCompound(compoundOptions);
compoundOptions.shapes = flowerPetals2;
const flower2 = await compound.makeCompound(compoundOptions);

const pbrOptions = new Bit.Inputs.BabylonMaterial.PBRMetallicRoughnessDto();
pbrOptions.baseColor = "#111111";
pbrOptions.metallic = 0.6;
pbrOptions.roughness = 0.6;
pbrOptions.alpha = 0.4;
pbrOptions.zOffset = 2;
const pbrMaterial = pbrMetallicRoughness.create(pbrOptions);

const drawOpt = new Bit.Inputs.Draw.DrawOcctShapeMaterialOptions();
drawOpt.faceMaterial = pbrMaterial;
drawOpt.edgeColour = "#ffffff";
drawOpt.drawEdges = true;
drawOpt.precision = 0.1;

// Draw the completed flower with material options
const flowerMesh = await bitbybit.draw.drawAnyAsync({
entity: flower,
options: drawOpt
});

const flowerMesh2 = await bitbybit.draw.drawAnyAsync({
entity: flower2,
options: drawOpt
});

const emitterMesh = flowerMesh.getChildMeshes()[0] as BABYLON.Mesh;
emitterMesh.isPickable = false;

const emitterMesh2 = flowerMesh2.getChildMeshes()[0] as BABYLON.Mesh;
emitterMesh2.isPickable = false;

const scene = bitbybit.babylon.scene.getScene();

const purpleStartColor = new BABYLON.Color4(0.7, 0.3, 1.0, 1.0);
const purpleMidColor = new BABYLON.Color4(1.0, 0.4, 0.8, 1.0);

const blueStartColor = new BABYLON.Color4(0.2, 0.7, 1.0, 1.0);
const blueMidColor = new BABYLON.Color4(0.5, 0.8, 1.0, 1.0);

// This object will be shared with both particle systems to track the mouse.
const mouseTracker = { position: null as BABYLON.Vector3 | null };

// Create all the 3D assets: emitters, particle systems, and the interaction plane.

// Remove any old observable before adding a new one.
if (scene.metadata && scene.metadata.observable) {
scene.metadata.observable.remove();
}

// Centralized mouse interaction logic.
const resObs = scene.onPointerObservable.add((pointerInfo) => {
if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERMOVE) {
// We only check for hits against our single, invisible interaction plane.
const pickInfo = scene.pick(scene.pointerX, scene.pointerY, (mesh) => mesh === emitterMesh);
if (pickInfo.hit) {
// If we hit the plane, update the shared tracker object's position.
mouseTracker.position = pickInfo.pickedPoint;
} else {
// If the mouse is not over the plane, clear the position.
mouseTracker.position = null;
}
}
});

if (scene.metadata) {
scene.metadata.observable = resObs;
} else {
scene.metadata = { observable: resObs };
}

createParticleSystemForMesh(emitterMesh, scene, purpleStartColor, purpleMidColor, mouseTracker);
createParticleSystemForMesh(emitterMesh2, scene, blueStartColor, blueMidColor, mouseTracker);

}

// Execute the flower creation function
start();


// The core particle system definition.
function createParticleSystemForMesh(
emitterMesh: BABYLON.Mesh,
scene: BABYLON.Scene,
animStartColor: BABYLON.Color4,
animMidColor: BABYLON.Color4,
mouseTracker: { position: BABYLON.Vector3 | null }
): BABYLON.ParticleSystem {

const animEndColor = new BABYLON.Color4(0.1, 0.2, 0.8, 0.0);
const DRIFTER_CHANCE = 0.07;
const DRIFTER_SPEED = 0.4;

const particleSystem = new BABYLON.ParticleSystem("particles_" + emitterMesh.name, 25000, scene);
particleSystem.particleTexture = new BABYLON.Texture("https://assets.babylonjs.com/textures/flare.png", scene);
particleSystem.emitter = emitterMesh;
particleSystem.particleEmitterType = createUniformMeshParticleEmitter(emitterMesh);

particleSystem.color1 = animEndColor.clone();
particleSystem.color2 = animEndColor.clone();
particleSystem.colorDead = animEndColor.clone();
particleSystem.minSize = 0;
particleSystem.maxSize = 0;
particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
particleSystem.minLifeTime = 4.0;
particleSystem.maxLifeTime = 8.0;
particleSystem.emitRate = 2000;
particleSystem.minEmitPower = 0.1;
particleSystem.maxEmitPower = 0.5;
particleSystem.gravity = new BABYLON.Vector3(0, -1.0, 0);
(particleSystem as any).dragFactor = 0.97;

particleSystem.updateFunction = function (particles) {
const repulsionRadius = 20.0, repulsionStrength = 30;
const scaledUpdateSpeed = this._scaledUpdateSpeed;
const mousePickPosition = mouseTracker.position;
const regularStartSize = 0.35, regularEndSize = 0.1;
const largeStartSize = 0.8, largeEndSize = 0.2;

for (let index = 0; index < particles.length; index++) {
const particle = particles[index] as Particle;
particle.age += scaledUpdateSpeed;
if (particle.age >= particle.lifeTime) {
particles.splice(index, 1); this._stockParticles.push(particle); index--; continue;
}

if (particle.age === scaledUpdateSpeed) {
particle.isLarge = (Math.random() < 0.05);
particle.isDrifter = (Math.random() < DRIFTER_CHANCE);
if (particle.isDrifter) {
const driftVector = new BABYLON.Vector3(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5);
particle.driftDirection = driftVector.normalize();
}
}

particle.direction.scaleInPlace(this.dragFactor);
if (particle.isDrifter) {
const driftForce = particle.driftDirection.scale(DRIFTER_SPEED * scaledUpdateSpeed);
particle.direction.addInPlace(driftForce);
} else {
particle.direction.addInPlace(this.gravity.scale(scaledUpdateSpeed));
}

if (mousePickPosition) {
const distance = BABYLON.Vector3.Distance(particle.position, mousePickPosition);
if (distance < repulsionRadius) {
const forceDirection = particle.position.subtract(mousePickPosition).normalize();
const forceMagnitude = repulsionStrength * (1 - distance / repulsionRadius);
const forceVector = forceDirection.scale(forceMagnitude * scaledUpdateSpeed);
particle.direction.addInPlace(forceVector);
}
}

particle.position.addInPlace(particle.direction.scale(scaledUpdateSpeed));

const startSize = particle.isLarge ? largeStartSize : regularStartSize;
const endSize = particle.isLarge ? largeEndSize : regularEndSize;
const lifeRatio = particle.age / particle.lifeTime;
const fadeInDuration = 0.1;

if (lifeRatio < fadeInDuration) {
const fadeInRatio = lifeRatio / fadeInDuration;
particle.size = BABYLON.Scalar.Lerp(0, startSize, fadeInRatio);
BABYLON.Color4.LerpToRef(animEndColor, animStartColor, fadeInRatio, particle.color);
} else {
const mainLifeRatio = (lifeRatio - fadeInDuration) / (1 - fadeInDuration);
particle.size = BABYLON.Scalar.Lerp(startSize, endSize, mainLifeRatio);
if (mainLifeRatio < 0.5) {
BABYLON.Color4.LerpToRef(animStartColor, animMidColor, mainLifeRatio * 2, particle.color);
} else {
BABYLON.Color4.LerpToRef(animMidColor, animEndColor, (mainLifeRatio - 0.5) * 2, particle.color);
}
}
}
};

particleSystem.start();
return particleSystem;
}

// Creates a custom emitter to ensure particles are distributed evenly across a mesh surface.
function createUniformMeshParticleEmitter(mesh: BABYLON.Mesh): BABYLON.CustomParticleEmitter {
const positions = mesh.getVerticesData(BABYLON.VertexBuffer.PositionKind);
const indices = mesh.getIndices();
const totalFaces = indices.length / 3;
const cumulativeTriangleAreas: number[] = [];
let totalArea = 0;
const vA = new BABYLON.Vector3(), vB = new BABYLON.Vector3(), vC = new BABYLON.Vector3();
const edge1 = new BABYLON.Vector3(), edge2 = new BABYLON.Vector3();

for (let i = 0; i < totalFaces; i++) {
const indexA = indices[i * 3], indexB = indices[i * 3 + 1], indexC = indices[i * 3 + 2];
BABYLON.Vector3.FromArrayToRef(positions, indexA * 3, vA);
BABYLON.Vector3.FromArrayToRef(positions, indexB * 3, vB);
BABYLON.Vector3.FromArrayToRef(positions, indexC * 3, vC);
vB.subtractToRef(vA, edge1);
vC.subtractToRef(vA, edge2);
const area = BABYLON.Vector3.Cross(edge1, edge2).length() * 0.5;
totalArea += area;
cumulativeTriangleAreas.push(totalArea);
}

for (let i = 0; i < totalFaces; i++) cumulativeTriangleAreas[i] /= totalArea;

const customEmitter = new BABYLON.CustomParticleEmitter();
customEmitter.particlePositionGenerator = (index, particle, out) => {
const random = Math.random();
let triangleIndex = 0;
for (let i = 0; i < totalFaces; i++) if (random < cumulativeTriangleAreas[i]) { triangleIndex = i; break; }
const iA = indices[triangleIndex * 3], iB = indices[triangleIndex * 3 + 1], iC = indices[triangleIndex * 3 + 2];
BABYLON.Vector3.FromArrayToRef(positions, iA * 3, vA);
BABYLON.Vector3.FromArrayToRef(positions, iB * 3, vB);
BABYLON.Vector3.FromArrayToRef(positions, iC * 3, vC);
let r1 = Math.random(), r2 = Math.random();
if (r1 + r2 > 1) { r1 = 1 - r1; r2 = 1 - r2; }
vB.subtractToRef(vA, edge1);
vC.subtractToRef(vA, edge2);
out.copyFrom(vA).addInPlace(edge1.scaleInPlace(r1)).addInPlace(edge2.scaleInPlace(r2));
};
customEmitter.particleDestinationGenerator = (index, particle, out) => {
out.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5);
};
return customEmitter;
}