@woosh/meep-engine 2.169.4 → 2.169.6
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/package.json +1 -1
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.d.ts.map +1 -1
- package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +57 -18
- package/src/engine/graphics/shaders/TerrainShader.js +5 -18
- package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.d.ts +16 -0
- package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.d.ts.map +1 -0
- package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -0
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.d.ts +22 -0
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.d.ts.map +1 -0
- package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -0
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"description": "Pure JavaScript game engine. Fully featured and production ready.",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"author": "Alexander Goldring",
|
|
9
|
-
"version": "2.169.
|
|
9
|
+
"version": "2.169.6",
|
|
10
10
|
"main": "build/meep.module.js",
|
|
11
11
|
"module": "build/meep.module.js",
|
|
12
12
|
"exports": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TerrainLayers.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/ecs/terrain/ecs/layers/TerrainLayers.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TerrainLayers.d.ts","sourceRoot":"","sources":["../../../../../../../src/engine/ecs/terrain/ecs/layers/TerrainLayers.js"],"names":[],"mappings":"AA0GA;IAGQ;;;OAGG;IACH,QAFU,KAAK,YAAY,CAAC,CAEJ;IAExB;;;OAGG;IACH,YAFU,OAAO,CAEoD;IAMrE;;;OAGG;IACH,SAFU,kBAAkB,CAE8B;IAuB1D,2BAAsF;IAc1F;;;;;;MAKC;IAED;;;aAGC;IAED;;;;OAIG;IACH,gCAHW,MAAM,iBACN,MAAM,QAuChB;IAED;;;;OAIG;IACH,0DAyBC;IAED,sCAIC;IAED;;;;OAIG;IACH,WAHW,MAAM,GACL,YAAY,CAIvB;IAED;;;;OAIG;IACH,8DAFa,MAAM,CAqBlB;IAED;;;;OAIG;IACH,kCAHW,MAAM,GACJ,MAAM,CAMlB;IAED;;;OAGG;IACH,SAFY,MAAM,CAIjB;IAED;;;;OAIG;IACH,gBAHW,YAAY,GACV,MAAM,CAWlB;IAED;;;;OAIG;IACH,mBAHW,YAAY,GACX,OAAO,CAIlB;IAED;;;;;;OAMG;IACH,0CAqCC;IAED;;;OAGG;IACH,iCAFW,MAAM,QAgChB;IAED,gBAGC;IAED,cAEC;IAGD,qBA4CC;CACJ;iBA3cgB,6CAA6C;6BAajC,mBAAmB;oBAZ5B,qCAAqC;mCALlD,OAAO;4BAAP,OAAO"}
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
DataTexture,
|
|
4
4
|
DataTexture2DArray,
|
|
5
5
|
FloatType,
|
|
6
|
-
LinearEncoding,
|
|
7
6
|
LinearFilter,
|
|
8
7
|
LinearMipMapLinearFilter,
|
|
9
8
|
NearestFilter,
|
|
10
9
|
RepeatWrapping,
|
|
11
10
|
RGBAFormat,
|
|
12
11
|
RGFormat,
|
|
12
|
+
sRGBEncoding,
|
|
13
13
|
UnsignedByteType
|
|
14
14
|
} from "three";
|
|
15
15
|
import { assert } from "../../../../../core/assert.js";
|
|
@@ -22,7 +22,12 @@ import { invokeObjectHash } from "../../../../../core/model/object/invokeObjectH
|
|
|
22
22
|
import { computeStringHash } from "../../../../../core/primitives/strings/computeStringHash.js";
|
|
23
23
|
import { sampler2d_scale } from "../../../../graphics/texture/sampler/resize/sampler2d_scale.js";
|
|
24
24
|
import { Sampler2D } from "../../../../graphics/texture/sampler/Sampler2D.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
sampler2d_linear_f16_to_srgb_u8
|
|
27
|
+
} from "../../../../graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js";
|
|
28
|
+
import {
|
|
29
|
+
sampler2d_srgb_u8_to_linear_f16
|
|
30
|
+
} from "../../../../graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js";
|
|
26
31
|
import { TerrainLayer } from "./TerrainLayer.js";
|
|
27
32
|
|
|
28
33
|
class ScaledTextureKey {
|
|
@@ -74,6 +79,29 @@ const scaled_texture_cache = new Cache({
|
|
|
74
79
|
}
|
|
75
80
|
});
|
|
76
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Resample an sRGB uint8 RGBA layer sampler to the given size.
|
|
84
|
+
*
|
|
85
|
+
* Resize in linear light. Averaging sRGB-encoded bytes mixes in gamma space and
|
|
86
|
+
* comes out too dark, the same defect the shader used to have. Float16 is the
|
|
87
|
+
* intermediate so the round trip through linear costs no precision against the
|
|
88
|
+
* 8-bit source, at half the footprint of float32.
|
|
89
|
+
*
|
|
90
|
+
* @param {Sampler2D} sampler
|
|
91
|
+
* @param {number} width
|
|
92
|
+
* @param {number} height
|
|
93
|
+
* @returns {Sampler2D}
|
|
94
|
+
*/
|
|
95
|
+
function rescale_layer_sampler(sampler, width, height) {
|
|
96
|
+
const linear = sampler2d_srgb_u8_to_linear_f16(sampler);
|
|
97
|
+
|
|
98
|
+
const scaled_linear = Sampler2D.float16(4, width, height);
|
|
99
|
+
|
|
100
|
+
sampler2d_scale(linear, scaled_linear);
|
|
101
|
+
|
|
102
|
+
return sampler2d_linear_f16_to_srgb_u8(scaled_linear);
|
|
103
|
+
}
|
|
104
|
+
|
|
77
105
|
const DEFAULT_RESOLUTION = 512;
|
|
78
106
|
|
|
79
107
|
export class TerrainLayers {
|
|
@@ -112,8 +140,14 @@ export class TerrainLayers {
|
|
|
112
140
|
this.texture.minFilter = LinearMipMapLinearFilter;
|
|
113
141
|
|
|
114
142
|
this.texture.generateMipmaps = true;
|
|
115
|
-
|
|
116
|
-
|
|
143
|
+
|
|
144
|
+
// Layer diffuse maps are authored in sRGB. Using an sRGB internal format makes the
|
|
145
|
+
// hardware decode each texel to linear BEFORE filtering, so bilinear/trilinear taps
|
|
146
|
+
// and the splat weight blend in TerrainShader all average in linear space.
|
|
147
|
+
// Consequence: the shader must NOT decode again.
|
|
148
|
+
this.texture.encoding = sRGBEncoding;
|
|
149
|
+
this.texture.internalFormat = 'SRGB8_ALPHA8';
|
|
150
|
+
|
|
117
151
|
this.texture.needsUpdate = true;
|
|
118
152
|
|
|
119
153
|
this.scalesTexture = new DataTexture(new Float32Array(64), 32, 1, RGFormat, FloatType);
|
|
@@ -321,27 +355,32 @@ export class TerrainLayers {
|
|
|
321
355
|
if (layerSampler.width === resolution_x && layerSampler.height === resolution_y) {
|
|
322
356
|
//layer's data matches texture dimensions, we can just copy the data directly
|
|
323
357
|
return layer.diffuse;
|
|
324
|
-
}
|
|
358
|
+
}
|
|
325
359
|
|
|
326
|
-
|
|
327
|
-
const key = ScaledTextureKey.from(layer.textureDiffuseURL, resolution);
|
|
328
|
-
const existing = scaled_texture_cache.get(key);
|
|
360
|
+
const uri = layer.textureDiffuseURL;
|
|
329
361
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
362
|
+
// The cache identifies pixels by their source URL, which only works for layers that
|
|
363
|
+
// were actually loaded from one. A layer whose diffuse sampler is assigned
|
|
364
|
+
// programmatically keeps the default empty URL, and every such layer would collide on
|
|
365
|
+
// ("", resolution) and silently get the first one's pixels back. Those layers rescale
|
|
366
|
+
// on every call instead; nothing about the URL-backed path changes.
|
|
367
|
+
if (uri === "") {
|
|
368
|
+
return rescale_layer_sampler(layerSampler, resolution_x, resolution_y);
|
|
369
|
+
}
|
|
333
370
|
|
|
334
|
-
|
|
371
|
+
// check cache
|
|
372
|
+
const key = ScaledTextureKey.from(uri, resolution);
|
|
373
|
+
const existing = scaled_texture_cache.get(key);
|
|
335
374
|
|
|
336
|
-
|
|
375
|
+
if (existing !== null) {
|
|
376
|
+
return existing;
|
|
377
|
+
}
|
|
337
378
|
|
|
338
|
-
|
|
339
|
-
const rgba = sampler2d_ensure_uint8_RGBA(scaledLayerSampler);
|
|
379
|
+
const rgba = rescale_layer_sampler(layerSampler, resolution_x, resolution_y);
|
|
340
380
|
|
|
341
|
-
|
|
381
|
+
scaled_texture_cache.put(key, rgba);
|
|
342
382
|
|
|
343
|
-
|
|
344
|
-
}
|
|
383
|
+
return rgba;
|
|
345
384
|
}
|
|
346
385
|
|
|
347
386
|
/**
|
|
@@ -161,19 +161,6 @@ function fragment() {
|
|
|
161
161
|
|
|
162
162
|
${ShaderChunks.random}
|
|
163
163
|
|
|
164
|
-
vec4 blendSplatTextures(const in vec4 tex0, const in vec4 tex1, const in vec4 tex2, const in vec4 tex3, const in vec4 splat){
|
|
165
|
-
|
|
166
|
-
float b0 = splat[0];
|
|
167
|
-
|
|
168
|
-
float b1 = splat[1];
|
|
169
|
-
|
|
170
|
-
float b2 = splat[2];
|
|
171
|
-
|
|
172
|
-
float b3 = splat[3];
|
|
173
|
-
|
|
174
|
-
return (tex0*b0 + tex1*b1 + tex2*b2 + tex3*b3) / (b0 + b1 + b2 + b3);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
164
|
void randomBlend(in vec2 uv, inout vec3 v, float level){
|
|
178
165
|
vec3 noise = vec3( rand(uv.x*12.9898 + uv.y*78.233), rand(uv.x*10.9898 + uv.y*73.233) , rand(uv.x*6.9898 + uv.y*71.233));
|
|
179
166
|
v = v*(1.0 - level) + noise*level;
|
|
@@ -264,8 +251,10 @@ function fragment() {
|
|
|
264
251
|
return tile_color * tile_data;
|
|
265
252
|
}
|
|
266
253
|
|
|
254
|
+
// Returns linear-space colour: diffuseMaps uses an sRGB internal format, so texture()
|
|
255
|
+
// hands back already-decoded texels and the weighted average below is done in linear space.
|
|
267
256
|
vec4 computeSplatMix(vec2 uv){
|
|
268
|
-
|
|
257
|
+
|
|
269
258
|
float weightSum = 0.0;
|
|
270
259
|
vec4 colorSum = vec4(0.0);
|
|
271
260
|
|
|
@@ -291,11 +280,9 @@ function fragment() {
|
|
|
291
280
|
out highp vec4 pc_fragColor;
|
|
292
281
|
|
|
293
282
|
void main() {
|
|
283
|
+
// already linear, the sampler decoded it
|
|
294
284
|
vec4 splatDiffuseColor = computeSplatMix(vUv);
|
|
295
|
-
|
|
296
|
-
//decode the texture
|
|
297
|
-
splatDiffuseColor = sRGBToLinear( splatDiffuseColor );
|
|
298
|
-
|
|
285
|
+
|
|
299
286
|
//ensure it is fully opaque
|
|
300
287
|
splatDiffuseColor.a = 1.0;
|
|
301
288
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encodes a linear-light RGBA image back to 8-bit sRGB.
|
|
3
|
+
*
|
|
4
|
+
* Reverse of {@link sampler2d_srgb_u8_to_linear_f16}, and the last step of a linear-space
|
|
5
|
+
* resize. The conversion saturates: linear values outside [0,1] are clamped rather than
|
|
6
|
+
* wrapped, since the 8-bit output cannot represent them.
|
|
7
|
+
*
|
|
8
|
+
* The alpha channel is NOT transfer-encoded, it is merely scaled back to 0..255.
|
|
9
|
+
*
|
|
10
|
+
* @param {Sampler2D} input RGBA, linear light. Any numeric backing store, float16 in the resize path.
|
|
11
|
+
* @returns {Sampler2D} 8-bit RGBA, sRGB-encoded
|
|
12
|
+
* @throws {Error} if the input is not 4-channel
|
|
13
|
+
*/
|
|
14
|
+
export function sampler2d_linear_f16_to_srgb_u8(input: Sampler2D): Sampler2D;
|
|
15
|
+
import { Sampler2D } from "./Sampler2D.js";
|
|
16
|
+
//# sourceMappingURL=sampler2d_linear_f16_to_srgb_u8.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampler2d_linear_f16_to_srgb_u8.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;GAYG;AACH,uDAJW,SAAS,GACP,SAAS,CA8BrB;0BAnDyB,gBAAgB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { convert_channel_linear_to_sRGB } from "../../../../core/color/sRGB/linear_to_sRGB.js";
|
|
2
|
+
import { clamp } from "../../../../core/math/clamp.js";
|
|
3
|
+
import { Sampler2D } from "./Sampler2D.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param {number} linear
|
|
8
|
+
* @returns {number} sRGB-encoded byte, 0..255
|
|
9
|
+
*/
|
|
10
|
+
function encode_channel(linear) {
|
|
11
|
+
return Math.round(clamp(convert_channel_linear_to_sRGB(linear), 0, 1) * 255);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Encodes a linear-light RGBA image back to 8-bit sRGB.
|
|
16
|
+
*
|
|
17
|
+
* Reverse of {@link sampler2d_srgb_u8_to_linear_f16}, and the last step of a linear-space
|
|
18
|
+
* resize. The conversion saturates: linear values outside [0,1] are clamped rather than
|
|
19
|
+
* wrapped, since the 8-bit output cannot represent them.
|
|
20
|
+
*
|
|
21
|
+
* The alpha channel is NOT transfer-encoded, it is merely scaled back to 0..255.
|
|
22
|
+
*
|
|
23
|
+
* @param {Sampler2D} input RGBA, linear light. Any numeric backing store, float16 in the resize path.
|
|
24
|
+
* @returns {Sampler2D} 8-bit RGBA, sRGB-encoded
|
|
25
|
+
* @throws {Error} if the input is not 4-channel
|
|
26
|
+
*/
|
|
27
|
+
export function sampler2d_linear_f16_to_srgb_u8(input) {
|
|
28
|
+
if (input.itemSize !== 4) {
|
|
29
|
+
throw new Error(`Input must have 4 channels exactly, instead got '${input.itemSize}'`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const width = input.width;
|
|
33
|
+
const height = input.height;
|
|
34
|
+
|
|
35
|
+
const output = Sampler2D.uint8(4, width, height);
|
|
36
|
+
|
|
37
|
+
const source = input.data;
|
|
38
|
+
const destination = output.data;
|
|
39
|
+
|
|
40
|
+
const texel_count = width * height;
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < texel_count; i++) {
|
|
43
|
+
const address = i * 4;
|
|
44
|
+
|
|
45
|
+
destination[address] = encode_channel(source[address]);
|
|
46
|
+
destination[address + 1] = encode_channel(source[address + 1]);
|
|
47
|
+
destination[address + 2] = encode_channel(source[address + 2]);
|
|
48
|
+
|
|
49
|
+
// alpha carries no transfer function
|
|
50
|
+
destination[address + 3] = Math.round(clamp(source[address + 3], 0, 1) * 255);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return output;
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decodes an 8-bit sRGB RGBA image into linear light, stored as float16.
|
|
3
|
+
*
|
|
4
|
+
* Intended as the first step of a resize: averaging sRGB-encoded values is wrong, because
|
|
5
|
+
* the encoding is non-linear, and the result comes out too dark. Decode first, resize in
|
|
6
|
+
* linear, then re-encode with {@link sampler2d_linear_f16_to_srgb_u8}.
|
|
7
|
+
*
|
|
8
|
+
* Float16 holds linear values in [0,1] with ~3 decimal digits of precision, which is finer
|
|
9
|
+
* than the 8-bit encoding it came from and half the footprint of float32. Note that V8 does
|
|
10
|
+
* not yet specialize Float16Array element access, so this is not a good fit for a per-frame
|
|
11
|
+
* hot loop — it is meant for load-time work whose result gets cached.
|
|
12
|
+
*
|
|
13
|
+
* The alpha channel is NOT transfer-encoded, matching how the GPU treats SRGB8_ALPHA8: only
|
|
14
|
+
* RGB goes through the curve, alpha is already linear and is merely normalized to [0,1].
|
|
15
|
+
*
|
|
16
|
+
* @param {Sampler2D} input 8-bit RGBA, sRGB-encoded
|
|
17
|
+
* @returns {Sampler2D} RGBA float16, linear light, values in [0,1]
|
|
18
|
+
* @throws {Error} if the input is not 4-channel, or is not 8-bit
|
|
19
|
+
*/
|
|
20
|
+
export function sampler2d_srgb_u8_to_linear_f16(input: Sampler2D): Sampler2D;
|
|
21
|
+
import { Sampler2D } from "./Sampler2D.js";
|
|
22
|
+
//# sourceMappingURL=sampler2d_srgb_u8_to_linear_f16.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampler2d_srgb_u8_to_linear_f16.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js"],"names":[],"mappings":"AAcA;;;;;;;;;;;;;;;;;;GAkBG;AACH,uDAJW,SAAS,GACP,SAAS,CAqCrB;0BAlEyB,gBAAgB"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { convert_channel_sRGB_to_linear } from "../../../../core/color/sRGB/sRGB_to_linear.js";
|
|
2
|
+
import { Sampler2D } from "./Sampler2D.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Decoded linear value for every possible sRGB byte. The input domain is only 256 values
|
|
6
|
+
* wide, so the transfer function is evaluated once per value instead of once per channel.
|
|
7
|
+
* @type {Float32Array}
|
|
8
|
+
*/
|
|
9
|
+
const SRGB_BYTE_TO_LINEAR = new Float32Array(256);
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < 256; i++) {
|
|
12
|
+
SRGB_BYTE_TO_LINEAR[i] = convert_channel_sRGB_to_linear(i / 255);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Decodes an 8-bit sRGB RGBA image into linear light, stored as float16.
|
|
17
|
+
*
|
|
18
|
+
* Intended as the first step of a resize: averaging sRGB-encoded values is wrong, because
|
|
19
|
+
* the encoding is non-linear, and the result comes out too dark. Decode first, resize in
|
|
20
|
+
* linear, then re-encode with {@link sampler2d_linear_f16_to_srgb_u8}.
|
|
21
|
+
*
|
|
22
|
+
* Float16 holds linear values in [0,1] with ~3 decimal digits of precision, which is finer
|
|
23
|
+
* than the 8-bit encoding it came from and half the footprint of float32. Note that V8 does
|
|
24
|
+
* not yet specialize Float16Array element access, so this is not a good fit for a per-frame
|
|
25
|
+
* hot loop — it is meant for load-time work whose result gets cached.
|
|
26
|
+
*
|
|
27
|
+
* The alpha channel is NOT transfer-encoded, matching how the GPU treats SRGB8_ALPHA8: only
|
|
28
|
+
* RGB goes through the curve, alpha is already linear and is merely normalized to [0,1].
|
|
29
|
+
*
|
|
30
|
+
* @param {Sampler2D} input 8-bit RGBA, sRGB-encoded
|
|
31
|
+
* @returns {Sampler2D} RGBA float16, linear light, values in [0,1]
|
|
32
|
+
* @throws {Error} if the input is not 4-channel, or is not 8-bit
|
|
33
|
+
*/
|
|
34
|
+
export function sampler2d_srgb_u8_to_linear_f16(input) {
|
|
35
|
+
if (input.itemSize !== 4) {
|
|
36
|
+
throw new Error(`Input must have 4 channels exactly, instead got '${input.itemSize}'`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const source_constructor = input.data.constructor;
|
|
40
|
+
|
|
41
|
+
if (source_constructor !== Uint8Array && source_constructor !== Uint8ClampedArray) {
|
|
42
|
+
// the decode is a 256-entry lookup, a wider type would index past the end of it
|
|
43
|
+
throw new Error(`Input must be 8-bit, instead got '${source_constructor.name}'`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const width = input.width;
|
|
47
|
+
const height = input.height;
|
|
48
|
+
|
|
49
|
+
const output = Sampler2D.float16(4, width, height);
|
|
50
|
+
|
|
51
|
+
const source = input.data;
|
|
52
|
+
const destination = output.data;
|
|
53
|
+
|
|
54
|
+
const texel_count = width * height;
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < texel_count; i++) {
|
|
57
|
+
const address = i * 4;
|
|
58
|
+
|
|
59
|
+
destination[address] = SRGB_BYTE_TO_LINEAR[source[address]];
|
|
60
|
+
destination[address + 1] = SRGB_BYTE_TO_LINEAR[source[address + 1]];
|
|
61
|
+
destination[address + 2] = SRGB_BYTE_TO_LINEAR[source[address + 2]];
|
|
62
|
+
|
|
63
|
+
// alpha carries no transfer function
|
|
64
|
+
destination[address + 3] = source[address + 3] / 255;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return output;
|
|
68
|
+
}
|