Create dynamic GameObjects and transform their positions together from JSON data - unity3d

Consider this dataset:
[
{
PartId: 0,
Positions: [
{ x: 0, y: 0, z: 0, timeCode: 0.0000 },
{ x: 0, y: 0, z: 1, timeCode: 0.0025 },
{ x: 0, y: 0, z: 2, timeCode: 0.0050 },
{ x: 0, y: 0, z: 3, timeCode: 0.0075 },
{ x: 0, y: 0, z: 4, timeCode: 0.0100 },
{ x: 0, y: 0, z: 5, timeCode: 0.0125 },
{ x: 0, y: 0, z: 6, timeCode: 0.0150 },
{ x: 0, y: 0, z: 7, timeCode: 0.0175 },
{ x: 0, y: 0, z: 8, timeCode: 0.0200 },
]
},
{
PartId: 1,
Positions: [
{ x: 0, y: 0, z: 0, timeCode: 0.0000 },
{ x: 0, y: 0, z: 2, timeCode: 0.0025 },
{ x: 0, y: 0, z: 4, timeCode: 0.0050 },
{ x: 0, y: 0, z: 6, timeCode: 0.0075 },
{ x: 0, y: 0, z: 8, timeCode: 0.0100 },
{ x: 0, y: 0, z: 10, timeCode: 0.0125 },
{ x: 0, y: 0, z: 12, timeCode: 0.0150 },
{ x: 0, y: 0, z: 14, timeCode: 0.0175 },
{ x: 0, y: 0, z: 16, timeCode: 0.0200 },
]
}
]
I am able to load and parse the JSON data and I can create a new GameObject for each PartId. I am trying to figure out the best way to transform each "Part" according to the Position collection all at the same time.
I have an empty GameObject in the scene with an attached class. Inside the Start method I get the JSON data and then in a loop I create the GameObject class, set its initial position and then start a coroutine defined in the same class.
Main class:
void Start() {
// do json stuff...
// ........
// then
// for each part...
foreach(PartGroup pg in Parts) {
// create a new GameObject from the Part class
Part part = gameObject.AddComponent(typeof(Part)) as Part;
// send this part data to the new GameObject
part.PartGroup = pg;
// set the initial position for the part
part.Init();
// start a IEnumerator Coroutine in the part class
StartCoroutine(part.PlayFrom());
}
}
Part class:
public void Init() {
Joint = GameObject.CreatePrimitive(PrimitiveType.Sphere);
Joint.transform.localScale = new Vector3(jointSize, jointSize, jointSize);
Joint.transform.position = new Vector3(PartGroup.Positions[0].X, PartGroup.Positions[0].Z, PartGroup.Positions[0].Y);
}
public IEnumerator PlayFrom(float time = 0f) {
while (PlayBack(time)) {
yield return null;
time += Time.fixedDeltaTime;
}
}
bool PlayBack(float time) {
float sample = time / (Time.fixedDeltaTime / speed);
int previousIndex = (int)(sample);
int last = PartGroup.Positions.Count - 1;
if (previousIndex < last) {
int nextIndex = previousIndex + 1;
float interpolation = sample - previousIndex;
Joint.transform.position = Vector3.Lerp(
new Vector3(PartGroup.Positions[previousIndex].X, PartGroup.Positions[previousIndex].Z, PartGroup.Positions[previousIndex].Y),
new Vector3(PartGroup.Positions[nextIndex].X, PartGroup.Positions[nextIndex].Z, PartGroup.Positions[nextIndex].Y),
interpolation);
return true;
}
Joint.transform.position = new Vector3(PartGroup.Positions[last].X, PartGroup.Positions[last].Z, PartGroup.Positions[last].Y);
return false;
}
This is how I currently have it set up. It does work, but it is not smooth motion and sometimes it seems to lag or jump frames. Is this the best way to accomplish this, or is there a better way (like FixedUpdate)? I have set the fixed time property in my project settings to match the data timeCode.
Any help with best practices for stuff like this is greatly appreciated!

