first commit

This commit is contained in:
aap 2020-07-29 22:10:38 +02:00
parent 2d2119a869
commit bea1a1c2a0
11 changed files with 9356 additions and 0 deletions

2
README.md Normal file
View File

@ -0,0 +1,2 @@
Does not come with data.
See it in action [here](http://gta.rockstarvision.com/vehicleviewer/).

6888
gl-matrix.js Normal file

File diff suppressed because it is too large Load Diff

105
gl-util.js Normal file
View File

@ -0,0 +1,105 @@
var gl;
const ATTRIB_POS = 0;
const ATTRIB_NORMAL = 1;
const ATTRIB_COLOR = 2;
const ATTRIB_TEXCOORDS0 = 3;
const ATTRIB_TEXCOORDS1 = 4;
function
loadTexture(url)
{
if(gl === undefined)
return null;
const texid = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texid);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,
1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
new Uint8Array([255, 255, 255, 255]));
const image = new Image();
image.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, texid);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,
gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
};
image.src = url;
return texid;
}
function
loadShaders(vs, fs)
{
const shaderProgram = initShaderProgram(vs, fs);
programInfo = {
program: shaderProgram,
a: [
gl.getAttribLocation(shaderProgram, 'in_pos'),
gl.getAttribLocation(shaderProgram, 'in_normal'),
gl.getAttribLocation(shaderProgram, 'in_color'),
gl.getAttribLocation(shaderProgram, 'in_tex0'),
gl.getAttribLocation(shaderProgram, 'in_tex1'),
],
u: {
u_proj: gl.getUniformLocation(shaderProgram, 'u_proj'),
u_view: gl.getUniformLocation(shaderProgram, 'u_view'),
u_world: gl.getUniformLocation(shaderProgram, 'u_world'),
u_env: gl.getUniformLocation(shaderProgram, 'u_env'),
u_matColor: gl.getUniformLocation(shaderProgram, 'u_matColor'),
u_surfaceProps: gl.getUniformLocation(shaderProgram, 'u_surfaceProps'),
u_ambLight: gl.getUniformLocation(shaderProgram, 'u_ambLight'),
u_lightDir: gl.getUniformLocation(shaderProgram, 'u_lightDir'),
u_lightCol: gl.getUniformLocation(shaderProgram, 'u_lightCol'),
u_alphaRef: gl.getUniformLocation(shaderProgram, 'u_alphaRef'),
u_tex0: gl.getUniformLocation(shaderProgram, 'tex0'),
u_tex1: gl.getUniformLocation(shaderProgram, 'tex1'),
u_tex2: gl.getUniformLocation(shaderProgram, 'tex2'),
},
};
console.log(programInfo.u.u_alphaRef);
return programInfo;
}
function
initShaderProgram(vsSource, fsSource)
{
const vertexShader = loadShader(gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if(!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)){
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return null;
}
return shaderProgram;
}
function
loadShader(type, source)
{
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}

83
index.html Normal file
View File

@ -0,0 +1,83 @@
<!doctype html>
<html lang="en">
<head>
<title>WebGL</title>
<meta charset="utf-8">
<link rel="stylesheet" href="webgl.css" type="text/css">
</head>
<body>
<div id="control">
<a href="javascript: startVehicleViewerIII();">III</a>
<a href="javascript: startVehicleViewerVC();">VC</a>
<a href="javascript: startVehicleViewerSA();">SA</a>
</div>
<canvas id="glcanvas" width="640" height="480" style="float:left"></canvas>
<div style="float:left">
<select id="objects" size="25">
<!-- JS inserts models here -->
</select>
</div>
<table id="colors" style="float:left">
<!-- JS inserts colors here -->
</table>
<div style="display:table;">
<ul id="frames">
<!-- JS inserts frames here -->
</ul>
</div>
<div style="clear:both;"></div>
</body>
<script src="gl-matrix.js"></script>
<script src="gl-util.js"></script>
<script src="rw.js"></script>
<script src="rwrender.js"></script>
<script src="shaders.js"></script>
<script src="rwstream.js"></script>
<script src="main.js"></script>
<script src="loaddata.js"></script>
<script>
InitRW();
function
startVehicleViewerIII()
{
DataDirPath = "iii/data";
ModelsDirPath = "iii/models";
TexturesDirPath = "iii/textures";
loadCar = loadCarIII;
loadVehicleViewer("default.ide", function() {
SelectModel("cheetah");
});
}
function
startVehicleViewerVC()
{
DataDirPath = "vc/data";
ModelsDirPath = "vc/models";
TexturesDirPath = "vc/textures";
loadCar = loadCarVC;
loadVehicleViewer("default.ide", function() {
SelectModel("cheetah");
});
}
function
startVehicleViewerSA()
{
DataDirPath = "sa/data";
ModelsDirPath = "sa/models";
TexturesDirPath = "sa/textures";
loadCar = loadCarSA;
loadVehicleViewer("vehicles.ide", function() {
SelectModel("cheetah");
});
}
startVehicleViewerIII();
</script>
</html>

213
loaddata.js Normal file
View File

