com.github.asus4.texture-source 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/LICENSE.meta +7 -0
- package/README.md +29 -0
- package/README.md.meta +7 -0
- package/Resources/com.github.asus4.texture-source/TextureTransform.compute +24 -0
- package/Resources/com.github.asus4.texture-source/TextureTransform.compute.meta +9 -0
- package/Resources/com.github.asus4.texture-source.meta +8 -0
- package/Resources.meta +8 -0
- package/Runtime/BaseTextureSource.cs +13 -0
- package/Runtime/BaseTextureSource.cs.meta +11 -0
- package/Runtime/ITextureSource.cs +14 -0
- package/Runtime/ITextureSource.cs.meta +11 -0
- package/Runtime/TextureSource.asmdef +14 -0
- package/Runtime/TextureSource.asmdef.meta +7 -0
- package/Runtime/TextureTransformer.cs +74 -0
- package/Runtime/TextureTransformer.cs.meta +11 -0
- package/Runtime/VideoTextureSource.cs +111 -0
- package/Runtime/VideoTextureSource.cs.meta +11 -0
- package/Runtime/VirtualTextureSource.cs +131 -0
- package/Runtime/VirtualTextureSource.cs.meta +11 -0
- package/Runtime/WebCamTextureSource.cs +156 -0
- package/Runtime/WebCamTextureSource.cs.meta +11 -0
- package/Runtime.meta +8 -0
- package/package.json +14 -0
- package/package.json.meta +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Koki Ibukuro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/LICENSE.meta
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Texture Source
|
|
2
|
+
|
|
3
|
+
TextureSource is an utility for using CV in Unity. It enables you to choose multiple input sources in Editor.
|
|
4
|
+
|
|
5
|
+
## Install via UPM
|
|
6
|
+
|
|
7
|
+
Add following setting to `Packages/manifest.json`
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
{
|
|
11
|
+
"scopedRegistries": [
|
|
12
|
+
{
|
|
13
|
+
"name": "npm",
|
|
14
|
+
"url": "https://registry.npmjs.com",
|
|
15
|
+
"scopes": [
|
|
16
|
+
"com.github.asus4"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"com.github.asus4.texture-source": "0.1.0",
|
|
22
|
+
...// other dependencies
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Acknowledgement
|
|
28
|
+
|
|
29
|
+
Inspired from [TestTools](https://github.com/keijiro/TestTools)
|
package/README.md.meta
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#pragma kernel TextureTransform
|
|
2
|
+
|
|
3
|
+
Texture2D<float4> _InputTex;
|
|
4
|
+
RWTexture2D<float4> _OutputTex;
|
|
5
|
+
int2 _OutputTexSize;
|
|
6
|
+
float4x4 _TransformMatrix;
|
|
7
|
+
|
|
8
|
+
SamplerState linearClampSampler;
|
|
9
|
+
|
|
10
|
+
[numthreads(8,8,1)]
|
|
11
|
+
void TextureTransform (uint2 id : SV_DispatchThreadID)
|
|
12
|
+
{
|
|
13
|
+
if(any(id >= _OutputTexSize))
|
|
14
|
+
{
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
float2 uv = float2(id.x, id.y) / float2(_OutputTexSize.x, _OutputTexSize.y);
|
|
19
|
+
uv = mul(_TransformMatrix, float4(uv, 0, 1)).xy;
|
|
20
|
+
|
|
21
|
+
_OutputTex[id] = any(uv < 0) || any(uv > 1)
|
|
22
|
+
? float4(0, 0, 0, 1)
|
|
23
|
+
: _InputTex.SampleLevel(linearClampSampler, uv, 0);
|
|
24
|
+
}
|
package/Resources.meta
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
namespace TextureSource
|
|
2
|
+
{
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
|
|
5
|
+
public abstract class BaseTextureSource : ScriptableObject, ITextureSource
|
|
6
|
+
{
|
|
7
|
+
public abstract bool DidUpdateThisFrame { get; }
|
|
8
|
+
public abstract Texture Texture { get; }
|
|
9
|
+
public abstract void Start();
|
|
10
|
+
public abstract void Stop();
|
|
11
|
+
public abstract void Next();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "TextureSource",
|
|
3
|
+
"rootNamespace": "",
|
|
4
|
+
"references": [],
|
|
5
|
+
"includePlatforms": [],
|
|
6
|
+
"excludePlatforms": [],
|
|
7
|
+
"allowUnsafeCode": false,
|
|
8
|
+
"overrideReferences": false,
|
|
9
|
+
"precompiledReferences": [],
|
|
10
|
+
"autoReferenced": true,
|
|
11
|
+
"defineConstraints": [],
|
|
12
|
+
"versionDefines": [],
|
|
13
|
+
"noEngineReferences": false
|
|
14
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
namespace TextureSource
|
|
2
|
+
{
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
|
|
5
|
+
public class TextureTransformer : System.IDisposable
|
|
6
|
+
{
|
|
7
|
+
private static ComputeShader compute;
|
|
8
|
+
private static int kernel;
|
|
9
|
+
private static readonly int _InputTex = Shader.PropertyToID("_InputTex");
|
|
10
|
+
private static readonly int _OutputTex = Shader.PropertyToID("_OutputTex");
|
|
11
|
+
private static readonly int _OutputTexSize = Shader.PropertyToID("_OutputTexSize");
|
|
12
|
+
private static readonly int _TransformMatrix = Shader.PropertyToID("_TransformMatrix");
|
|
13
|
+
|
|
14
|
+
private static readonly Matrix4x4 PopMatrix = Matrix4x4.Translate(new Vector3(0.5f, 0.5f, 0));
|
|
15
|
+
private static readonly Matrix4x4 PushMatrix = Matrix4x4.Translate(new Vector3(-0.5f, -0.5f, 0));
|
|
16
|
+
|
|
17
|
+
private RenderTexture texture;
|
|
18
|
+
public readonly int width;
|
|
19
|
+
public readonly int height;
|
|
20
|
+
|
|
21
|
+
public RenderTexture Texture => texture;
|
|
22
|
+
|
|
23
|
+
public TextureTransformer(int width, int height)
|
|
24
|
+
{
|
|
25
|
+
this.width = width;
|
|
26
|
+
this.height = height;
|
|
27
|
+
|
|
28
|
+
var desc = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32)
|
|
29
|
+
{
|
|
30
|
+
enableRandomWrite = true,
|
|
31
|
+
useMipMap = false,
|
|
32
|
+
depthBufferBits = 0,
|
|
33
|
+
};
|
|
34
|
+
texture = new RenderTexture(desc);
|
|
35
|
+
texture.Create();
|
|
36
|
+
|
|
37
|
+
if (compute == null)
|
|
38
|
+
{
|
|
39
|
+
const string SHADER_PATH = "com.github.asus4.texture-source/TextureTransform";
|
|
40
|
+
compute = Resources.Load<ComputeShader>(SHADER_PATH);
|
|
41
|
+
kernel = compute.FindKernel("TextureTransform");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public void Dispose()
|
|
46
|
+
{
|
|
47
|
+
if (texture != null)
|
|
48
|
+
{
|
|
49
|
+
texture.Release();
|
|
50
|
+
Object.Destroy(texture);
|
|
51
|
+
}
|
|
52
|
+
texture = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public RenderTexture Transform(Texture input, Matrix4x4 t)
|
|
56
|
+
{
|
|
57
|
+
compute.SetTexture(kernel, _InputTex, input, 0);
|
|
58
|
+
compute.SetTexture(kernel, _OutputTex, texture, 0);
|
|
59
|
+
compute.SetInts(_OutputTexSize, texture.width, texture.height);
|
|
60
|
+
compute.SetMatrix(_TransformMatrix, t);
|
|
61
|
+
compute.Dispatch(kernel, Mathf.CeilToInt(texture.width / 8f), Mathf.CeilToInt(texture.height / 8f), 1);
|
|
62
|
+
return texture;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public RenderTexture Transform(Texture input, Vector2 offset, float eulerRotation, Vector2 scale)
|
|
66
|
+
{
|
|
67
|
+
Matrix4x4 trs = Matrix4x4.TRS(
|
|
68
|
+
new Vector3(-offset.x, -offset.y, 0),
|
|
69
|
+
Quaternion.Euler(0, 0, -eulerRotation),
|
|
70
|
+
new Vector3(1f / scale.x, 1f / scale.y, 1));
|
|
71
|
+
return Transform(input, PopMatrix * trs * PushMatrix);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
namespace TextureSource
|
|
2
|
+
{
|
|
3
|
+
using System.IO;
|
|
4
|
+
using UnityEngine;
|
|
5
|
+
using UnityEngine.Video;
|
|
6
|
+
|
|
7
|
+
[CreateAssetMenu(menuName = "ScriptableObject/Texture Source/Video", fileName = "VideoTextureSource")]
|
|
8
|
+
public class VideoTextureSource : BaseTextureSource
|
|
9
|
+
{
|
|
10
|
+
[System.Serializable]
|
|
11
|
+
public class VideoData
|
|
12
|
+
{
|
|
13
|
+
public VideoSource source;
|
|
14
|
+
public string url;
|
|
15
|
+
public VideoClip clip;
|
|
16
|
+
|
|
17
|
+
public VideoSource Source => clip == null
|
|
18
|
+
? VideoSource.Url : VideoSource.VideoClip;
|
|
19
|
+
|
|
20
|
+
public string URL
|
|
21
|
+
{
|
|
22
|
+
get
|
|
23
|
+
{
|
|
24
|
+
return Path.IsPathRooted(url)
|
|
25
|
+
? url
|
|
26
|
+
: Path.Combine(Application.dataPath, url);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[SerializeField]
|
|
32
|
+
[Tooltip("Whether to loop the video")]
|
|
33
|
+
private bool loop = true;
|
|
34
|
+
|
|
35
|
+
[SerializeField]
|
|
36
|
+
[Tooltip("Whether to play sound in the video")]
|
|
37
|
+
private bool playSound = false;
|
|
38
|
+
|
|
39
|
+
[SerializeField]
|
|
40
|
+
private VideoData[] videos = default;
|
|
41
|
+
|
|
42
|
+
private VideoPlayer player;
|
|
43
|
+
private int currentIndex;
|
|
44
|
+
private long currentFrame = -1;
|
|
45
|
+
|
|
46
|
+
public override bool DidUpdateThisFrame
|
|
47
|
+
{
|
|
48
|
+
get
|
|
49
|
+
{
|
|
50
|
+
long frame = player.frame;
|
|
51
|
+
bool isUpdated = frame != currentFrame;
|
|
52
|
+
currentFrame = frame;
|
|
53
|
+
return isUpdated;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public override Texture Texture => player.texture;
|
|
58
|
+
|
|
59
|
+
public override void Start()
|
|
60
|
+
{
|
|
61
|
+
GameObject go = new GameObject(nameof(VideoTextureSource));
|
|
62
|
+
DontDestroyOnLoad(go);
|
|
63
|
+
player = go.AddComponent<VideoPlayer>();
|
|
64
|
+
player.renderMode = VideoRenderMode.APIOnly;
|
|
65
|
+
player.audioOutputMode = playSound
|
|
66
|
+
? VideoAudioOutputMode.Direct
|
|
67
|
+
: VideoAudioOutputMode.None;
|
|
68
|
+
player.isLooping = loop;
|
|
69
|
+
|
|
70
|
+
currentIndex = Mathf.Min(currentIndex, videos.Length - 1);
|
|
71
|
+
|
|
72
|
+
StartVideo(currentIndex);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public override void Stop()
|
|
76
|
+
{
|
|
77
|
+
if (player == null)
|
|
78
|
+
{
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
player.Stop();
|
|
82
|
+
Destroy(player.gameObject);
|
|
83
|
+
player = null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public override void Next()
|
|
87
|
+
{
|
|
88
|
+
currentIndex = (currentIndex + 1) % videos.Length;
|
|
89
|
+
StartVideo(currentIndex);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private void StartVideo(int index)
|
|
93
|
+
{
|
|
94
|
+
var data = videos[index];
|
|
95
|
+
VideoSource source = data.Source;
|
|
96
|
+
player.source = source;
|
|
97
|
+
if (source == VideoSource.Url)
|
|
98
|
+
{
|
|
99
|
+
player.url = data.URL;
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
{
|
|
103
|
+
player.clip = data.clip;
|
|
104
|
+
}
|
|
105
|
+
player.Prepare();
|
|
106
|
+
player.Play();
|
|
107
|
+
|
|
108
|
+
currentFrame = -1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
namespace TextureSource
|
|
2
|
+
{
|
|
3
|
+
using UnityEngine;
|
|
4
|
+
using UnityEngine.Events;
|
|
5
|
+
using UnityEngine.Scripting;
|
|
6
|
+
|
|
7
|
+
/// <summary>
|
|
8
|
+
/// Virtual Texture Source
|
|
9
|
+
/// </summary>
|
|
10
|
+
public class VirtualTextureSource : MonoBehaviour
|
|
11
|
+
{
|
|
12
|
+
[System.Serializable]
|
|
13
|
+
public class TextureEvent : UnityEvent<Texture> { }
|
|
14
|
+
[System.Serializable]
|
|
15
|
+
public class AspectChangeEvent : UnityEvent<float> { }
|
|
16
|
+
|
|
17
|
+
[SerializeField]
|
|
18
|
+
[Tooltip("A texture source scriptable object")]
|
|
19
|
+
private BaseTextureSource source = default;
|
|
20
|
+
|
|
21
|
+
[SerializeField]
|
|
22
|
+
[Tooltip("A texture source scriptable object for Editor. If it is null, used source in Editor")]
|
|
23
|
+
private BaseTextureSource sourceForEditor = null;
|
|
24
|
+
|
|
25
|
+
[SerializeField]
|
|
26
|
+
[Tooltip("If true, the texture is trimmed to the screen aspect ratio. Use this to show in full screen")]
|
|
27
|
+
private bool trimToScreenAspect = false;
|
|
28
|
+
|
|
29
|
+
[Tooltip("Event called when texture updated")]
|
|
30
|
+
public TextureEvent OnTexture = new TextureEvent();
|
|
31
|
+
|
|
32
|
+
[Tooltip("Event called when the aspect ratio changed")]
|
|
33
|
+
public AspectChangeEvent OnAspectChange = new AspectChangeEvent();
|
|
34
|
+
|
|
35
|
+
private ITextureSource activeSource;
|
|
36
|
+
private float aspect = float.NegativeInfinity;
|
|
37
|
+
private TextureTransformer transformer;
|
|
38
|
+
|
|
39
|
+
public bool DidUpdateThisFrame => activeSource.DidUpdateThisFrame;
|
|
40
|
+
public Texture Texture => activeSource.Texture;
|
|
41
|
+
|
|
42
|
+
private void OnEnable()
|
|
43
|
+
{
|
|
44
|
+
activeSource = sourceForEditor != null && Application.isEditor
|
|
45
|
+
? sourceForEditor
|
|
46
|
+
: source;
|
|
47
|
+
|
|
48
|
+
if (activeSource == null)
|
|
49
|
+
{
|
|
50
|
+
Debug.LogError("Source is not set.", this);
|
|
51
|
+
enabled = false;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
activeSource.Start();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private void OnDisable()
|
|
58
|
+
{
|
|
59
|
+
activeSource?.Stop();
|
|
60
|
+
transformer?.Dispose();
|
|
61
|
+
transformer = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private void Update()
|
|
65
|
+
{
|
|
66
|
+
if (!activeSource.DidUpdateThisFrame)
|
|
67
|
+
{
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
Texture tex = trimToScreenAspect
|
|
72
|
+
? TrimToScreen(Texture)
|
|
73
|
+
: Texture;
|
|
74
|
+
OnTexture?.Invoke(tex);
|
|
75
|
+
|
|
76
|
+
float aspect = (float)tex.width / tex.height;
|
|
77
|
+
if (aspect != this.aspect)
|
|
78
|
+
{
|
|
79
|
+
OnAspectChange?.Invoke(aspect);
|
|
80
|
+
this.aspect = aspect;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Invoked by UI Events
|
|
85
|
+
[Preserve]
|
|
86
|
+
public void NextSource()
|
|
87
|
+
{
|
|
88
|
+
activeSource?.Next();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private Texture TrimToScreen(Texture texture)
|
|
92
|
+
{
|
|
93
|
+
float cameraAspect = (float)texture.width / texture.height;
|
|
94
|
+
float targetAspect = (float)Screen.width / Screen.height;
|
|
95
|
+
|
|
96
|
+
if (Mathf.Abs(cameraAspect - targetAspect) < 0.01f)
|
|
97
|
+
{
|
|
98
|
+
return texture;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
int width, height;
|
|
102
|
+
Vector2 scale;
|
|
103
|
+
if (cameraAspect > targetAspect)
|
|
104
|
+
{
|
|
105
|
+
width = RoundToEven(texture.height * targetAspect);
|
|
106
|
+
height = texture.height;
|
|
107
|
+
scale = new Vector2((float)texture.width / width, 1);
|
|
108
|
+
}
|
|
109
|
+
else
|
|
110
|
+
{
|
|
111
|
+
width = texture.width;
|
|
112
|
+
height = RoundToEven(texture.width / targetAspect);
|
|
113
|
+
scale = new Vector2(1, (float)texture.height / height);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
bool needInitialize = transformer == null || width != transformer.width || height != transformer.height;
|
|
117
|
+
if (needInitialize)
|
|
118
|
+
{
|
|
119
|
+
transformer?.Dispose();
|
|
120
|
+
transformer = new TextureTransformer(width, height);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return transformer.Transform(texture, Vector2.zero, 0, scale);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private static int RoundToEven(float n)
|
|
127
|
+
{
|
|
128
|
+
return Mathf.RoundToInt(n / 2) * 2;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
namespace TextureSource
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Linq;
|
|
5
|
+
using UnityEngine;
|
|
6
|
+
|
|
7
|
+
[CreateAssetMenu(menuName = "ScriptableObject/Texture Source/WebCam", fileName = "WebCamTextureSource")]
|
|
8
|
+
public sealed class WebCamTextureSource : BaseTextureSource
|
|
9
|
+
{
|
|
10
|
+
[Flags]
|
|
11
|
+
public enum WebCamKindFlag
|
|
12
|
+
{
|
|
13
|
+
WideAngle = 1 << 0,
|
|
14
|
+
Telephoto = 1 << 1,
|
|
15
|
+
ColorAndDepth = 1 << 2,
|
|
16
|
+
UltraWideAngle = 1 << 3,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
[Flags]
|
|
20
|
+
public enum FacingFlag
|
|
21
|
+
{
|
|
22
|
+
Front = 1 << 0,
|
|
23
|
+
Back = 1 << 1,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[SerializeField]
|
|
27
|
+
private WebCamKindFlag kindFilter = WebCamKindFlag.WideAngle | WebCamKindFlag.Telephoto | WebCamKindFlag.UltraWideAngle;
|
|
28
|
+
|
|
29
|
+
[SerializeField]
|
|
30
|
+
private FacingFlag facingFilter = FacingFlag.Front | FacingFlag.Back;
|
|
31
|
+
|
|
32
|
+
[SerializeField]
|
|
33
|
+
private Vector2Int resolution = new Vector2Int(1270, 720);
|
|
34
|
+
|
|
35
|
+
[SerializeField]
|
|
36
|
+
private int frameRate = 60;
|
|
37
|
+
|
|
38
|
+
public override bool DidUpdateThisFrame
|
|
39
|
+
{
|
|
40
|
+
get
|
|
41
|
+
{
|
|
42
|
+
if (webCamTexture == null || webCamTexture.width < 20)
|
|
43
|
+
{
|
|
44
|
+
// On macOS, it returns the 10x10 texture at first several frames.
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return webCamTexture.didUpdateThisFrame;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public override Texture Texture => NormalizeWebCam();
|
|
52
|
+
|
|
53
|
+
private WebCamDevice[] devices;
|
|
54
|
+
private WebCamTexture webCamTexture;
|
|
55
|
+
private int currentIndex;
|
|
56
|
+
private TextureTransformer transformer;
|
|
57
|
+
private int lastUpdatedFrame = -1;
|
|
58
|
+
private bool isFrontFacing;
|
|
59
|
+
|
|
60
|
+
public override void Start()
|
|
61
|
+
{
|
|
62
|
+
devices = WebCamTexture.devices.Where(IsMatchFilter).ToArray();
|
|
63
|
+
StartCamera(currentIndex);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public override void Stop()
|
|
67
|
+
{
|
|
68
|
+
if (webCamTexture != null)
|
|
69
|
+
{
|
|
70
|
+
webCamTexture.Stop();
|
|
71
|
+
webCamTexture = null;
|
|
72
|
+
}
|
|
73
|
+
transformer?.Dispose();
|
|
74
|
+
transformer = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public override void Next()
|
|
78
|
+
{
|
|
79
|
+
currentIndex = (currentIndex + 1) % devices.Length;
|
|
80
|
+
StartCamera(currentIndex);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private void StartCamera(int index)
|
|
84
|
+
{
|
|
85
|
+
Stop();
|
|
86
|
+
WebCamDevice device = devices[index];
|
|
87
|
+
webCamTexture = new WebCamTexture(device.name, resolution.x, resolution.y, frameRate);
|
|
88
|
+
webCamTexture.Play();
|
|
89
|
+
isFrontFacing = device.isFrontFacing;
|
|
90
|
+
lastUpdatedFrame = -1;
|
|
91
|
+
Debug.Log($"Started camera:{device.name}");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private RenderTexture NormalizeWebCam()
|
|
95
|
+
{
|
|
96
|
+
if (webCamTexture == null)
|
|
97
|
+
{
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (lastUpdatedFrame == Time.frameCount)
|
|
102
|
+
{
|
|
103
|
+
return transformer.Texture;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
bool isPortrait = webCamTexture.videoRotationAngle == 90 || webCamTexture.videoRotationAngle == 270;
|
|
107
|
+
int width = webCamTexture.width;
|
|
108
|
+
int height = webCamTexture.height;
|
|
109
|
+
if (isPortrait)
|
|
110
|
+
{
|
|
111
|
+
(width, height) = (height, width); // swap
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
bool needInitialize = transformer == null || width != transformer.width || height != transformer.height;
|
|
115
|
+
if (needInitialize)
|
|
116
|
+
{
|
|
117
|
+
transformer?.Dispose();
|
|
118
|
+
transformer = new TextureTransformer(width, height);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
Vector2 scale;
|
|
122
|
+
if (isPortrait)
|
|
123
|
+
{
|
|
124
|
+
scale = new Vector2(webCamTexture.videoVerticallyMirrored ^ isFrontFacing ? -1 : 1, 1);
|
|
125
|
+
}
|
|
126
|
+
else
|
|
127
|
+
{
|
|
128
|
+
scale = new Vector2(isFrontFacing ? -1 : 1, webCamTexture.videoVerticallyMirrored ? -1 : 1);
|
|
129
|
+
}
|
|
130
|
+
transformer.Transform(webCamTexture, Vector2.zero, -webCamTexture.videoRotationAngle, scale);
|
|
131
|
+
|
|
132
|
+
// Debug.Log($"mirrored: {webCamTexture.videoVerticallyMirrored}, angle: {webCamTexture.videoRotationAngle}, isFrontFacing: {isFrontFacing}");
|
|
133
|
+
|
|
134
|
+
lastUpdatedFrame = Time.frameCount;
|
|
135
|
+
return transformer.Texture;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private bool IsMatchFilter(WebCamDevice device)
|
|
139
|
+
{
|
|
140
|
+
WebCamKindFlag kind = device.kind switch
|
|
141
|
+
{
|
|
142
|
+
WebCamKind.WideAngle => WebCamKindFlag.WideAngle,
|
|
143
|
+
WebCamKind.Telephoto => WebCamKindFlag.Telephoto,
|
|
144
|
+
WebCamKind.ColorAndDepth => WebCamKindFlag.ColorAndDepth,
|
|
145
|
+
WebCamKind.UltraWideAngle => WebCamKindFlag.UltraWideAngle,
|
|
146
|
+
_ => throw new NotImplementedException($"Unknown WebCamKind: {device.kind}"),
|
|
147
|
+
};
|
|
148
|
+
FacingFlag facing = device.isFrontFacing
|
|
149
|
+
? FacingFlag.Front
|
|
150
|
+
: FacingFlag.Back;
|
|
151
|
+
|
|
152
|
+
return kindFilter.HasFlag(kind)
|
|
153
|
+
&& facingFilter.HasFlag(facing);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
package/Runtime.meta
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "com.github.asus4.texture-source",
|
|
3
|
+
"displayName": "TextureSource",
|
|
4
|
+
"author": "Koki Ibukuro",
|
|
5
|
+
"description": "Virtual Texture Source",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"unity"
|
|
8
|
+
],
|
|
9
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
10
|
+
"unity": "2020.3",
|
|
11
|
+
"unityRelease": "0f1",
|
|
12
|
+
"version": "0.1.0",
|
|
13
|
+
"type": "tool"
|
|
14
|
+
}
|