You have to use Time.deltaTime in
time += Time.deltaTime;
and
float sample = time / (Time.deltaTime / speed);
Coroutines are executed together with all Update calls so using fixedDeltaTime breaks the frame independency.
or probably use WaitForFixedUpdate to execute it like FixedUpdate
while (PlayBack(time)) {
yield return new WaitForFixedUpdate();
time += Time.fixedDeltaTime;
}
Also in
foreach(PartGroup pg in Parts)
{
// create a new GameObject from the Part class
Part part = gameObject.AddComponent(typeof(Part)) as Part;
// send this part data to the new GameObject
part.PartGroup = pg;
// set the initial position for the part
part.Init();
// start a IEnumerator Coroutine in the part class
StartCoroutine(part.PlayFrom());
}
it looks like you are adding a component for each element in the list all to one and the same GameObject .. I don't know if that's what you planned to do. AddComponent does not create a new GameObject with that component but attaches that component to the same gameObject that script is attached to.
You probably ment to use new GameObject
Part part = new GameObject("new GameObject").AddComponent<Part>();
// make it a child of this gameObject?
part.SetParent(gameObject, false);
also the calculations
float sample = time / (Time.fixedDeltaTime / speed);
int previousIndex = (int)(sample);
...
float interpolation = sample - previousIndex;
seems a bit odd .. are you sure it always returns a value between 0 and 1?

Related

How to using flutter implement this html5 canvas animation

I'm learning flutter recently, but I still don't understand the use of flutter canvas,
I hope to get your help, here.
How to using flutter implement this html5 canvas animation?
jsfiddle animation demo link
HTML code
margin: 0;
padding: 0;
background: #000000;
}
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="mycanvas"></canvas>
<script type="text/javascript">
const mycanvas = document.getElementById('mycanvas')
const context = mycanvas.getContext('2d')
const canvasWidth = window.innerWidth
const canvasHeight = window.innerHeight
mycanvas.width = canvasWidth
mycanvas.height = canvasHeight
console.log(canvasWidth, canvasHeight);
// 创建渐变
function createGradient(context, p0, p1) {
const gradient = context.createLinearGradient(p0.x, p0.y, p1.x, p1.y)
gradient.addColorStop(0, 'rgba(255, 255, 0, 0)')
gradient.addColorStop(1, 'rgba(255, 255, 0, 1)')
return gradient
}
// 绘制曲线
function createCurveLine(points) {
const gradient = createGradient(context, points[0], points[points.length - 1])
context.beginPath()
context.moveTo(points[0].x, points[0].y)
// 参数 points 是曲线上一部分连续点的集合,我们用 lineTo 把这些点连结起来,就近似的得到了曲线
for (let i = 0; i < points.length; i++) {
const p = points[i]
context.lineTo(p.x, p.y)
}
context.moveTo(points[0].x, points[0].y)
context.strokeStyle = gradient
context.lineCap = 'round'
context.lineWidth = 5
context.shadowColor = 'rgba(255, 0, 255, 1)'
context.shadowBlur = 10
context.stroke()
}
const P0 = {
x: 100,
y: canvasHeight / 2
}
const P1 = {
x: canvasWidth / 2,
y: canvasHeight / 2 - 200
}
const P2 = {
x: canvasWidth - 100,
y: canvasHeight / 2
}
let t0 = 0
let t1 = 0
let points = [] // 存储曲线上点的集合
const lineLength = 0.3;
function draw() {
context.clearRect(0, 0, canvasWidth, canvasHeight);
if (t1 < lineLength) {
t0 = 0;
}
if (t0 > 1 - lineLength) {
t1 = 1;
}
const currentPoint = {
x: computeCurvePoint(P0.x, P1.x, P2.x, t1),
y: computeCurvePoint(P0.y, P1.y, P2.y, t1)
}
// 每当 t1 变化时,就将对应的点添加到 points 集合中
points.push(currentPoint)
const len = points.length
context.save()
if (len > 1) {
createCurveLine(points.slice(Math.floor(len * t0), Math.max(Math.ceil(len * t1), 2)))
}
context.restore()
t0 += 0.005
t1 += 0.005
if (t0 > 1 && t1 > 1) {
t0 = 0
t1 = 0
points = []
}
requestAnimationFrame(draw)
}
draw()
/*!
* 计算二次贝塞尔曲线上的点
* #param {Number} p0 起始点
* #param {Number} p1 控制点
* #param {Number} p2 结束点
* #param {Number} t 0-1的集合
* #return {Number} 返回计算后的点
*/
function computeCurvePoint(p0, p1, p2, t) {
return (1 - t) * (1 - t) * p0 + 2 * t * (1 - t) * p1 + t * t * p2
}
function arc(...points) {
points.forEach(p => {
context.beginPath()
context.arc(p.x, p.y, 3, 0, Math.PI * 2)
context.stroke();
});
}
</script>
</body>
</html>