@ -0,0 +1,213 @@
var VehicleColours = [];
var ModelInfos = {};
var ModelInfosName = {};
var CurrentModel = null;
function
SetCarColors(cols)
{
carColors = [
[ 0, 0, 0, 255 ],
[ 0, 0, 0, 255 ],
[ 0, 0, 0, 255 ],
[ 0, 0, 0, 255 ]
];
if(cols !== undefined)
for(let i = 0; i < cols.length; i++){
let col = VehicleColours[cols[i]];
carColors[i][0] = col[0];
carColors[i][1] = col[1];
carColors[i][2] = col[2];
}
}
function
LoadColors(cols)
{
SetCarColors(cols);
setVehicleColors(modelinfo, carColors[0], carColors[1], carColors[2], carColors[3]);
}
function
SelectModel(model)
{
let colortable = document.getElementById('colors');
removeChildren(colortable);
CurrentModel = ModelInfosName[model];
let col1 = [ 0, 0, 0, 255 ];
let col2 = [ 0, 0, 0, 255 ];
for(let i = 0; i < CurrentModel.colors.length; i++){
let c = CurrentModel.colors[i];
let c1 = VehicleColours[c[0]];
let c2 = VehicleColours[c[1]];
if(i == 0){
col1[0] = c1[0];
col1[1] = c1[1];
col1[2] = c1[2];
col2[0] = c2[0];
col2[1] = c2[1];
col2[2] = c2[2];
}
let tr = document.createElement('tr');
for(let j = 0; j < c.length; j++){
let td = document.createElement('td');
td.width = "16px";
td.height = "16px";
let col = VehicleColours[c[j]];
td.style = "background-color: rgb("+col[0]+","+col[1]+","+col[2]+")";
tr.appendChild(td);
}
tr.onclick = function() { LoadColors(c); };
colortable.appendChild(tr);
}
camDist = 5.0;
camPitch = 0.3;
camYaw = 1.0;
SetCarColors(CurrentModel.colors[0]);
loadCar(model + ".dff");
}
function
StartUI()
{
let objects = document.getElementById('objects');
removeChildren(objects);
for(let model in ModelInfosName){
let option = document.createElement('option');
option.innerHTML = model;
option.onclick = function(){ SelectModel(model); };
objects.appendChild(option);
}
}
function
LoadVehicle(fields)
{
let id = Number(fields[0]);
let mi = {};
mi.id = id;
mi.type = "vehicle";
mi.model = fields[1];
mi.txd = fields[2];
mi.vehtype = fields[3];
mi.handling = fields[4];
if(mi.vehtype == "car"){
// TODO: check SA
mi.wheelId = Number(fields[11]);
mi.wheelScale = Number(fields[12]);
}
mi.colors = [];
ModelInfos[mi.id] = mi;
ModelInfosName[mi.model] = mi;
}
function
LoadColour(fields)
{
let r = Number(fields[0]);
let g = Number(fields[1]);
let b = Number(fields[2]);
VehicleColours.push([r, g, b]);
}
function
LoadVehicleColour(fields)
{
let mi = ModelInfosName[fields[0]];
for(let i = 1; i < fields.length; i += 2){
let c1 = Number(fields[i]);
let c2 = Number(fields[i+1]);
mi.colors.push([c1, c2]);
}
}
function
LoadVehicleColour4(fields)
{
let mi = ModelInfosName[fields[0]];
for(let i = 1; i < fields.length; i += 4){
let c1 = Number(fields[i]);
let c2 = Number(fields[i+1]);
let c3 = Number(fields[i+2]);
let c4 = Number(fields[i+3]);
mi.colors.push([c1, c2, c3, c4]);
}
}
function
LoadObjectTypes(text)
{
LoadSectionedFile(text, {
"cars": LoadVehicle
});
}
function
LoadVehicleColours(text)
{
LoadSectionedFile(text, {
"col": LoadColour,
"car": LoadVehicleColour,
"car4": LoadVehicleColour4
});
}
function
LoadSectionedFile(text, sections)
{
let section = "end";
let lines = text.split("\n");
for(let i = 0; i < lines.length; i++){
let line = lines[i].replace(/,/g, " ").replace(/#.*/g, "").trim().toLowerCase();
if(line.length == 0)
continue;
let fields = line.split(/[\t ]+/);
if(section == "end"){
section = fields[0];
continue;
}
if(fields[0] == "end"){
section = "end";
continue;
}
if(section in sections)
sections[section](fields);
}
}
function
loadText(filename, cb)
{
let req = new XMLHttpRequest();
req.open("GET", DataDirPath + "/" + filename, true);
req.responseType = "text";
req.onload = function(oEvent){
cb(req.response);
};
req.send(null);
}
function
loadVehicleViewer(idefile, CB)
{
VehicleColours = [];
ModelInfos = {};
ModelInfosName = {};
CurrentModel = null;
loadText(idefile, function(text){
LoadObjectTypes(text);
loadText("carcols.dat", function(text){
LoadVehicleColours(text);
StartUI();
CB();
});
});
}

430
main.js Normal file
View File

@ -0,0 +1,430 @@
var DataDirPath;
var ModelsDirPath;
var TexturesDirPath;
// init info
var isIIICar;
var isSACar;
var carColors;
// the scene
var running = false;
var myclump;
var modelinfo;
var camPitch;
var camYaw;
var camDist;
// gl things
var state = {};
var whitetex;
var camera;
var envFrame;
var defaultProgram;
var envMapProgram;
var carPS2Program;
function deg2rad(d) { return d / 180.0 * Math.PI; }
var rotating, zooming;
function
mouseDown(e)
{
if(e.button == 0)
rotating = true;
else if(e.button == 1)
zooming = true;
old_x = e.pageX;
old_y = e.pageY;
e.preventDefault();
}
function
mouseUp(e)
{
rotating = false;
zooming = false;
}
function
mouseMove(e)
{
let dX, dY;
if(rotating){
dX = (e.pageX-old_x)*2*Math.PI/gl.canvas.width,
dY = (e.pageY-old_y)*2*Math.PI/gl.canvas.height;
camYaw -= dX;
camPitch += dY;
if(camPitch > Math.PI/2 - 0.01) camPitch = Math.PI/2 - 0.01
if(camPitch < -Math.PI/2 + 0.01) camPitch = -Math.PI/2 + 0.01
old_x = e.pageX;
old_y = e.pageY;
e.preventDefault();
}
if(zooming){
dY = (e.pageY-old_y)/gl.canvas.height;
camDist += dY;
if(camDist < 0.1) camDist = 0.1;
old_x = e.pageX;
e.preventDefault();
}
};
function
InitRW()
{
console.log("InitRW()");
let canvas = document.querySelector('#glcanvas');
gl = canvas.getContext('webgl');
if(!gl){
alert('Unable to initialize WebGL. Your browser or machine may not support it.');
return;
}
canvas.addEventListener("mousedown", mouseDown, false);
canvas.addEventListener("mouseup", mouseUp, false);
canvas.addEventListener("mouseout", mouseUp, false);
canvas.addEventListener("mousemove", mouseMove, false);
whitetex = loadTexture("textures/white.png");
defaultProgram = loadShaders(defaultVS, defaultFS);
envMapProgram = loadShaders(envVS, envFS);
carPS2Program = loadShaders(carPS2VS, carPS2FS);
state.alphaRef = 0.1;
state.projectionMatrix = mat4.create();
state.viewMatrix = mat4.create();
state.worldMatrix = mat4.create();
state.envMatrix = mat4.create();
state.matColor = vec4.create();
state.surfaceProps = vec4.create();
state.ambLight = vec3.fromValues(0.4, 0.4, 0.4);
const alpha = 45;
const beta = 45;
state.lightDir = vec3.fromValues(
-Math.cos(deg2rad(beta))*Math.cos(deg2rad(alpha)),
-Math.sin(deg2rad(beta))*Math.cos(deg2rad(alpha)),
-Math.sin(deg2rad(alpha))
);
state.lightCol = vec3.fromValues(1.0, 1.0, 1.0);
AttachPlugins();
camera = RwCameraCreate();
camera.nearPlane = 0.1;
camera.farPlane = 100.0;
let frm = RwFrameCreate();
RwCameraSetFrame(camera, frm);
const fov = deg2rad(70);
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
camera.viewWindow[1] = Math.tan(fov / 2);
camera.viewWindow[0] = camera.viewWindow[1]*aspect;
envFrame = RwFrameCreate();
mat4.rotateX(envFrame.matrix, envFrame.matrix, deg2rad(60));
rwFrameSynchLTM(envFrame);
}
function
displayFrames(frame, parelem)
{
let li = document.createElement('li');
li.innerHTML = frame.name;
for(let i = 0; i < frame.objects.length; i++){
let o = frame.objects[i];
if(o.type == rwID_ATOMIC){
let checkbox = document.createElement('input');
checkbox.type = "checkbox";
checkbox.onclick = function() { o.visible = checkbox.checked; };
checkbox.checked = o.visible;
li.appendChild(checkbox);
}
}
parelem.appendChild(li);
if(frame.child){
let ul = document.createElement('ul');
parelem.appendChild(ul);
for(let c = frame.child; c != null; c = c.next)
displayFrames(c, ul);
}
}
function
putControl()
{
// let ctl = document.getElementById('control');
// let reloadbtn = document.createElement('input');
// reloadbtn.type = "button";
// reloadbtn.value = "reload";
// reloadbtn.onclick = reload;
// ctl.appendChild(reloadbtn);
}
function
loadCarIII(filename)
{
loadDFF(filename, function(clump){
myclump = clump;
modelinfo = processVehicle(myclump);
setupIIICar(myclump);
setVehicleColors(modelinfo, carColors[0], carColors[1]);
main();
});
}
function
loadCarVC(filename)
{
loadDFF(filename, function(clump){
myclump = clump;
modelinfo = processVehicle(myclump);
setVehicleColors(modelinfo, carColors[0], carColors[1]);
main();
});
}
function
loadCarSA(filename)
{
loadDFF(filename, function(clump){
myclump = clump;
modelinfo = processVehicle(myclump);
setupSACar(myclump);
setVehicleColors(modelinfo,
carColors[0], carColors[1], carColors[2], carColors[3]);
// setVehicleLightColors(modelinfo,
// [ 128, 0, 0, 255 ],
// [ 128, 0, 0, 255 ],
// [ 128, 0, 0, 255 ],
// [ 128, 0, 0, 255 ]);
main();
});
}
function
loadModel(filename)
{
loadDFF(filename, function(clump){
myclump = clump;
main();
});
}
function
removeChildren(x)
{
while(x.firstChild)
x.removeChild(x.firstChild);
}
function
main()
{
let ul = document.getElementById('frames');
removeChildren(ul);
displayFrames(myclump.frame, ul);
if(!running){
running = true;
putControl();
let then = 0;
function render(now){
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
drawScene(deltaTime);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
}
function
setupIIICar(clump)
{
for(let i = 0; i < clump.atomics.length; i++){
let a = clump.atomics[i];
a.pipeline = matFXPipe;
for(let j = 0; j < a.geometry.materials.length; j++){
m = a.geometry.materials[j];
if(m.surfaceProperties[1] <= 0.0)
continue;
m.matfx = {
type: 2,
envCoefficient: m.surfaceProperties[1],
envTex: RwTextureRead("reflection01", "")
};
}
}
}
function
setupSACar(clump)
{
for(let i = 0; i < clump.atomics.length; i++){
let a = clump.atomics[i];
a.pipeline = carPipe;
for(let j = 0; j < a.geometry.materials.length; j++){
m = a.geometry.materials[j];
m.fxFlags = 0;
if(!m.matfx || m.matfx.type != 2) continue;
if(m.matfx.envTex && m.envMap && m.envMap.shininess != 0){
m.envMap.texture = m.matfx.envTex;
if(m.envMap.texture.name[0] == 'x')
m.fxFlags |= 2;
else
m.fxFlags |= 1;
}
if(m.specMap && m.specMap.specularity != 0)
m.fxFlags |= 4;
}
}
}
function
setVehicleColors(vehinfo, c1, c2, c3, c4)
{
for(let i = 0; i < vehinfo.firstMaterials.length; i++)
vehinfo.firstMaterials[i].color = c1;
for(let i = 0; i < vehinfo.secondMaterials.length; i++)
vehinfo.secondMaterials[i].color = c2;
for(let i = 0; i < vehinfo.thirdMaterials.length; i++)
vehinfo.thirdMaterials[i].color = c3;
for(let i = 0; i < vehinfo.fourthMaterials.length; i++)
vehinfo.fourthMaterials[i].color = c4;
}
function
setVehicleLightColors(vehinfo, c1, c2, c3, c4)
{
for(let i = 0; i < vehinfo.firstLightMaterials.length; i++)
vehinfo.firstLightMaterials[i].color = c1;
for(let i = 0; i < vehinfo.secondLightMaterials.length; i++)
vehinfo.secondLightMaterials[i].color = c2;
for(let i = 0; i < vehinfo.thirdLightMaterials.length; i++)
vehinfo.thirdLightMaterials[i].color = c3;
for(let i = 0; i < vehinfo.fourthLightMaterials.length; i++)
vehinfo.fourthLightMaterials[i].color = c4;
}
function
findEditableMaterials(geo, vehinfo)
{
for(let i = 0; i < geo.materials.length; i++){
m = geo.materials[i];
if(m.color[0] == 0x3C && m.color[1] == 0xFF && m.color[2] == 0)
vehinfo.firstMaterials.push(m);
else if(m.color[0] == 0xFF && m.color[1] == 0 && m.color[2] == 0xAF)
vehinfo.secondMaterials.push(m);
else if(m.color[0] == 0 && m.color[1] == 0xFF && m.color[2] == 0xFF)
vehinfo.thirdMaterials.push(m);
else if(m.color[0] == 0xFF && m.color[1] == 0x00 && m.color[2] == 0xFF)
vehinfo.fourthMaterials.push(m);
else if(m.color[0] == 0xFF && m.color[1] == 0xAF && m.color[2] == 0)
vehinfo.firstLightMaterials.push(m);
else if(m.color[0] == 0 && m.color[1] == 0xFF && m.color[2] == 0xC8)
vehinfo.secondLightMaterials.push(m);
else if(m.color[0] == 0xB9 && m.color[1] == 0xFF && m.color[2] == 0)
vehinfo.thirdLightMaterials.push(m);
else if(m.color[0] == 0xFF && m.color[1] == 0x3C && m.color[2] == 0)
vehinfo.fourthLightMaterials.push(m);
}
}
function
processVehicle(clump)
{
let vehicleInfo = {
firstMaterials: [],
secondMaterials: [],
thirdMaterials: [],
fourthMaterials: [],
firstLightMaterials: [], // front left
secondLightMaterials: [], // front right
thirdLightMaterials: [], // back left
fourthLightMaterials: [], // back right
clump: clump
};
for(let i = 0; i < clump.atomics.length; i++){
a = clump.atomics[i];
f = a.frame;
if(f.name.endsWith("_dam") ||
f.name.endsWith("_lo") ||
f.name.endsWith("_vlo"))
a.visible = false;
findEditableMaterials(a.geometry, vehicleInfo);
}
return vehicleInfo;
}
function
drawScene(deltaTime)
{
// camYaw += deltaTime;
gl.clearColor(0.5, 0.5, 0.5, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
let x = camDist * Math.cos(camYaw)* Math.cos(camPitch);
let y = camDist * Math.sin(camYaw)* Math.cos(camPitch);
let z = camDist * Math.sin(camPitch);
RwFrameLookAt(camera.frame,
[ x, y, z ],
[ 0.0, 0.0, 0.0 ],
[ 0.0, 0.0, 1.0 ]);
rwFrameSynchLTM(camera.frame);
RwCameraBeginUpdate(camera);
RenderPass = 0;
RpClumpRender(myclump);
RenderPass = 1;
RpClumpRender(myclump);
RenderPass = -1;
}
function
loadDFF(filename, cb)
{
let req = new XMLHttpRequest();
req.open("GET", ModelsDirPath + "/" + filename, true);
req.responseType = "arraybuffer";
req.onload = function(oEvent){
let arrayBuffer = req.response;
if(arrayBuffer){
stream = RwStreamCreate(arrayBuffer);
if(RwStreamFindChunk(stream, rwID_CLUMP)){
let c = RpClumpStreamRead(stream);
if(c != null)
cb(c);
}
return null;
}
};
req.send(null);
}

556
rw.js Normal file
View File

@ -0,0 +1,556 @@
// core
var rwID_STRUCT = 0x01;
var rwID_STRING = 0x02;
var rwID_EXTENSION = 0x03;
var rwID_CAMERA = 0x05;
var rwID_TEXTURE = 0x06;
var rwID_MATERIAL = 0x07;
var rwID_MATLIST = 0x08;
var rwID_FRAMELIST = 0x0E;
var rwID_GEOMETRY = 0x0F;
var rwID_CLUMP = 0x10;
var rwID_LIGHT = 0x12;
var rwID_ATOMIC = 0x14;
var rwID_GEOMETRYLIST = 0x1A;
// tk
var rwID_HANIMPLUGIN = 0x11E;
var rwID_MATERIALEFFECTSPLUGIN = 0x120;
// world
var rwID_BINMESHPLUGIN = 0x50E;
// R*
var rwID_NODENAME = 0x0253F2FE;
var rwID_ENVMAT = 0x0253F2FC;
var rwID_SPECMAT = 0x0253F2F6;
var frameTKList = {};
var textureTKList = {};
var materialTKList = {};
var geometryTKList = {};
var atomicTKList = {};
var clumpTKList = {};
/* RwFrame */
function
rwSetHierarchyRoot(frame, root)
{
frame.root = root;
for(frame = frame.child; frame != null; frame = frame.next)
rwSetHierarchyRoot(frame, root);
}
function
RwFrameRemoveChild(c)
{
let f = c.parent.child;
// remove as child
if(f == c)
c.parent.child = c.next;
else{
while(f.next != c)
f = f.next;
f.next = c.next;
}
// now make this the root of a new hierarchy
c.parent = null;
c.next = null;
rwSetHierarchyRoot(c, c);
}
function
RwFrameAddChild(p, child)
{
if(child.parent != null)
RwFrameRemoveChild(child);
// append as child of p
if(p.child == null)
p.child = child;
else{
let c;
for(c = p.child; c.next != null; c = c.next);
c.next = child;
}
child.next = null;
child.parent = p;
rwSetHierarchyRoot(child, p.root);
}
function
rwFrameSynchLTM(f)
{
if(f.parent == null)
mat4.copy(f.ltm, f.matrix);
else
mat4.multiply(f.ltm, f.matrix, f.parent.ltm);
for(let c = f.child; c != null; c = c.next)
rwFrameSynchLTM(c);
}
function
RwFrameLookAt(frm, pos, target, up)
{
let at = vec3.create();
vec3.subtract(at, target, pos);
vec3.normalize(at, at);
let left = vec3.create();
vec3.cross(left, up, at);
vec3.normalize(left, left);
vec3.cross(up, at, left);
m = frm.matrix;
m[0] = left[0];
m[1] = left[1];
m[2] = left[2];
m[4] = up[0];
m[5] = up[1];
m[6] = up[2];
m[8] = at[0];
m[9] = at[1];
m[10] = at[2];
m[12] = pos[0];
m[13] = pos[1];
m[14] = pos[2];
}
function
RwFrameCreate()
{
let f = {
parent: null,
root: null,
child: null,
next: null,
matrix: mat4.create(),
ltm: mat4.create(),
objects: [],
name: ""
};
f.root = f;
mat4.copy(f.ltm, f.matrix);
return f;
}
/* RwCamera */
function
RwCameraCreate()
{
cam = {
type: rwID_CAMERA,
frame: null,
viewWindow: [ 1.0, 1.0 ],
viewOffset: [ 0.0, 0.0 ],
nearPlane: [ 0.5 ],
farPlane: [ 10.0 ],
fogPlane: [ 5.0 ],
projmat: mat4.create()
};
return cam;
}
function
RwCameraSetFrame(c, f)
{
c.frame = f;
f.objects.push(c);
}
function
RwCameraBeginUpdate(cam)
{
mat4.invert(state.viewMatrix, camera.frame.ltm);
state.viewMatrix[0] = -state.viewMatrix[0];
state.viewMatrix[4] = -state.viewMatrix[4];
state.viewMatrix[8] = -state.viewMatrix[8];
state.viewMatrix[12] = -state.viewMatrix[12];
p = cam.projmat;
let xscl = 1.0/cam.viewWindow[0];
let yscl = 1.0/cam.viewWindow[1];
let zscl = 1.0/(cam.farPlane-cam.nearPlane);
p[0] = xscl;
p[1] = 0;
p[2] = 0;
p[3] = 0;
p[4] = 0;
p[5] = yscl;
p[6] = 0;
p[7] = 0;
p[8] = cam.viewOffset[0]*xscl;
p[9] = cam.viewOffset[1]*yscl;
p[12] = -p[8];
p[13] = -p[9];
p[10] = (cam.farPlane+cam.nearPlane)*zscl;
p[11] = 1.0;
p[14] = -2.0*cam.nearPlane*cam.farPlane*zscl;
p[15] = 0.0;
mat4.copy(state.projectionMatrix, p);
}
/* RwTexture */
function
RwTextureRead(name, mask)
{
return {
name: name,
mask: mask,
tex: loadTexture(TexturesDirPath + "/" + name + ".png")
};
}
/* RpMaterial */
function
RpMaterialCreate()
{
let mat = {
color: [ 255, 255, 255, 255 ],
surfaceProperties: [ 1.0, 1.0, 1.0 ]
};
return mat;
}
/* RpGeometry */
function
RpGeometryCreate(flags, numMorphTargets)
{
let geo = {
numVertices: 0, // used for instancing
triangles: [],
morphTargets: [],
materials: [],
meshtype: 0,
meshes: [],
totalMeshIndices: 0 // used for instancing
};
let numTexCoords = (flags >> 16) & 0xFF;
if(numTexCoords == 0){
if(flags & 0x04) numTexCoords = 1;
if(flags & 0x80) numTexCoords = 2;
}
geo.texCoords = [];
if(numTexCoords > 0)
while(numTexCoords--)
geo.texCoords.push([]);
if(flags & 0x08)
geo.prelit = [];
while(numMorphTargets--){
let mt = { vertices: [] };
if(flags & 0x10)
mt.normals = [];
geo.morphTargets.push(mt);
}
return geo;
}
/* RpAtomic */
function
RpAtomicCreate()
{
return {
type: rwID_ATOMIC,
frame: null,
geometry: null,
visible: true,
pipeline: defaultPipe
};
}
function
RpAtomicSetFrame(a, f)
{
a.frame = f;
f.objects.push(a);
}
function
RpAtomicRender(atomic)
{
if(atomic.geometry.instData === undefined)
instanceGeo(atomic.geometry);
atomic.pipeline.renderCB(atomic);
}
/* RpClump */
function
RpClumpCreate()
{
return {
type: rwID_CLUMP,
frame: null,
atomics: []
};
}
function RpClumpSetFrame(c, f) { c.frame = f; }
function
RpClumpRender(clump)
{
for(let i = 0; i < clump.atomics.length; i++)
RpAtomicRender(clump.atomics[i]);
}
/* Instancing */
function
instanceGeo(geo)
{
let header = {
prim: gl.TRIANGLES,
totalNumVertices: geo.numVertices,
totalNumIndices: geo.totalMeshIndices,
vbo: gl.createBuffer(),
ibo: gl.createBuffer(),
inst: [],
attribs: []
};
if(geo.meshtype == 1)
header.prim = gl.TRIANGLE_STRIP;
// instance indices
let buffer = new ArrayBuffer(header.totalNumIndices*2);
offset = 0;
for(let i = 0; i < geo.meshes.length; i++){
m = geo.meshes[i];
inst = {
material: m.material,
numIndices: m.indices.length,
offset: offset
};
let indices = new Uint16Array(buffer, inst.offset, inst.numIndices);
indices.set(m.indices);
offset += inst.numIndices*2;
header.inst.push(inst);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, header.ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
geo.instData = header;
instanceVertices(geo);
}
function
instanceVertices(geo)
{
let i;
let stride = 0;
let attribs = [];
attribs.push({
index: ATTRIB_POS,
size: 3,
type: gl.FLOAT,
normalized: false,
offset: stride
});
stride += 12;
if(geo.morphTargets[0].normals){
attribs.push({
index: ATTRIB_NORMAL,
size: 3,
type: gl.FLOAT,
normalized: false,
offset: stride
});
stride += 12;
}
if(geo.prelit){
attribs.push({
index: ATTRIB_COLOR,
size: 4,
type: gl.UNSIGNED_BYTE,
normalized: true,
offset: stride
});
stride += 4;
}
for(i = 0; i < geo.texCoords.length; i++){
attribs.push({
index: ATTRIB_TEXCOORDS0 + i,
size: 2,
type: gl.FLOAT,
normalized: false,
offset: stride
});
stride += 8;
}
for(i = 0; i < attribs.length; i++)
attribs[i].stride = stride;
header = geo.instData;
header.attribs = attribs;
let buffer = new ArrayBuffer(header.totalNumVertices*stride);
// instance verts
for(i = 0; attribs[i].index != ATTRIB_POS; i++);
let a = attribs[i];
instV3d(buffer, a.offset, a.stride, geo.morphTargets[0].vertices, header.totalNumVertices);
if(geo.morphTargets[0].normals){
for(i = 0; attribs[i].index != ATTRIB_NORMAL; i++);
let a = attribs[i];
instV3d(buffer, a.offset, a.stride, geo.morphTargets[0].normals, header.totalNumVertices);
}
if(geo.prelit){
for(i = 0; attribs[i].index != ATTRIB_COLOR; i++);
let a = attribs[i];
instRGBA(buffer, a.offset, a.stride, geo.prelit, header.totalNumVertices);
}
for(i = 0; i < geo.texCoords.length; i++){
for(j = 0; attribs[j].index != ATTRIB_TEXCOORDS0 + i; j++);
let a = attribs[j];
instV2d(buffer, a.offset, a.stride, geo.texCoords[i], header.totalNumVertices);
}
gl.bindBuffer(gl.ARRAY_BUFFER, header.vbo);
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
}
function
instV3d(buffer, offset, stride, src, n)
{
let view = new DataView(buffer);
let o = offset;
for(let i = 0; i < n; i++){
view.setFloat32(o+0, src[i][0], true);
view.setFloat32(o+4, src[i][1], true);
view.setFloat32(o+8, src[i][2], true);
o += stride;
}
}
function
instV2d(buffer, offset, stride, src, n)
{
let view = new DataView(buffer);
let o = offset;
for(let i = 0; i < n; i++){
view.setFloat32(o+0, src[i][0], true);
view.setFloat32(o+4, src[i][1], true);
o += stride;
}
}
function
instRGBA(buffer, offset, stride, src, n)
{
let view = new DataView(buffer);
let o = offset;
for(let i = 0; i < n; i++){
view.setUint8(o+0, src[i][0]);
view.setUint8(o+1, src[i][1]);
view.setUint8(o+2, src[i][2]);
view.setUint8(o+3, src[i][3]);
o += stride;
}
}
/* The Init functions are for when we
* read a json structure produced by convdff */
function
rwFrameInit(f)
{
f.parent = null;
f.root = f;
f.child = null;
f.next = null;
f.objects = [];
m = f.matrix;
f.matrix = mat4.fromValues(
m[0], m[1], m[2], 0,
m[3], m[4], m[5], 0,
m[6], m[7], m[8], 0,
m[9], m[10], m[11], 1);
f.ltm = mat4.create();
mat4.copy(f.ltm, f.matrix);
}
function
rpMaterialInit(m)
{
if(m.texture)
m.texture = RwTextureRead(m.texture.name, m.texture.mask);
if(m.matfx && m.matfx.envTex)
m.matfx.envTex = RwTextureRead(m.matfx.envTex.name, m.matfx.envTex.mask);
if(m.specMat)
m.specMat.texture = RwTextureRead(m.specMat.texture, "");
}
function
rpGeometryInit(g)
{
for(let i = 0; i < g.materials.length; i++){
m = g.materials[i];
rpMaterialInit(m);
}
for(let i = 0; i < g.meshes.length; i++){
g.meshes[i].material = g.materials[g.meshes[i].matId];
delete g.meshes[i].matId;
}
g.numVertices = g.morphTargets[0].vertices.length;
g.totalMeshIndices = 0;
for(let i = 0; i < g.meshes.length; i++)
g.totalMeshIndices += g.meshes[i].indices.length;
}
function
rpAtomicInit(atomic)
{
atomic.type = rwID_ATOMIC;
atomic.frame = null;
atomic.visible = true;
atomic.pipeline = defaultPipe;
atomic.objects = [];
if(atomic.matfx)
atomic.pipeline = matFXPipe;
}
function
rpClumpInit(clump)
{
for(let i = 0; i < clump.atomics.length; i++){
let a = clump.atomics[i];
let f = clump.frames[a.frame];
rpAtomicInit(a);
RpAtomicSetFrame(a, f);
rpGeometryInit(a.geometry);
instanceGeo(a.geometry)
}
clump.frame = null;
for(let i = 0; i < clump.frames.length; i++){
let f = clump.frames[i];
p = f.parent;
rwFrameInit(f);
if(p >= 0)
RwFrameAddChild(clump.frames[p], f);
else
RpClumpSetFrame(clump, f);
}
delete clump.frames;
rwFrameSynchLTM(clump.frame);
}

260
rwrender.js Normal file
View File

@ -0,0 +1,260 @@
var RenderPass = -1;
var defaultPipe = {
renderCB: defaultRenderCB
};
var matFXPipe = {
renderCB: matfxRenderCB
};
var carPipe = {
renderCB: carRenderCB
};
function
RenderThisPass(mat)
{
switch(RenderPass){
case 0: // opaque
return mat.color[3] == 255;
case 1: // transparent
return mat.color[3] != 255;
}
return true;
}
function
uploadState(proginfo)
{
gl.uniformMatrix4fv(proginfo.u.u_proj, false, state.projectionMatrix);
gl.uniformMatrix4fv(proginfo.u.u_view, false, state.viewMatrix);
gl.uniformMatrix4fv(proginfo.u.u_world, false, state.worldMatrix);
if(proginfo.u.u_env)
gl.uniformMatrix4fv(proginfo.u.u_env, false, state.envMatrix);
gl.uniform3fv(proginfo.u.u_ambLight, state.ambLight);
gl.uniform3fv(proginfo.u.u_lightDir, state.lightDir);
gl.uniform3fv(proginfo.u.u_lightCol, state.lightCol);
gl.uniform1i(proginfo.u.u_tex0, 0);
gl.uniform1i(proginfo.u.u_tex1, 1);
gl.uniform1i(proginfo.u.u_tex2, 2);
gl.uniform1f(proginfo.u.u_alphaRef, state.alphaRef);
}
function
setAttributes(attribs, proginfo)
{
for(let i = 0; i < attribs.length; i++){
a = attribs[i];
if(proginfo.a[a.index] < 0)
continue;
gl.vertexAttribPointer(proginfo.a[a.index],
a.size, a.type,
a.normalized,
a.stride, a.offset);
gl.enableVertexAttribArray(proginfo.a[a.index]);
}
}
function
resetAttributes(attribs, proginfo)
{
for(let i = 0; i < attribs.length; i++){
a = attribs[i];
if(proginfo.a[a.index] >= 0)
gl.disableVertexAttribArray(proginfo.a[a.index]);
}
}
function
defaultRenderCB(atomic)
{
if(!atomic.visible)
return;
mat4.copy(state.worldMatrix, atomic.frame.ltm);
let header = atomic.geometry.instData;
gl.bindBuffer(gl.ARRAY_BUFFER, header.vbo);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, header.ibo);
let prg = defaultProgram;
gl.useProgram(prg.program);
setAttributes(header.attribs, prg);
uploadState(prg);
for(let i = 0; i < header.inst.length; i++){
inst = header.inst[i];
m = inst.material;
if(!RenderThisPass(m))
continue;
gl.activeTexture(gl.TEXTURE0);
if(m.texture)
gl.bindTexture(gl.TEXTURE_2D, m.texture.tex);
else
gl.bindTexture(gl.TEXTURE_2D, whitetex);
vec4.scale(state.matColor, m.color, 1.0/255.0);
state.surfaceProps[0] = m.surfaceProperties[0];
state.surfaceProps[1] = m.surfaceProperties[1];
state.surfaceProps[2] = m.surfaceProperties[2];
gl.uniform4fv(prg.u.u_matColor, state.matColor);
gl.uniform4fv(prg.u.u_surfaceProps, state.surfaceProps);
if(m.color[3] != 255 || m.texture){
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
}else
gl.disable(gl.BLEND);
gl.drawElements(header.prim, inst.numIndices, gl.UNSIGNED_SHORT, inst.offset);
}
resetAttributes(header.attribs, programInfo);
}
var envMatScale = mat4.fromValues(
-0.5, 0.0, 0.0, 0.0,
0.0, -0.5, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.5, 0.5, 0.0, 1.0
);
function
matfxRenderCB(atomic)
{
if(!atomic.visible)
return;
mat4.copy(state.worldMatrix, atomic.frame.ltm);
let tmp = mat4.create();
mat4.invert(tmp, envFrame.ltm);
mat4.multiply(tmp, tmp, state.viewMatrix);
tmp[12] = tmp[13] = tmp[14] = 0.0;
mat4.multiply(state.envMatrix, envMatScale, tmp);
let header = atomic.geometry.instData;
gl.bindBuffer(gl.ARRAY_BUFFER, header.vbo);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, header.ibo);
let prg = envMapProgram;
gl.useProgram(prg.program);
setAttributes(header.attribs, prg);
uploadState(prg);
for(let i = 0; i < header.inst.length; i++){
inst = header.inst[i];
m = inst.material;
if(!RenderThisPass(m))
continue;
gl.activeTexture(gl.TEXTURE0);
if(m.texture)
gl.bindTexture(gl.TEXTURE_2D, m.texture.tex);
else
gl.bindTexture(gl.TEXTURE_2D, whitetex);
gl.activeTexture(gl.TEXTURE1);
envcoef = 0.0;
if(m.matfx && m.matfx.envTex){
envcoef = m.matfx.envCoefficient;
gl.bindTexture(gl.TEXTURE_2D, m.matfx.envTex.tex);
}else
gl.bindTexture(gl.TEXTURE_2D, null);
vec4.scale(state.matColor, m.color, 1.0/255.0);
state.surfaceProps[0] = m.surfaceProperties[0];
state.surfaceProps[1] = envcoef;
state.surfaceProps[2] = m.surfaceProperties[2];
gl.uniform4fv(prg.u.u_matColor, state.matColor);
gl.uniform4fv(prg.u.u_surfaceProps, state.surfaceProps);
if(m.color[3] != 255 || m.texture){
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
}else
gl.disable(gl.BLEND);
gl.drawElements(header.prim, inst.numIndices, gl.UNSIGNED_SHORT, inst.offset);
}
resetAttributes(header.attribs, programInfo);
}
function
carRenderCB(atomic)
{
if(!atomic.visible)
return;
mat4.copy(state.worldMatrix, atomic.frame.ltm);
let header = atomic.geometry.instData;
gl.bindBuffer(gl.ARRAY_BUFFER, header.vbo);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, header.ibo);
let prg = carPS2Program;
gl.useProgram(prg.program);
setAttributes(header.attribs, prg);
uploadState(prg);
for(let i = 0; i < header.inst.length; i++){
inst = header.inst[i];
m = inst.material;
if(!RenderThisPass(m))
continue;
gl.activeTexture(gl.TEXTURE0);
if(m.texture)
gl.bindTexture(gl.TEXTURE_2D, m.texture.tex);
else
gl.bindTexture(gl.TEXTURE_2D, whitetex);
let shininess = 0.0;
let specularity = 0.0;
if(m.fxFlags & 3){
shininess = m.envMap.shininess;
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, m.envMap.texture.tex);
}
if(m.fxFlags & 4){
specularity = m.specMap.specularity;
gl.activeTexture(gl.TEXTURE2);
gl.bindTexture(gl.TEXTURE_2D, m.specMap.texture.tex);
}
vec4.scale(state.matColor, m.color, 1.0/255.0);
state.surfaceProps[0] = m.surfaceProperties[0];
state.surfaceProps[1] = specularity;
state.surfaceProps[2] = m.surfaceProperties[2];
state.surfaceProps[3] = shininess;
gl.uniform4fv(prg.u.u_matColor, state.matColor);
gl.uniform4fv(prg.u.u_surfaceProps, state.surfaceProps);
if(m.color[3] != 255 || m.texture){
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
}else
gl.disable(gl.BLEND);
gl.drawElements(header.prim, inst.numIndices, gl.UNSIGNED_SHORT, inst.offset);
}
resetAttributes(header.attribs, programInfo);
}

623
rwstream.js Normal file
View File

@ -0,0 +1,623 @@
function
AttachPlugins()
{
frameTKList[rwID_NODENAME] = { streamRead: NodeNameStreamRead };
geometryTKList[rwID_BINMESHPLUGIN] = { streamRead: rpMeshRead };
materialTKList[rwID_MATERIALEFFECTSPLUGIN] = { streamRead: rpMatfxMaterialStreamRead };
materialTKList[rwID_ENVMAT] = { streamRead: envMatStreamRead };
materialTKList[rwID_SPECMAT] = { streamRead: specMatStreamRead };
atomicTKList[rwID_MATERIALEFFECTSPLUGIN] = { streamRead: rpMatfxAtomicStreamRead };
}
function
RwStreamCreate(buffer)
{
return {
buffer: buffer,
view: new DataView(buffer),
offset: 0,
eof: false
};
}
function
RwStreamSkip(stream, len)
{
stream.offset += len;
}
function
RwStreamReadUInt8(stream)
{
if(stream.offset >= stream.buffer.byteLength){
stream.eof = true;
return null;
}
let v = stream.view.getUint8(stream.offset, true);
stream.offset += 1;
return v;
}
function
RwStreamReadUInt16(stream)
{
if(stream.offset >= stream.buffer.byteLength){
stream.eof = true;
return null;
}
let v = stream.view.getUint16(stream.offset, true);
stream.offset += 2;
return v;
}
function
RwStreamReadUInt32(stream)
{
if(stream.offset >= stream.buffer.byteLength){
stream.eof = true;
return null;
}
let v = stream.view.getUint32(stream.offset, true);
stream.offset += 4;
return v;
}
function
RwStreamReadInt32(stream)
{
if(stream.offset >= stream.buffer.byteLength){
stream.eof = true;
return null;
}
let v = stream.view.getInt32(stream.offset, true);
stream.offset += 4;
return v;
}
function
RwStreamReadReal(stream)
{
if(stream.offset >= stream.buffer.byteLength){
stream.eof = true;
return null;
}
let v = stream.view.getFloat32(stream.offset, true);
stream.offset += 4;
return v;
}
function
RwStreamReadString(stream, length)
{
if(stream.offset >= stream.buffer.byteLength){
stream.eof = true;
return null;
}
let a = new Uint8Array(stream.buffer, stream.offset, length);
let s = "";
for(let i = 0; i < length && a[i] != 0; i++)
s += String.fromCharCode(a[i]);
stream.offset += length;
return s;
}
function
rwStreamReadChunkHeader(stream)
{
let t = RwStreamReadUInt32(stream);
let l = RwStreamReadUInt32(stream);
let id = RwStreamReadUInt32(stream);
if(stream.eof)
return null;
let version = 0;
let build = 0;
if((id & 0xFFFF0000) == 0){
version = id<<8;
build = 0;
}else{
version = ((id>>14) & 0x3FF00) + 0x30000 |
((id>>16) & 3);
build = id & 0xFFFF;
}
return {
type: t,
length: l,
version: version,
build: build,
};
}
function
RwStreamFindChunk(stream, type)
{
let header;
while(header = rwStreamReadChunkHeader(stream)){
if(header.type == type)
return header;
RwStreamSkip(stream, header.length);
}
return null;
}
function
rwPluginRegistryReadDataChunks(tklist, stream, object)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_EXTENSION)) == null)
return null;
let end = stream.offset + header.length;
while(stream.offset < end){
header = rwStreamReadChunkHeader(stream);
if(header.type in tklist && tklist[header.type].streamRead){
if(!tklist[header.type].streamRead(stream, object, header.length))
return null;
}else
RwStreamSkip(stream, header.length);
}
return 1;
}
function
rwStringStreamFindAndRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRING)) == null)
return null;
return RwStreamReadString(stream, header.length);
}
function
rwFrameListStreamRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let numFrames = RwStreamReadInt32(stream);
let frames = [];
for(let i = 0; i < numFrames; i++){
let xx = RwStreamReadReal(stream);
let xy = RwStreamReadReal(stream);
let xz = RwStreamReadReal(stream);
let yx = RwStreamReadReal(stream);
let yy = RwStreamReadReal(stream);
let yz = RwStreamReadReal(stream);
let zx = RwStreamReadReal(stream);
let zy = RwStreamReadReal(stream);
let zz = RwStreamReadReal(stream);
let wx = RwStreamReadReal(stream);
let wy = RwStreamReadReal(stream);
let wz = RwStreamReadReal(stream);
frame = RwFrameCreate();
mat4.set(frame.matrix,
xx, xy, xz, 0,
yx, yy, yz, 0,
zx, zy, zz, 0,
wx, wy, wz, 1);
frames.push(frame);
let parent = RwStreamReadInt32(stream);
RwStreamReadInt32(stream); // unused
if(parent >= 0)
RwFrameAddChild(frames[parent], frame);
}
for(let i = 0; i < numFrames; i++)
if(!rwPluginRegistryReadDataChunks(frameTKList, stream, frames[i]))
return null;
return frames;
}
function
RwTextureStreamRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let flags = RwStreamReadUInt32(stream); // we ignore this
let name = rwStringStreamFindAndRead(stream);
if(name == null) return null;
let mask = rwStringStreamFindAndRead(stream);
if(mask == null) return null;
let tex = RwTextureRead(name, mask);
if(!rwPluginRegistryReadDataChunks(textureTKList, stream, tex))
return null;
return tex;
}
function
RpMaterialStreamRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let mat = RpMaterialCreate();
RwStreamReadInt32(stream); // flags, unused
mat.color[0] = RwStreamReadUInt8(stream);
mat.color[1] = RwStreamReadUInt8(stream);
mat.color[2] = RwStreamReadUInt8(stream);
mat.color[3] = RwStreamReadUInt8(stream);
RwStreamReadInt32(stream); // unused
let textured = RwStreamReadInt32(stream);
mat.surfaceProperties[0] = RwStreamReadReal(stream);
mat.surfaceProperties[1] = RwStreamReadReal(stream);
mat.surfaceProperties[2] = RwStreamReadReal(stream);
if(textured){
if((header = RwStreamFindChunk(stream, rwID_TEXTURE)) == null)
return null;
mat.texture = RwTextureStreamRead(stream);
if(mat.texture == null)
return null;
}
if(!rwPluginRegistryReadDataChunks(materialTKList, stream, mat))
return null;
return mat;
}
function
rpMaterialListStreamRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let numMaterials = RwStreamReadInt32(stream);
let indices = [];
while(numMaterials--)
indices.push(RwStreamReadInt32(stream));
let materials = []
for(let i = 0; i < indices.length; i++){
if(indices[i] >= 0)
materials.push(materials[indices[i]]);
else{
if((header = RwStreamFindChunk(stream, rwID_MATERIAL)) == null)
return null;
let m = RpMaterialStreamRead(stream);
if(m == null)
return null;
materials.push(m);
}
}
return materials;
}
function
RpGeometryStreamRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let flags = RwStreamReadUInt32(stream);
let numTriangles = RwStreamReadInt32(stream);
let numVertices = RwStreamReadInt32(stream);
let numMorphTargets = RwStreamReadInt32(stream);
if(header.version < 0x34000)
RwStreamSkip(stream, 12);
if(flags & 0x01000000) return null; // native geometry not supported
let geo = RpGeometryCreate(flags, numMorphTargets);
geo.numVertices = numVertices;
if(geo.prelit)
for(let i = 0; i < numVertices; i++){
let r = RwStreamReadUInt8(stream);
let g = RwStreamReadUInt8(stream);
let b = RwStreamReadUInt8(stream);
let a = RwStreamReadUInt8(stream);
geo.prelit.push([r, g, b, a]);
}
for(let i = 0; i < geo.texCoords.length; i++){
let texCoords = geo.texCoords[i];
for(let j = 0; j < numVertices; j++){
let u = RwStreamReadReal(stream);
let v = RwStreamReadReal(stream);
texCoords.push([u, v]);
}
}
for(let i = 0; i < numTriangles; i++){
let w1 = RwStreamReadUInt32(stream);
let w2 = RwStreamReadUInt32(stream);
let v1 = w1>>16 & 0xFFFF;
let v2 = w1 & 0xFFFF;
let v3 = w2>>16 & 0xFFFF;
let matid = w2 & 0xFFFF;
geo.triangles.push([v1, v2, v3, matid]);
}
for(let i = 0; i < numMorphTargets; i++){
let mt = geo.morphTargets[i];
RwStreamSkip(stream, 4*4 + 4 + 4); // ignore bounding sphere and flags
for(let j = 0; j < numVertices; j++){
let x = RwStreamReadReal(stream);
let y = RwStreamReadReal(stream);
let z = RwStreamReadReal(stream);
mt.vertices.push([x, y, z]);
}
if(mt.normals)
for(let j = 0; j < numVertices; j++){
let x = RwStreamReadReal(stream);
let y = RwStreamReadReal(stream);
let z = RwStreamReadReal(stream);
mt.normals.push([x, y, z]);
}
}
if((header = RwStreamFindChunk(stream, rwID_MATLIST)) == null)
return null;
geo.materials = rpMaterialListStreamRead(stream);
if(geo.materials == null)
return null;
if(!rwPluginRegistryReadDataChunks(geometryTKList, stream, geo))
return null;
return geo;
}
function
rpMeshRead(stream, geo, length)
{
geo.meshtype = RwStreamReadInt32(stream);
let numMeshes = RwStreamReadInt32(stream);
geo.totalMeshIndices = RwStreamReadInt32(stream);
while(numMeshes--){
let numIndices = RwStreamReadInt32(stream);
let matid = RwStreamReadInt32(stream);
let m = {
indices: [],
material: geo.materials[matid]
};
while(numIndices--)
m.indices.push(RwStreamReadInt32(stream));
geo.meshes.push(m);
}
return geo;
}
function
rpGeometryListStreamRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let numGeoms = RwStreamReadInt32(stream);
let geoms = []
while(numGeoms--){
if((header = RwStreamFindChunk(stream, rwID_GEOMETRY)) == null)
return null;
let g = RpGeometryStreamRead(stream);
if(g == null)
return null;
geoms.push(g);
}
return geoms;
}
function
rpClumpAtomicStreamRead(stream, frames, geos)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let atomic = RpAtomicCreate();
let frame = RwStreamReadInt32(stream);
let geometry = RwStreamReadInt32(stream);
let flags = RwStreamReadInt32(stream); // ignored
RwStreamReadInt32(stream); // unused
RpAtomicSetFrame(atomic, frames[frame]);
atomic.geometry = geos[geometry];
if(!rwPluginRegistryReadDataChunks(atomicTKList, stream, atomic))
return null;
return atomic;
}
function
RpClumpStreamRead(stream)
{
let header;
if((header = RwStreamFindChunk(stream, rwID_STRUCT)) == null)
return null;
let numAtomics = RwStreamReadInt32(stream);
let numLights = 0;
let numCameras = 0;
if(header.version > 0x33000){
numLights = RwStreamReadInt32(stream);
numCameras = RwStreamReadInt32(stream);
}
if((header = RwStreamFindChunk(stream, rwID_FRAMELIST)) == null)
return null;
frames = rwFrameListStreamRead(stream);
if(frames == null)
return null;
clump = RpClumpCreate();
RpClumpSetFrame(clump, frames[0]);
if((header = RwStreamFindChunk(stream, rwID_GEOMETRYLIST)) == null)
return null;
geos = rpGeometryListStreamRead(stream);
if(geos == null)
return null;
while(numAtomics--){
if((header = RwStreamFindChunk(stream, rwID_ATOMIC)) == null)
return null;
let a = rpClumpAtomicStreamRead(stream, frames, geos);
if(a == null)
return null;
clump.atomics.push(a);
}
if(!rwPluginRegistryReadDataChunks(clumpTKList, stream, clump))
return null;
rwFrameSynchLTM(clump.frame);
// TODO? lights, cameras
return clump;
}
/*
* Plugins
*/
/* MatFX */
var rpMATFXEFFECTBUMPMAP = 1;
var rpMATFXEFFECTENVMAP = 2;
var rpMATFXEFFECTBUMPENVMAP = 3;
var rpMATFXEFFECTDUAL = 4;
var rpMATFXEFFECTUVTRANSFORM = 5;
var rpMATFXEFFECTDUALUVTRANSFORM = 6;
function
RpMatFXMaterialSetEffects(mat, effects)
{
mat.matfx = {
type: effects,
bump: false,
env: false,
dual: false,
uvxform: false
};
// TODO: init the relevant fields here
switch(effects){
case rpMATFXEFFECTBUMPMAP:
mat.matfx.bump = true;
break;
case rpMATFXEFFECTENVMAP:
mat.matfx.env = true;
break;
case rpMATFXEFFECTBUMPENVMAP:
mat.matfx.bump = true;
mat.matfx.env = true;
break;
case rpMATFXEFFECTDUAL:
mat.matfx.dual = true;
break;
case rpMATFXEFFECTUVTRANSFORM:
mat.matfx.uvxform = true;
break;
case rpMATFXEFFECTDUALUVTRANSFORM:
mat.matfx.dual = true;
mat.matfx.uvxform = true;
break;
}
}
function
rpMatfxMaterialStreamRead(stream, mat, length)
{
let header;
let effects = RwStreamReadInt32(stream);
RpMatFXMaterialSetEffects(mat, effects);
let mfx = mat.matfx;
for(let i = 0; i < 2; i++){
let type = RwStreamReadInt32(stream);
switch(type){
case rpMATFXEFFECTBUMPMAP:
mfx.bumpCoefficient = RwStreamReadReal(stream);
if(RwStreamReadInt32(stream)){
if((header = RwStreamFindChunk(stream, rwID_TEXTURE)) == null)
return null;
mfx.bumpedTex = RwTextureStreamRead(stream);
if(mfx.bumpedTex == null)
return null;
}
if(RwStreamReadInt32(stream)){
if((header = RwStreamFindChunk(stream, rwID_TEXTURE)) == null)
return null;
mfx.bumpTex = RwTextureStreamRead(stream);
if(mfx.bumpTex == null)
return null;
}
break;
case rpMATFXEFFECTENVMAP:
mfx.envCoefficient = RwStreamReadReal(stream);
mfx.envFBalpha = RwStreamReadInt32(stream);
if(RwStreamReadInt32(stream)){
if((header = RwStreamFindChunk(stream, rwID_TEXTURE)) == null)
return null;
mfx.envTex = RwTextureStreamRead(stream);
if(mfx.envTex == null)
return null;
}
break;
case rpMATFXEFFECTDUAL:
mfs.srcBlend = RwStreamReadInt32(stream);
mfs.dstBlend = RwStreamReadInt32(stream);
if(RwStreamReadInt32(stream)){
if((header = RwStreamFindChunk(stream, rwID_TEXTURE)) == null)
return null;
mfx.dualTex = RwTextureStreamRead(stream);
if(mfx.dualTex == null)
return null;
}
break;
}
}
return mat;
}
function
rpMatfxAtomicStreamRead(stream, atomic, length)
{
atomic.matfx = RwStreamReadInt32(stream);
if(atomic.matfx)
atomic.pipeline = matFXPipe;
return atomic;
}
/* GTA Node Name */
function
NodeNameStreamRead(stream, frame, length)
{
frame.name = RwStreamReadString(stream, length);
return frame;
}
/* GTA Env Map */
function
envMatStreamRead(stream, mat, length)
{
let sclX = RwStreamReadReal(stream);
let sclY = RwStreamReadReal(stream);
let transSclX = RwStreamReadReal(stream);
let transSclY = RwStreamReadReal(stream);
let shininess = RwStreamReadReal(stream);
RwStreamReadInt32(stream); // ignore
mat.envMap = {
scale: [ sclX, sclY ],
transScale: [ transSclX, transSclY ],
shininess: shininess
};
return mat;
}
/* GTA Spec Map */
function
specMatStreamRead(stream, mat, length)
{
let specularity = RwStreamReadReal(stream);
let texname = RwStreamReadString(stream, 24);
mat.specMap = {
specularity: specularity,
texture: RwTextureRead(texname, "")
};
return mat;
}

