mirror of
https://github.com/GTAmodding/modelviewjs.git
synced 2025-07-09 02:20:14 +02:00
first commit
This commit is contained in:
parent
2d2119a869
commit
bea1a1c2a0
2
README.md
Normal file
2
README.md
Normal 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
6888
gl-matrix.js
Normal file
File diff suppressed because it is too large
Load Diff
105
gl-util.js
Normal file
105
gl-util.js
Normal 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
83
index.html
Normal 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
213
loaddata.js
Normal 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
430
main.js
Normal 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
556
rw.js
Normal 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
260
rwrender.js
Normal 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
623
rwstream.js
Normal 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
189
shaders.js
Normal 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;
|
||||
}
|
||||
`;
|
Loading…
x
Reference in New Issue
Block a user