How can I make number counter to count multiple elements in Wix Velo

I'm making a website on Wix and it has Velo which works like javascript (I don't understand much in coding)
I was trying to do a number counter which counts from 0 to a given number and I did it, but I need 4 different counters not sure how to do it maybe someone can help, please.
so my code looks like this
$w.onReady(function() {});
let startNum = 0;
let endNum = 145;
const duration = 20;
$w.onReady(function() {
setInterval(() => {
countUp();
}, duration);
});
function countUp() {
if (startNum <= endNum) {
$w('#StartNumber').text = startNum.toString();
startNum++;
}
}
#startnumber is a text element that goes from 0 to 145
I want to do the same with 3 more elements #startnumber2, 3, and 4.
this is what I'm trying to do
The counting logic can be extracted to a function so you could call it for all the text components.
$w.onReady(function () {
count($w('#text1'), 0, 150, 1000);
count($w('#text2'), 0, 250, 1000);
count($w('#text3'), 0, 500, 1000);
count($w('#text4'), 0, 1000, 1000);
});
function count(obj, start, end, duration) {
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
obj.text = `${Math.floor(progress * (end - start) + start)}`;
if (progress < 1) {
requestAnimationFrame(step);
}
};
requestAnimationFrame(step);
}
Demo: https://moshef9.wixsite.com/count-numbers
The counter function is taken from: https://stackoverflow.com/a/60291224/863110

What Should I Do If a HUAWEI Quick App Freezes When the canvas Component Draws an Image Using setInterval?

In my huawei quick app, the setInterval function is used to cyclically execute the code for using canvas. However, the quick app freezes when rendering an image on a Huawei phone.
The code where the exception occurs is as follows:
click0() {
this.speed = 0.3
let ctx = this.$element('canvas').getContext('2d')
setInterval(() => {
this.num0 += 2
this.noise = Math.min(0.5, 1) * this.MAX
this._draw(ctx)
this.MAX <= 200 && (this.MAX += 4)
}, 20)
},
_draw(ctx) {
this.phase = (this.phase + this.speed) % (Math.PI * 64)
ctx.clearRect(0, 0, this.width, this.height)
this._drawLine(ctx, -2, 'rgba(0, 194, 255, 0.2)')
this._drawLine(ctx, -6, 'rgba(0, 194, 255, 0.4)')
this._drawLine(ctx, 4, 'rgba(0, 194, 255, 0.6)')
this._drawLine(ctx, 2, 'rgba(0, 194, 255, 0.8)')
this._drawLine(ctx, 1, 'rgba(0, 194, 255, 1)', 4)
},
You can first obtain the service provider by calling the API for querying device information to determine whether the quick app is supported by Huawei Quick App Loader.
If so, set the time interval to longer than 100 ms. The sample code is as follows:
onShow: function () {
var that = this
device.getInfo({
success: function (ret) {
console.log("handling success:", JSON.stringify(ret));
that.engineProvider = ret.engineProvider;
},
fail: function (erromsg, errocode) {
console.log("message:", erromsg, errocode);
}
})
},
click0() {
var that = this
this.speed = 0.3
console.log(that.engineProvider)
let ctx = this.$element('canvas').getContext('2d')
if (that.engineProvider === "huawei") {
setInterval(() => {
this.num0 += 2
this.noise = Math.min(0.5, 1) * this.MAX
this._draw(ctx)
this.MAX <= 200 && (this.MAX += 4)
}, 120)
} else {
setInterval(() => {
this.num0 += 2
this.noise = Math.min(0.5, 1) * this.MAX
this._draw(ctx)
this.MAX <= 200 && (this.MAX += 4)
}, 20)
}
},
_draw(ctx) {
this.phase = (this.phase + this.speed) % (Math.PI * 64)
ctx.clearRect(0, 0, this.width, this.height)
this._drawLine(ctx, -2, 'rgba(0, 194, 255, 0.2)')
this._drawLine(ctx, -6, 'rgba(0, 194, 255, 0.4)')
this._drawLine(ctx, 4, 'rgba(0, 194, 255, 0.6)')
this._drawLine(ctx, 2, 'rgba(0, 194, 255, 0.8)')
this._drawLine(ctx, 1, 'rgba(0, 194, 255, 1)', 4)
},
_drawLine(ctx, attenuation, color, width) {
ctx.save()
ctx.moveTo(0, 0);
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = width || 1;
var x, y;
for (var i = -this.K; i <= this.K; i += 0.01) {
x = this.width * ((i + this.K) / (this.K * 2))
y = this.height / 2 + this.noise * this._globalAttenuationFn(i) * (1 / attenuation) * Math.sin(this.F * i - this.phase)
ctx.lineTo(x, y)
}
ctx.stroke()
ctx.restore()
},
For Details,pls kindly refer:
Introduction to the canvas API
Quick app materials