189
shaders.js Normal file
View File

@ -0,0 +1,189 @@
const defaultVS = `
attribute vec3 in_pos;
attribute vec3 in_normal;
attribute vec4 in_color;
attribute vec2 in_tex0;
uniform mat4 u_world;
uniform mat4 u_view;
uniform mat4 u_proj;
uniform vec4 u_matColor;
uniform vec4 u_surfaceProps;
uniform vec3 u_ambLight;
uniform vec3 u_lightDir;
uniform vec3 u_lightCol;
varying highp vec4 v_color;
varying highp vec2 v_tex0;
void main() {
gl_Position = u_proj * u_view * u_world * vec4(in_pos, 1.0);
v_tex0 = in_tex0;
v_color = in_color;
v_color.rgb += u_ambLight*u_surfaceProps.x;
vec3 N = mat3(u_world) * in_normal;
float L = max(0.0, dot(N, -normalize(u_lightDir)));
v_color.rgb += L*u_lightCol*u_surfaceProps.z;
v_color = clamp(v_color, 0.0, 1.0);
v_color *= u_matColor;
}
`;
const defaultFS = `
uniform sampler2D tex;
uniform highp float u_alphaRef;
varying highp vec4 v_color;
varying highp vec2 v_tex0;
void main() {
gl_FragColor = v_color*texture2D(tex, v_tex0);
if(gl_FragColor.a < u_alphaRef)
discard;
}
`;
const envVS = `
attribute vec3 in_pos;
attribute vec3 in_normal;
attribute vec4 in_color;
attribute vec2 in_tex0;
uniform mat4 u_world;
uniform mat4 u_view;
uniform mat4 u_proj;
uniform mat4 u_env;
uniform vec4 u_matColor;
uniform vec4 u_surfaceProps;
uniform vec3 u_ambLight;
uniform vec3 u_lightDir;
uniform vec3 u_lightCol;
varying highp vec4 v_color0;
varying highp vec4 v_color1;
varying highp vec2 v_tex0;
varying highp vec2 v_tex1;
void main() {
gl_Position = u_proj * u_view * u_world * vec4(in_pos, 1.0);
v_tex0 = in_tex0;
v_color0 = in_color;
v_color0.rgb += u_ambLight*u_surfaceProps.x;
vec3 N = mat3(u_world) * in_normal;
float L = max(0.0, dot(N, -normalize(u_lightDir)));
v_color0.rgb += L*u_lightCol*u_surfaceProps.z;
v_color0 = clamp(v_color0, 0.0, 1.0);
v_color0 *= u_matColor;
v_color1 = v_color0*u_surfaceProps.y;
v_tex1 = (u_env*vec4(N, 1.0)).xy;
}
`;
const envFS = `
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform highp float u_alphaRef;
varying highp vec4 v_color0;
varying highp vec4 v_color1;
varying highp vec2 v_tex0;
varying highp vec2 v_tex1;
void main() {
gl_FragColor = v_color0*texture2D(tex0, v_tex0);
if(gl_FragColor.a < u_alphaRef)
discard;
gl_FragColor.rgb += (v_color1*texture2D(tex1, v_tex1)).rgb;
}
`;
const carPS2VS = `
attribute vec3 in_pos;
attribute vec3 in_normal;
attribute vec4 in_color;
attribute vec2 in_tex0;
attribute vec2 in_tex1;
uniform mat4 u_world;
uniform mat4 u_view;
uniform mat4 u_proj;
uniform mat4 u_env;
uniform vec4 u_matColor;
uniform vec4 u_surfaceProps;
uniform vec3 u_ambLight;
uniform vec3 u_lightDir;
uniform vec3 u_lightCol;
varying highp vec4 v_color0;
varying highp vec4 v_color1;
varying highp vec4 v_color2;
varying highp vec2 v_tex0;
varying highp vec2 v_tex1;
varying highp vec2 v_tex2;
void main() {
gl_Position = u_proj * u_view * u_world * vec4(in_pos, 1.0);
v_tex0 = in_tex0;
v_color0 = in_color;
v_color0.rgb += u_ambLight*u_surfaceProps.x;
vec3 N = mat3(u_world) * in_normal;
float L = max(0.0, dot(N, -normalize(u_lightDir)));
v_color0.rgb += L*u_lightCol*u_surfaceProps.z;
v_color0 = clamp(v_color0, 0.0, 1.0);
v_color0 *= u_matColor;
v_tex1 = in_tex1;
v_color1 = vec4(1.5*u_surfaceProps.w);
N = mat3(u_view) * N;
vec3 D = mat3(u_view) * u_lightDir;
N = D - 2.0*N*dot(N, D);
v_tex2 = (N.xy + vec2(1.0, 1.0))/2.0;
if(N.z < 0.0)
v_color2 = vec4(0.75*u_surfaceProps.y);
else
v_color2 = vec4(0.0);
}
`;
const carPS2FS = `
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform highp float u_alphaRef;
varying highp vec4 v_color0;
varying highp vec4 v_color1;
varying highp vec4 v_color2;
varying highp vec2 v_tex0;
varying highp vec2 v_tex1;
varying highp vec2 v_tex2;
void main() {
gl_FragColor = v_color0*texture2D(tex0, v_tex0);
if(gl_FragColor.a < u_alphaRef)
discard;
gl_FragColor.rgb += (v_color1*texture2D(tex1, v_tex1)).rgb;
gl_FragColor.rgb += (v_color2*texture2D(tex2, v_tex2)).rgb;
}
`;

7
webgl.css Normal file
View File

@ -0,0 +1,7 @@
canvas {
border: 2px solid black;
background-color: black;
}
video {
display: none;
}