Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions mesh/animated/example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
tags: mesh
title: Animated Mesh - Flag
brief: This example shows how to create and animate a textured mesh component in the shape of a rectangle.
author_ids:
- defold-foundation
scripts: flag.script, flag.fp, flag.vp
---

This is a small Defold example showing how to create and animate textured meshes to create a waving flag. It shows how to animate an existing textured mesh without creating new buffers or using a physics simulation.

Use a mouse or touch to move the camera around.

# Setup

The flag mesh is defined in `flag.buffer` resource file and consists of a grid of triangles with position, normal, and texture coordinate streams forming a rectangle.
The texture is assigned directly to the Mesh component.

The mesh is attached in the example collection to a game object with the `flag.script` that controls the animation of the vertices.

Additionally, the camera with `orbit_camera.script` is in the collection to control the view.

![setup](setup.png)

# How it works?

In `flag.script`, call to `resource.get_buffer()` allows us to retrieve the existing mesh buffer, and `buffer.get_stream()` provides access to its vertex positions and normals.

Each frame, a sine wave modifies the Z coordinates of the vertices to simulate fabric waving in the wind. The wave amplitude increases toward the free edge of the flag, keeping the opposite edge fixed.

The vertex normals are also updated to maintain smooth lighting as the mesh deforms. The data that is modified each frame is then updated in the mesh using `resource.set_buffer()`.

A custom material and shaders (`flag.vp` and `flag.fp`) handle texture sampling and directional lighting. A perspective camera allows to see the animated mesh in 3D.
50 changes: 50 additions & 0 deletions mesh/animated/example/animatedmesh.collection
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: "animatedmesh"
scale_along_z: 0
embedded_instances {
id: "camera"
data: "components {\n"
" id: \"orbit_camera\"\n"
" component: \"/example/orbit_camera.script\"\n"
" properties {\n"
" id: \"zoom\"\n"
" value: \"7.0\"\n"
" type: PROPERTY_TYPE_NUMBER\n"
" }\n"
"}\n"
"embedded_components {\n"
" id: \"camera\"\n"
" type: \"camera\"\n"
" data: \"aspect_ratio: 1.0\\n"
"fov: 0.7854\\n"
"near_z: 0.01\\n"
"far_z: 30.0\\n"
"auto_aspect_ratio: 1\\n"
"\"\n"
"}\n"
""
position {
z: 10.0
}
}
embedded_instances {
id: "flag"
data: "components {\n"
" id: \"flag\"\n"
" component: \"/example/flag.script\"\n"
"}\n"
"embedded_components {\n"
" id: \"mesh\"\n"
" type: \"mesh\"\n"
" data: \"material: \\\"/example/flag.material\\\"\\n"
"vertices: \\\"/example/flag.buffer\\\"\\n"
"textures: \\\"/example/defold_flag.png\\\"\\n"
"position_stream: \\\"position\\\"\\n"
"normal_stream: \\\"normal\\\"\\n"
"\"\n"
"}\n"
""
position {
x: -2.25
y: -1.25
}
}
Binary file added mesh/animated/example/defold_flag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,559 changes: 1,559 additions & 0 deletions mesh/animated/example/flag.buffer

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions mesh/animated/example/flag.fp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#version 140

in highp vec3 var_normal;
in highp vec3 var_view_position;
in mediump vec2 var_texcoord0;
out vec4 out_fragColor;

uniform mediump sampler2D tex0;

void main()
{
vec4 fabric = texture(tex0, var_texcoord0);
vec3 n = normalize(var_normal);

// Soft fill light avoids harsh, triangle-shaped shadow transitions.
vec3 light_dir = normalize(vec3(-0.2, 0.5, 1.0));
float diffuse = max(dot(n, light_dir), 0.0);
float lighting = 0.55 + 0.45 * diffuse;

// Low-gloss fabric, not shiny plastic.
vec3 view_dir = normalize(-var_view_position);
vec3 h = normalize(light_dir + view_dir);
float sheen = 0.018 * pow(max(dot(n, h), 0.0), 16.0);

out_fragColor = vec4(fabric.rgb * lighting + vec3(sheen), fabric.a);
}
25 changes: 25 additions & 0 deletions mesh/animated/example/flag.material
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: "textured_flag_3d"
tags: "model"
vertex_program: "/example/flag.vp"
fragment_program: "/example/flag.fp"
vertex_space: VERTEX_SPACE_LOCAL
vertex_constants {
name: "mtx_proj"
type: CONSTANT_TYPE_PROJECTION
}
vertex_constants {
name: "mtx_worldview"
type: CONSTANT_TYPE_WORLDVIEW
}
vertex_constants {
name: "mtx_normal"
type: CONSTANT_TYPE_NORMAL
}
samplers {
name: "tex0"
wrap_u: WRAP_MODE_CLAMP_TO_EDGE
wrap_v: WRAP_MODE_CLAMP_TO_EDGE
filter_min: FILTER_MODE_MIN_LINEAR
filter_mag: FILTER_MODE_MAG_LINEAR
max_anisotropy: 0.0
}
43 changes: 43 additions & 0 deletions mesh/animated/example/flag.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

local WIDTH = 4.4

function init(self)
self.time = 0

-- Get the mesh buffer assigned to the Mesh component.
self.resource = go.get("#mesh", "vertices")
self.mesh = resource.get_buffer(self.resource)

-- Get the vertex position and normal streams.
self.positions = buffer.get_stream(self.mesh, "position")
self.normals = buffer.get_stream(self.mesh, "normal")
end