In Mapbox GL is there a way to have the labels for symbols be custom HTML

I'm creating a symbol layer with the layer symbols using custom images which is working well.
I also want to create custom labels with HTML (basically a blue background with an eased border and the label) but I'm unsure whether that's possible or how. I'm including what I'm using right now to render the points, the get icons renders the custom images for each point which are loaded in advance using map.loadImage.
map.addLayer({
id: 'points',
type: 'symbol',
source: 'points',
paint: {
"text-color": "#ffffff",
},
layout: {
'icon-image': ['get', 'icon'], // 'cat',
'icon-size': 1,
'icon-allow-overlap': true,
'text-field': ['get', 'name'],
'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
'text-offset': [0, 2.00],
'text-size': 14,
'text-anchor': 'top',
'text-allow-overlap': false,
},
})
You can't use HTML in symbol layers. You can:
Use Marker objects instead of symbol layers.
Use formatted content within symbol layers, with a mixture of fonts, font weights etc.
Use 9-part images to make custom borders.
This is almost what I've done in the past for a house rental agency.
Basically, I had to show a label with a price, whether the house had 3d pictures, add a blue background ...
Below is the source code that you can modify to satisfy your needs
What I did, is code all the infos in "icon-image" like this:
...
'icon-image': ['concat', 'projectmarker|', ['get', 'id'], '|', ['get', 'price'], '|', ['get', '3d'], '|', ['get', 'name'], '|', ['get', 'highlight']]
...
What happens then is that mapbox does not find the image and calls a "styleimagemissing" callback which does all the work using the element and converting it to a dataimage at the very end
const missingImages = [];
map.on('styleimagemissing', function (e) {
const id = e.id;
const blue = '#1f2d41';
const white = '#ffffff';
const yellow = '#a3a326';
// only create once
if (missingImages.indexOf(id) !== -1) return;
missingImages.push(id);
// check if this missing icon is one this function can generate
if (id.indexOf('projectmarker') !== 0) return;
// extract infos
const projectId = parseInt((id.split('|')[1]));
let price = parseInt((id.split('|')[2])).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const hasPrice = price !== "0";
const threeD = 'true' === (id.split('|')[3]);
const highlight = '1' === (id.split('|')[5]);
if (!hasPrice) {
price = id.split('|')[4];
} else {
price += ' ' + currencyCode;
}
// create canvas
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const height = 20;
const leftWidth = 40;
const rightWidth = (8 * price.length) + 10;
const leftBg = blue;
const rightBg = highlight ? yellow : white;
const radius = 4;
if (threeD) {
// 3d bg
ctx.fillStyle = leftBg;
roundRect(ctx, 0, 0, leftWidth, height, {tl: radius, tr: 0, br: 0, bl: radius}, true, true);
// 3d text
ctx.textAlign = "center";
ctx.font = "bold 14px Arial";
ctx.fillStyle = white;
ctx.fillText('360°', leftWidth / 2, 16);
// price bg
ctx.fillStyle = rightBg;
roundRect(ctx, leftWidth, 0, rightWidth, height, {tl: 0, tr: radius, br: radius, bl: 0}, true, true);
} else {
// price bg
ctx.fillStyle = rightBg;
roundRect(ctx, 0, 0, rightWidth, height, radius, true, true);
}
// price
ctx.textAlign = "center";
ctx.font = "14px Arial";
ctx.fillStyle = blue;
ctx.fillText(price.replace(',', ' '), (threeD ? leftWidth : 0) + (rightWidth / 2), 15);
// extract data and create mapbox image
const imageData = ctx.getImageData(0, 0, (threeD ? leftWidth : 0) + rightWidth, height);
map.addImage(id, imageData);
});
Below is the roundRect helper
const roundRect = (ctx, x, y, width, height, radius, fill, stroke) => {
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
const defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (let side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}

Why need get/set when using Computed Properties in Swift, while we can code without them actually?

Here's an example from Official Guide by Apple:
struct​ ​Point​ {
​ ​var​ ​x​ = ​0.0​, ​y​ = ​0.0
​}
​struct​ ​Size​ {
​ ​var​ ​width​ = ​0.0​, ​height​ = ​0.0
​}
​struct​ ​Rect​ {
​ ​var​ ​origin​ = ​Point​()
​ ​var​ ​size​ = ​Size​()
​ ​var​ ​center​: ​Point​ {
​ ​get​ {
​ ​let​ ​centerX​ = ​origin​.​x​ + (​size​.​width​ / ​2​)
​ ​let​ ​centerY​ = ​origin​.​y​ + (​size​.​height​ / ​2​)
​ ​return​ ​Point​(​x​: ​centerX​, ​y​: ​centerY​)
​ }
​ ​set​(​newCenter​) {
​ ​origin​.​x​ = ​newCenter​.​x​ - (​size​.​width​ / ​2​)
​ ​origin​.​y​ = ​newCenter​.​y​ - (​size​.​height​ / ​2​)
​ }
​ }
​}
​var​ ​square​ = ​Rect​(​origin​: ​Point​(​x​: ​0.0​, ​y​: ​0.0​),
​ ​size​: ​Size​(​width​: ​10.0​, ​height​: ​10.0​))
​let​ ​initialSquareCenter​ = ​square​.​center
​square​.​center​ = ​Point​(​x​: ​15.0​, ​y​: ​15.0​)
​println​(​"square.origin is now at (​\(​square​.​origin​.​x​)​, ​\(​square​.​origin​.​y​)​)"​)
​// prints "square.origin is now at (10.0, 10.0)”
This make me pretty confused why we need get/set here, even we actually can do without them, then I tried:
struct anotherRect {
var origin = Point()
var size = Size()
var center: Point{
return Point(x: (origin.x + size.width/2), y: (origin.y + size.height/2))
}
}
var anotherSquare = Rect(origin: Point(x: 20.0, y: 20.0), size: Size(width: 10.0, height: 10.0))
println("anotherSquare.center is (\(anotherSquare.center.x), \(anotherSquare.center.y))")
// prints "anotherSquare.center is (25.0, 25.0)"
anotherSquare.center = Point(x: 30.0, y: 30.0)
println("anotherSquare.origin is now at (\(anotherSquare.origin.x), \(anotherSquare.origin.y))")
// prints "anotherSquare.origin is now at (25.0, 25.0)"
With this, I can do get value or set a new value to the computed properties, exactly the same get/set by Apple's way. Do we really need get/set in our code? and Why?
I'm just a newcomer, so feel free let me know all about this stuff? I really appriciate your help!!
They are not the same. If you don't specify the get set keywords, you only implement the getter.
struct AnotherRect {
var origin = Point()
var size = Size()
var center: Point {
return Point(x: (origin.x + size.width/2), y: (origin.y + size.height/2))
}
}
is equivalent to
struct AnotherRect {
var origin = Point()
var size = Size()
var center: Point {
get {
return Point(x: (origin.x + size.width/2), y: (origin.y + size.height/2))
}
}
}
Your example got you confused because you have an error in your code:
var anotherSquare = Rect(origin: Point(x: 20.0, y: 20.0), size: Size(width: 10.0, height: 10.0))
should be
var anotherSquare = AnotherRect(origin: Point(x: 20.0, y: 20.0), size: Size(width: 10.0, height: 10.0))
Try that and you will notice that you cannot assign to the center property.
Also, the convention is that you name your custom types starting with a capital letter, e.g.: AnotherRect.