function update(self, dt)
self.time = self.time + dt

-- Each vertex has three components (x, y, z).
for i = 1, #self.positions, 3 do
local x, y = self.positions[i], self.positions[i + 1]

-- Increase the wave amplitude toward the free edge.
local amount = 0.28 * x / WIDTH
local phase = 2.8 * x + 0.25 * y - 2.3 * self.time
local s, c = math.sin(phase), math.cos(phase)

-- Move vertices along Z to create the waving effect.
self.positions[i + 2] = amount * s

-- Recalculate vertex normals to follow the deformed surface.
local dx = 0.28 / WIDTH * s + amount * 2.8 * c
local dy = amount * 0.25 * c
local inv = 1 / math.sqrt(1 + dx * dx + dy * dy)

self.normals[i] = -dx * inv
self.normals[i + 1] = -dy * inv
self.normals[i + 2] = inv
end

-- Upload the modified vertex data to the mesh.
resource.set_buffer(self.resource, self.mesh)
end
24 changes: 24 additions & 0 deletions mesh/animated/example/flag.vp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#version 140

uniform vs_uniforms {
mediump mat4 mtx_worldview;
mediump mat4 mtx_proj;
mediump mat4 mtx_normal;
};

in highp vec4 position;
in mediump vec3 normal;
in mediump vec2 texcoord0;

out highp vec3 var_normal;
out highp vec3 var_view_position;
out mediump vec2 var_texcoord0;

void main()
{
vec4 view_position = mtx_worldview * vec4(position.xyz, 1.0);
gl_Position = mtx_proj * view_position;
var_view_position = view_position.xyz;
var_normal = normalize((mtx_normal * vec4(normal, 0.0)).xyz);
var_texcoord0 = texcoord0;
}
49 changes: 49 additions & 0 deletions mesh/animated/example/orbit_camera.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-- The initial zoom level
go.property("zoom", 3)
-- The speed of the zoom
go.property("zoom_speed", 0.1)
-- The speed of the rotation
go.property("rotation_speed", 0.5)
-- The offset of the camera from the origin
go.property("offset", vmath.vector3(0, 0, 0))

function init(self)
-- Acquire input focus to receive input events
msg.post(".", "acquire_input_focus")

-- Initialize start values
self.yaw = go.get(".", "euler.y")
self.pitch = go.get(".", "euler.x")
self.zoom_offset = 0
self.current_yaw = self.yaw
self.current_pitch = self.pitch
self.current_zoom = self.zoom_offset
end

function update(self, dt)
-- Animate camera rotation and zoom
self.current_yaw = vmath.lerp(0.15, self.current_yaw, self.yaw)
self.current_pitch = vmath.lerp(0.15, self.current_pitch, self.pitch)
self.current_zoom = vmath.lerp(0.15, self.current_zoom, self.zoom_offset)

-- Calculate rotation and position
local camera_yaw = vmath.quat_rotation_y(math.rad(self.current_yaw))
local camera_pitch = vmath.quat_rotation_x(math.rad(self.current_pitch))
local camera_rotation = camera_yaw * camera_pitch
local camera_position = self.offset + vmath.rotate(camera_rotation, vmath.vector3(0, 0, self.zoom + self.current_zoom))

-- Set camera position and rotation
go.set_position(camera_position)
go.set_rotation(camera_rotation)
end

function on_input(self, action_id, action)
if action_id == hash("touch") and not action.pressed then
self.yaw = self.yaw - action.dx * self.rotation_speed
self.pitch = self.pitch + action.dy * self.rotation_speed
elseif action_id == hash("mouse_wheel_up") then
self.zoom_offset = self.zoom_offset - self.zoom * self.zoom_speed
elseif action_id == hash("mouse_wheel_down") then
self.zoom_offset = self.zoom_offset + self.zoom * self.zoom_speed
end
end
25 changes: 25 additions & 0 deletions mesh/animated/example/quad.buffer
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[
{
"name": "position",
"type": "float32",
"count": 3,
"data": [
-0.5, -0.5, 0,
0.5, -0.5, 0,
-0.5, 0.5, 0,
0.5, 0.5, 0
]
},
{
"name": "texcoord0",
"type": "float32",
"count": 2,
"data": [
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
1.0, 1.0
]
}
]
]
64 changes: 64 additions & 0 deletions mesh/animated/game.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[project]
title = Animated Mesh

[bootstrap]
main_collection = /example/animatedmesh.collectionc

[input]
game_binding = /builtins/input/all.input_bindingc
repeat_interval = 0.05

[display]
width = 720
height = 720
high_dpi = 1

[physics]
scale = 0.02
gravity_y = -500.0

[script]
shared_state = 1

[collection_proxy]
max_count = 256

[label]
subpixels = 1

[sprite]
subpixels = 1
max_count = 32765

[windows]
iap_provider =

[android]
package = com.defold.examples

[ios]
bundle_identifier = com.defold.examples

[osx]
bundle_identifier = com.defold.examples

[html5]
show_fullscreen_button = 0
show_made_with_defold = 0
scale_mode = no_scale
heap_size = 64

[collection]
max_instances = 32765

[particle_fx]
max_emitter_count = 1024

[render]
clear_color_blue = 0.183594
clear_color_green = 0.164063
clear_color_red = 0.160156

[sound]
sample_frame_count = 4

Binary file added mesh/animated/setup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mesh/animated/thumbnail.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading